<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>demonst&lt;span style=&quot;color:#595959FD;text-shadow:2px 4px 4px rgba(0,0,0,0.2),0px -5px 10px rgba(255,255,255,0.15);&quot;&gt;random&lt;/span&gt;■</title>
<link>https://demonstrandom.com/</link>
<atom:link href="https://demonstrandom.com/index.xml" rel="self" type="application/rss+xml"/>
<description>Random musings</description>
<generator>quarto-1.5.55</generator>
<lastBuildDate>Tue, 17 Mar 2026 04:00:00 GMT</lastBuildDate>
<item>
  <title>Building a Minimal Computational Invariant Theory Library</title>
  <link>https://demonstrandom.com/symmetry/posts/computational_invariant_theory/</link>
  <description><![CDATA[ 





<p><a href="https://en.wikipedia.org/wiki/Theo_van_Doesburg"><img src="https://demonstrandom.com/symmetry/posts/computational_invariant_theory/arithmetic-composition-1929.jpg" class="img-fluid" style="width:45.0%" alt="Theo van Doesburg. *Arithmetic Composition*. (1930)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>Now that we’ve investigated the basics of <a href="../../../symmetry/posts/invariant_theory/index.html">invariant theory</a>, we can look at how to compute invariants with code. In this post, I’ll investigate computational invariant theory, with an emphasis on the algorithmic aspects. I’m vaguely following <a href="https://www.math.uni-sb.de/ag/schwarz/alggeo/invarianttheory.html">Derksen and Kemper’s book</a>.</p>
<p>This is once again a long post, mixing theory, algorithms, and code. I used Claude to help with the code and the writing, but I also steered and reviewed heavily. Full code will be made available shortly.</p>
<!-- Code is [here](). -->
</section>
<section id="background" class="level1">
<h1>Background</h1>
<p>We are studying the action of a group <img src="https://latex.codecogs.com/png.latex?G"> on a vector space <img src="https://latex.codecogs.com/png.latex?V">. We want to understand the structure of the ring of invariants <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5BV%5D%5EG">, which consists of all polynomial functions on <img src="https://latex.codecogs.com/png.latex?V"> that are invariant under the action of <img src="https://latex.codecogs.com/png.latex?G">.</p>
<p>That is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%7BC%7D%5BV%5D%5EG%20=%20%5C%7B%20f%20%5Cin%20%5Cmathbb%7BC%7D%5BV%5D%20:%20f(g%20%5Ccdot%20v)%20=%20f(v)%20%5Ctext%7B%20for%20all%20%7D%20g%20%5Cin%20G,%20v%20%5Cin%20V%20%5C%7D%0A"></p>
<p>Last time, we established a rudimentary <a href="../../../symmetry/posts/invariant_theory/index.html#overall-flow">process</a> for computing invariants based on properties of the group action. The process was (roughly):</p>
<ol type="1">
<li>Is <img src="https://latex.codecogs.com/png.latex?G"> finite? If so, we can compute the invariants by averaging over the group using the Reynolds operator.</li>
<li>Is the group infinite, but compact? If so, we can compute the invariants by integrating over the group with respect to the Haar measure (also Reynolds operator). The Haar measure depends on the topology of <img src="https://latex.codecogs.com/png.latex?G">:
<ol type="a">
<li>If <img src="https://latex.codecogs.com/png.latex?G"> is a Lie group, we can sometimes construct the Haar measure explicitly via the Maurer-Cartan form.</li>
<li>There are other cases, but we won’t be concerned with them here.</li>
</ol></li>
<li>Is the group noncompact but reductive? If so, a Reynolds operator exists, but the actual computation uses representation theory to identify the equivariant projections directly.</li>
<li>Is the group non-reductive? Idiosyncratic methods exist for specific cases.</li>
</ol>
<p>In this post I’ll focus on the first three cases, which are the most common and well-studied. In particular, we’ll look at how to compute invariants for finite groups, compact Lie groups, and reductive groups.</p>
</section>
<section id="data-structures" class="level1">
<h1>Data Structures</h1>
<p>What are we actually computing here? As inputs to the functions, we’ll need a way to encode a group <img src="https://latex.codecogs.com/png.latex?G"> and its action on a structured set <img src="https://latex.codecogs.com/png.latex?X"> (usually a vector space <img src="https://latex.codecogs.com/png.latex?V">). As outputs, we’ll want to compute a generating set for the ring of invariants <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5BV%5D%5EG">. That is, a set of invariants such that any invariant can be expressed as a polynomial in the generators.</p>
<p>How should we represent these as data structures in code?</p>
<section id="review-of-existing-libraries" class="level2">
<h2 class="anchored" data-anchor-id="review-of-existing-libraries">Review of Existing Libraries</h2>
<p>The data structures will ultimately dictate the algorithms we can use, so we want to choose them carefully. How do existing libraries for computational invariant theory, such as <a href="https://magma.maths.usyd.edu.au/magma/">Magma</a>, <a href="https://www.sagemath.org/">SageMath</a>, and <a href="https://www.singular.uni-kl.de/">Singular</a>, represent groups, group actions, and invariants? I took a quick look (mostly not that helpful) at the documentation and source code for these libraries to get a sense of how they approach these problems.</p>
<section id="magma" class="level3">
<h3 class="anchored" data-anchor-id="magma">Magma</h3>
<p><a href="https://magma.maths.usyd.edu.au/magma/handbook/invariant_theory">Magma</a> is a general computer algebra system maintained by the University of Sydney that includes functionality for computational invariant theory. The library uses a custom language, with the performance-critical algorithms implemented in C at the kernel level. The actual library is closed-source (which makes it hard to investigate the data structures).</p>
<p>Based on the documentation, Magma apparently uses a typing system that corresponds to algebraic categories, and the type system enforces particular mathematical structures. Unfortunately I couldn’t investigate too deeply.</p>
</section>
<section id="sagemath" class="level3">
<h3 class="anchored" data-anchor-id="sagemath">SageMath</h3>
<p><a href="https://www.sagemath.org/">SageMath</a> is an open-source computer algebra system with various libraries for different areas of mathematics. The chief language is Python, with performance-critical algorithms implemented in Cython or C. The architecture is “federated” in some sense, with interfaces to ~100 different external packages for different areas of mathematics, all made interoperable via a central type coercion system. It seems like SageMath is a nightmare to distribute due to the large number of dependencies, but the upside is that it has a huge variety of functionality.</p>
<p>The entire library is GPL, so we can look inside. For invariant theory, SageMath has a <a href="https://doc.sagemath.org/html/en/reference/polynomial_rings/sage/rings/invariants/invariant_theory.html">package</a> called “invariant_theory”, which mostly focuses on the action of <img src="https://latex.codecogs.com/png.latex?SL(n,%20%5Cmathbb%7BC%7D)"> on homogeneous polynomials (as in the <a href="../../../symmetry/posts/invariant_theory/index.html">classical invariant theory post</a>).</p>
<p>Under the hood, there is a “parent/element” framework (which is used to deal with the federated nature of the library). The parent object encodes the algebraic structure, and the element objects encode specific instances of that structure. So for a polynomial, the parent would be the space it lives in (<img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BQ%7D%5Bx%5D">) and the element would be a specific polynomial (<img src="https://latex.codecogs.com/png.latex?x%5E2%20+%201">). The parents are organized in a hierarchy of algebraic categories and then there’s a bunch of infra for managing all the types.</p>
<p>For invariant theory in particular, the wrapped library is Singular, so we should just look at that.</p>
</section>
<section id="singular" class="level3">
<h3 class="anchored" data-anchor-id="singular">Singular</h3>
<p>Singular looks like it’s designed for polynomial computations, especially commutative and non-commutative algebra, algebraic geometry, and singularity theory.</p>
<p>It also has an open-source C++ library for invariant theory. A bunch of the documentation is <a href="https://www.singular.uni-kl.de/Manual//4-0-3/sing_1664.htm#SEC1739">here</a>.</p>
<p>Since Singular is designed for polynomial computations, a lot of its data structures look to be defined for those purposes. For example, a monomial <img src="https://latex.codecogs.com/png.latex?x%5E2y%5E3z"> is a vector of exponents <img src="https://latex.codecogs.com/png.latex?(2,%203,%201)">, and a polynomial is a list of monomials and coefficients. Rings are a sort of global context, and ideals are represented as arrays of polynomial generators (it seems like they are actually arrays of pointers to polynomials).</p>
<p>Singular does have some functionality for group actions and invariants (including algorithms for Grobner bases and Hilbert series) but it’s focused on specific cases (like finite groups acting on polynomial rings) rather than a general framework for group actions.</p>
<p>In short, Singular looks like a cool/good library, but since we are mostly interested in group actions and invariants, it departs pretty heavily from the abstractions I think we would ideally want.</p>
</section>
</section>
<section id="data-structure-implementations" class="level2">
<h2 class="anchored" data-anchor-id="data-structure-implementations">Data Structure Implementations</h2>
<p>How ought we design our data structures? We want to be able to represent different types of groups (finite, classical, reductive) and their actions on different types of structured sets (vector spaces, affine varieties, etc). We also want to be able to represent the invariants themselves, which are usually polynomials or rational functions, as well as the relations among them, presentations, etc. Furthermore, I want to follow my minimalist, compositional style.</p>
<section id="polynomials" class="level3">
<h3 class="anchored" data-anchor-id="polynomials">Polynomials</h3>
<p>Let’s start with the invariants themselves, which are usually polynomials (or sometimes rational functions). We need a way to represent multivariate polynomials with rational coefficients. We need arithmetic operations on these polynomials (addition, multiplication, scaling), and the ability to evaluate them at specific points.</p>
<p>A monomial <img src="https://latex.codecogs.com/png.latex?x_0%5E%7Ba_0%7D%20x_1%5E%7Ba_1%7D%20%5Ccdots%20x_%7Bn-1%7D%5E%7Ba_%7Bn-1%7D%7D"> can be identified with its exponent tuple <img src="https://latex.codecogs.com/png.latex?(a_0,%20a_1,%20%5Cldots,%20a_%7Bn-1%7D)%20%5Cin%20%5Cmathbb%7BN%7D%5En">. So a polynomial is just a finite linear combination of monomials with rational coefficients. Internally, we can use a <code>dict</code> mapping tuples to <code>Fraction</code>s, with associated arithmetic operations:</p>
<div id="49d1bb6e" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Poly:</span>
<span id="cb1-2"></span>
<span id="cb1-3">    Type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...], Fraction]</span>
<span id="cb1-4"></span>
<span id="cb1-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> add(f, g): ...</span>
<span id="cb1-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mul(f, g): ...</span>
<span id="cb1-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> scale(c, f): ...</span>
<span id="cb1-11">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> evaluate(f, point) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Fraction: ...</span>
<span id="cb1-13"></span>
<span id="cb1-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -- Constructors --</span></span>
<span id="cb1-15">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mono(alpha, c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>): ...       </span>
<span id="cb1-17">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-18">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> var(i, n_vars): ...          </span>
<span id="cb1-19">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-20">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> const(value, n_vars): ...    </span>
<span id="cb1-21"></span>
<span id="cb1-22">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -- Leading term operations --</span></span>
<span id="cb1-23">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-24">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> leading_monomial(f, order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>grlex): ...</span>
<span id="cb1-25">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-26">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> leading_coefficient(f, order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Fraction: ...</span>
<span id="cb1-27"></span>
<span id="cb1-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -- Monomial operations (for Gröbner bases) --</span></span>
<span id="cb1-29">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-30">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mono_divides(a, b) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...   </span>
<span id="cb1-31">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-32">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mono_lcm(a, b): ...               </span>
<span id="cb1-33">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-34">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mono_mul(a, b): ...               </span>
<span id="cb1-35">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-36">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mono_div(a, b): ...               </span>
<span id="cb1-37"></span>
<span id="cb1-38">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -- Orderings --</span></span>
<span id="cb1-39">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-40">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> grlex(alpha):</span>
<span id="cb1-41">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Graded lexicographic: total degree first, then lex."""</span></span>
<span id="cb1-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(alpha), alpha)</span>
<span id="cb1-43"></span>
<span id="cb1-44">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb1-45">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> elimination_order(k):</span>
<span id="cb1-46">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Orders the variables so that x_0,...,x_{k-1} are ordered before the remaining variables. For Gröbner elimination."""</span></span>
<span id="cb1-47">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> order(alpha):</span>
<span id="cb1-48">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (alpha[:k], <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(alpha[k:]), alpha[k:])</span>
<span id="cb1-49">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> order</span></code></pre></div>
</div>
<p>For example, implementing the polynomial <img src="https://latex.codecogs.com/png.latex?3x_0%5E2%20x_1%20-%20x_1%5E3%20+%207"> in three variables:</p>
<div id="e30f0c88" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>): Fraction(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>): Fraction(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>): Fraction(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)}</span></code></pre></div>
</div>
<p>Why use <code>Fraction</code> instead of floats? For now, we will use exact arithmetic (where possible, there is at least one exception since I don’t want to implement a full computer algebra system). With <code>Fraction</code>, the Reynolds operator, orbit sums, and Gröbner reductions stay exact.</p>
<p>The method implementations aren’t shown above, but they are pretty straightforward. The notable ones are the leading term operations, which are defined with respect to a monomial ordering (we will need this for Gröbner bases).</p>
</section>
<section id="group-actions" class="level3">
<h3 class="anchored" data-anchor-id="group-actions">Group Actions</h3>
<p>We can represent the group <img src="https://latex.codecogs.com/png.latex?G"> as a set of generators and relations, or as a matrix group acting on <img src="https://latex.codecogs.com/png.latex?V"> (or some <img src="https://latex.codecogs.com/png.latex?X">). The choice of representation will depend on the specific group and the context of the problem. For example, if <img src="https://latex.codecogs.com/png.latex?G"> is a finite group, we can represent it as a list of its elements or as a permutation group. If <img src="https://latex.codecogs.com/png.latex?G"> is a Lie group, we can represent it using its Lie algebra and the exponential map. So we’ll need some abstraction to ensure that we can work with different types of groups in a unified way.</p>
<p>Let’s assume we have some group object that can act on an object <img src="https://latex.codecogs.com/png.latex?X">. We need something along the lines of:</p>
<div id="c71d9fdd" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Group:</span>
<span id="cb3-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> identity(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb3-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span>
<span id="cb3-4"></span>
<span id="cb3-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> multiply(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g, h):</span>
<span id="cb3-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span>
<span id="cb3-7"></span>
<span id="cb3-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> inverse(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g):</span>
<span id="cb3-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span></code></pre></div>
</div>
<div id="f9e7fb13" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> GroupAction:</span>
<span id="cb4-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> act(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g, x):</span>
<span id="cb4-3">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Apply group element g to object x."""</span></span>
<span id="cb4-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span></code></pre></div>
</div>
<p>As written, this is too abstract to be useful, since different group types compute invariants in different ways.</p>
<p>We will look at at least a few different types of groups (tori, finite groups, classical groups, and reductive groups), and the algorithms for computing invariants differ in each case. A torus solves an integer linear system, a finite group averages over its elements using the Reynolds operator, and a classical group hard-codes generators from the First Fundamental Theorems. There is no single <code>act</code> method we can implement that covers all of these.</p>
<p>However, the downstream API should be the same regardless of group type. In all cases, we test invariance, compute generators, compute the Hilbert series, test orbits, etc. So we should organize the abstractions around the actions, with closures that each group type can fill in as needed:</p>
<div id="ed19cb30" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span>(frozen<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Action:</span>
<span id="cb5-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Bundle of closures encoding a (group, space) invariant theory problem."""</span></span>
<span id="cb5-4">    is_invariant:        Callable[[Poly], <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>]</span>
<span id="cb5-5">    invariants_of_degree: Callable[[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>], <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]]</span>
<span id="cb5-6">    hilbert_coeffs:      Callable[[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>], <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb5-7">    orbit_test:          Callable <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb5-8">    apply_element:       Callable <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb5-9">    elements:            Callable <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb5-10">    n_vars:              <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span></code></pre></div>
</div>
<p>We will have each group class provide an <code>.action(space)</code> method that attaches each of its primitives and returns an <code>Action</code>. The derived API is then group-agnostic, and operates on <code>Action</code> objects.</p>
<p>What are the different closures we need to fill in for different group types? We need to be able to check if a polynomial is an invariant, find invariants of a given degree, compute the Hilbert series, test if two points are in the same orbit, apply a group element to an object, and list the group elements (if finite). Not all of these will be implemented for every group type, but if we can implement them we should.</p>
<div id="fb4defaf" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> invariant_theory(group, space) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Action:</span>
<span id="cb6-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Assemble an Action from a group and a space descriptor."""</span></span>
<span id="cb6-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> group.action(space)</span></code></pre></div>
</div>
<p>The idea behind this design is to encapsulate all the group-specific logic inside the group classes, and then have a uniform API for working with invariants that is independent of the group type. The <code>Action</code> object serves as a bridge between the group and the algorithms for computing invariants, allowing us to write algorithms that are agnostic to the specific group structure. So adding a new group type requires only implementing a class with <code>.action(space) -&gt; Action</code>. Everything else (generators, Hilbert series, orbit tests, separators) should work “automatically”.</p>
<section id="torus-actions" class="level4">
<h4 class="anchored" data-anchor-id="torus-actions">Torus Actions</h4>
<p>A torus <img src="https://latex.codecogs.com/png.latex?T%20=%20(%5Cmathbb%7BC%7D%5E*)%5Er"> acts on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5Em"> via an integer weight matrix <img src="https://latex.codecogs.com/png.latex?W"> (<img src="https://latex.codecogs.com/png.latex?r%20%5Ctimes%20m">). A monomial <img src="https://latex.codecogs.com/png.latex?x%5E%5Calpha"> is invariant if and only if <img src="https://latex.codecogs.com/png.latex?W%5Calpha%20=%200">. We will go through the actual theory for a torus action below. This case is simple enough that we don’t need Reynolds operators or representation theory, we can just use integer linear algebra.</p>
<div id="2ceace69" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Torus:</span>
<span id="cb7-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, W: np.ndarray):</span>
<span id="cb7-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.asarray(W, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>)</span>
<span id="cb7-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb7-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb7-6"></span>
<span id="cb7-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_invariant_monomial(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, alpha: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb7-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">all</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> np.array(alpha, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb7-9"></span>
<span id="cb7-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hilbert_basis(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]]:</span>
<span id="cb7-11">        ...</span></code></pre></div>
</div>
</section>
<section id="finite-groups" class="level4">
<h4 class="anchored" data-anchor-id="finite-groups">Finite Groups</h4>
<p>For finite groups, we need an explicit list of <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> matrices (one for each group element). The core operations are the Reynolds operator (average over the group), orbit sums (a particularly clean basis construction in the monomial/permutation cases), and the Molien series (Hilbert series via eigenvalues).</p>
<div id="eb30c32d" class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> FiniteGroup:</span>
<span id="cb8-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, matrices: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[np.ndarray]):</span>
<span id="cb8-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.matrices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> matrices</span>
<span id="cb8-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(matrices)</span>
<span id="cb8-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> matrices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb8-6"></span>
<span id="cb8-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> reynolds(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb8-8">        total: Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb8-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.matrices:</span>
<span id="cb8-10">            total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(total, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.apply_to_poly(g, f))</span>
<span id="cb8-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> poly.scale(Fraction(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.order), total)</span>
<span id="cb8-12"></span>
<span id="cb8-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> orbit_sum(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb8-14">        seen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>()</span>
<span id="cb8-15">        total: Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb8-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.matrices:</span>
<span id="cb8-17">            gf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.apply_to_poly(g, f)</span>
<span id="cb8-18">            key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">frozenset</span>(gf.items())</span>
<span id="cb8-19">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> key <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> seen:</span>
<span id="cb8-20">                seen.add(key)</span>
<span id="cb8-21">                total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(total, gf)</span>
<span id="cb8-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> total</span>
<span id="cb8-23"></span>
<span id="cb8-24">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> molien_coeffs(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, max_d: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]:</span>
<span id="cb8-25">        ...</span></code></pre></div>
</div>
<p>For common finite groups, we can provide constructors in a group library:</p>
<div id="74ff6f52" class="cell" data-execution_count="9">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> symmetric(n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> FiniteGroup:</span>
<span id="cb9-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""S_n acting on C^n by permutation matrices."""</span></span>
<span id="cb9-3">    ...</span>
<span id="cb9-4"></span>
<span id="cb9-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> cyclic(n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, dim: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> FiniteGroup:</span>
<span id="cb9-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Z/nZ acting on C^dim by rotation."""</span></span>
<span id="cb9-7">    ...</span>
<span id="cb9-8"></span>
<span id="cb9-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dihedral(n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> FiniteGroup:</span>
<span id="cb9-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""D_n acting on C^2 by rotations and reflections."""</span></span>
<span id="cb9-11">    ...</span></code></pre></div>
</div>
</section>
<section id="classical-groups" class="level4">
<h4 class="anchored" data-anchor-id="classical-groups">Classical Groups</h4>
<p>Classical groups (<img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BO%7D(n)">, <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSL%7D(n)">, <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSp%7D(2n)">) are infinite and continuous, so in principle the Reynolds-operator story is more complicated.</p>
<p>We could construct the Maurer-Cartan form, compute the Molien-Weyl integral for the Hilbert series, and then project the result onto trivial representations to extract the invariants. Alternatively, the First Fundamental Theorems of Invariant Theory (FFTs) give us explicit generators for the invariant ring, so we could just hard-code those and build the invariants as products.</p>
<div id="dbc4c223" class="cell" data-execution_count="10">
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> orthogonal_action(n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, k: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Action:</span>
<span id="cb10-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""O(n) acting diagonally on k copies of C^n.</span></span>
<span id="cb10-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Generators: inner products &lt;v_i, v_j&gt;."""</span></span>
<span id="cb10-4">    ...</span>
<span id="cb10-5"></span>
<span id="cb10-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sl_action(n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, k: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Action:</span>
<span id="cb10-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""SL(n) acting diagonally on k copies of C^n.</span></span>
<span id="cb10-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Generators: n x n bracket determinants."""</span></span>
<span id="cb10-9">    ...</span>
<span id="cb10-10"></span>
<span id="cb10-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> symplectic_action(n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, k: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Action:</span>
<span id="cb10-12">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Sp(2n) acting diagonally on k copies of C^{2n}.</span></span>
<span id="cb10-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Generators: symplectic pairings omega(v_i, v_j)."""</span></span>
<span id="cb10-14">    ...</span></code></pre></div>
</div>
<p>For <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BO%7D(n)">, the generators are inner products. For <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSL%7D(n)">, the generators are determinantal brackets. For <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSp%7D(2n)">, the generators are symplectic pairings.</p>
<p>Each of these returns an <code>Action</code> (the same interface as finite groups and tori), so downstream code for Hilbert series, presentations, and orbit separation should work unchanged. In the easy cases, we can get the Hilbert series by counting monomials in the generators rather than evaluating the Molien-Weyl integral.</p>
</section>
<section id="reductive-groups" class="level4">
<h4 class="anchored" data-anchor-id="reductive-groups">Reductive Groups</h4>
<p>For more general reductive groups (of particular interest is <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BGL%7D(n)"> acting by conjugation), we’d need the full representation-theoretic machinery. This means decompose the polynomial ring into irreducible representations and extract the trivial summands.</p>
<p>There’s no general algorithm to handle all possible cases. For some cases (once again, <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BGL%7D(n)">) invariants are generated by traces of products.</p>
<p>This is out of scope for this post, but in principle we could implement the algorithms in the same framework as the other group types, with the <code>Action</code> object providing the necessary closures for testing invariance, computing generators, and so on.</p>
</section>
</section>
</section>
<section id="spaces" class="level2">
<h2 class="anchored" data-anchor-id="spaces">Spaces</h2>
<p>We need to encode how the group action affects the vector space the group acts on. For polynomials:</p>
<div id="a9967f72" class="cell" data-execution_count="11">
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span>(frozen<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Space:</span>
<span id="cb11-3">    n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb11-4">    apply_matrix: Callable  </span>
<span id="cb11-5">    add: Callable</span>
<span id="cb11-6">    scale: Callable</span>
<span id="cb11-7">    zero: Callable</span>
<span id="cb11-8"></span>
<span id="cb11-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> polynomial_ring(n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Space:</span>
<span id="cb11-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Standard polynomial ring C[x_0, ..., x_{n-1}]"""</span></span>
<span id="cb11-11">    ...</span></code></pre></div>
</div>
<p>Since the space is defined separately, the algorithms can be group-agnostic. The Reynolds operator and orbit sums in <code>FiniteGroup</code> use <code>space.add</code>, <code>space.scale</code>, and <code>space.apply_matrix</code> rather than calling polynomial arithmetic directly. So we can extend this library to work on new object types by implementing new space descriptors with the appropriate operations without adjusting the group classes or derived API.</p>
</section>
</section>
<section id="computational-tasks" class="level1">
<h1>Computational Tasks</h1>
<p>Now that we have the data structures in place, we can ask what kinds of computations actually arise in invariant theory. From the last section, we have data structures for groups, group actions, and invariants. What are the key computational tasks we want to perform with these objects? That is, what should the API of our computational invariant theory library look like?</p>
<p>Could be something like this:</p>
<div id="4f36242c" class="cell" data-execution_count="12">
<div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Decision problems</span></span>
<span id="cb12-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_invariant(action: Action, f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span>
<span id="cb12-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> in_null_cone(action: Action, v: np.ndarray, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span>
<span id="cb12-4"></span>
<span id="cb12-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Construction problems</span></span>
<span id="cb12-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_generators(action: Action, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]: ...</span>
<span id="cb12-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_hilbert_series(action: Action, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]: ...</span>
<span id="cb12-8"></span>
<span id="cb12-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Presentation problems (Gröbner-based)</span></span>
<span id="cb12-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_relations(generators: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]: ...</span>
<span id="cb12-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> normal_form(f: Poly, basis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly: ...</span>
<span id="cb12-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> in_ideal(f: Poly, basis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span>
<span id="cb12-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> eliminate(generators: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], k: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]: ...</span>
<span id="cb12-14"></span>
<span id="cb12-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Orbit problems</span></span>
<span id="cb12-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> same_orbit(action: Action, v: np.ndarray, u: np.ndarray) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span>
<span id="cb12-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_separator(action: Action, v: np.ndarray, u: np.ndarray, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>: ...</span></code></pre></div>
</div>
<p>Let’s look through these in more detail.</p>
<section id="decision-problems" class="level2">
<h2 class="anchored" data-anchor-id="decision-problems">Decision Problems</h2>
<p>In these types of problems, we are checking an input for some property, and the output is a boolean.</p>
<section id="testing-whether-a-polynomial-lies-in-kvg" class="level3">
<h3 class="anchored" data-anchor-id="testing-whether-a-polynomial-lies-in-kvg">Testing Whether a Polynomial Lies in <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"></h3>
<p>Probably the most fundamental decision problem is to determine whether a given polynomial is actually invariant under the action. This is the most direct membership test for the invariant ring.</p>
<p>This suggests an operation of the form</p>
<div id="c8dfc27e" class="cell" data-execution_count="13">
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_invariant(action: Action, f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span></code></pre></div>
</div>
</section>
<section id="testing-whether-an-object-is-invariant-under-the-action" class="level3">
<h3 class="anchored" data-anchor-id="testing-whether-an-object-is-invariant-under-the-action">Testing Whether an Object Is Invariant Under the Action</h3>
<p>More generally, we may want to test if some explicitly represented object is invariant under the action.</p>
<div id="847e99bc" class="cell" data-execution_count="14">
<div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_invariant(action: Action, x) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same interface, different object types via Space</span></span></code></pre></div>
</div>
</section>
<section id="testing-whether-a-point-lies-in-the-null-cone" class="level3">
<h3 class="anchored" data-anchor-id="testing-whether-a-point-lies-in-the-null-cone">Testing Whether a Point Lies in the Null Cone</h3>
<p>We have yet to introduce the concept of the null cone, but the idea is that, given the quotient map <img src="https://latex.codecogs.com/png.latex?%5Cpi:%20V%20%5Cto%20V/%5C!%5C!/G">, we want to test whether a point <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20V"> maps to the origin in the quotient. This is equivalent to testing whether all positive-degree invariants vanish at <img src="https://latex.codecogs.com/png.latex?v">. This has important geometric implications for the quotient space.</p>
<div id="391a517a" class="cell" data-execution_count="15">
<div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> in_null_cone(action: Action, v: np.ndarray, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span></code></pre></div>
</div>
</section>
</section>
<section id="construction-problems" class="level2">
<h2 class="anchored" data-anchor-id="construction-problems">Construction Problems</h2>
<p>These operations attempt to compute explicit invariants or structured generating data for the invariant ring.</p>
<section id="computing-generators-of-kvg" class="level3">
<h3 class="anchored" data-anchor-id="computing-generators-of-kvg">Computing Generators of <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"></h3>
<p>We might want to compute a generating set for the invariant ring. This gives a finite description of all polynomial invariants and is often the starting point for further structural work.</p>
<p>In API terms, this means we want an operation</p>
<div id="d843bfc3" class="cell" data-execution_count="16">
<div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_generators(action: Action, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]: ...</span></code></pre></div>
</div>
</section>
<section id="computing-primary-and-secondary-invariants" class="level3">
<h3 class="anchored" data-anchor-id="computing-primary-and-secondary-invariants">Computing Primary and Secondary Invariants</h3>
<p>If <img src="https://latex.codecogs.com/png.latex?G"> is a finite group acting on <img src="https://latex.codecogs.com/png.latex?V">, then the invariant ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"> is a finitely generated module over a polynomial subring generated by a homogeneous system of parameters (HSOP). The generators of the polynomial subring are called primary invariants, and the generators of the module are called secondary invariants. Computing these can give us a more structured understanding of the invariant ring.</p>
<div id="232152a9" class="cell" data-execution_count="17">
<div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_primary_secondary(action: Action, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]]: ...</span></code></pre></div>
</div>
</section>
<section id="computing-separating-invariants" class="level3">
<h3 class="anchored" data-anchor-id="computing-separating-invariants">Computing Separating Invariants</h3>
<p>A separating set of invariants is a subset of the invariant ring that can distinguish between different orbits of the group action. That is, if we have two points <img src="https://latex.codecogs.com/png.latex?v,%20u%20%5Cin%20V">, then invariants in the separating set can distinguish them whenever they have different images in the quotient. For finite groups, this is the same as distinguishing different orbits. Think of this as a “weaker” version of a generating set that is only concerned with separating orbits rather than generating the entire ring. Computing a separating set can be easier than computing a full generating set, and it is often sufficient for many applications.</p>
<p>As an API operation:</p>
<div id="85ded29a" class="cell" data-execution_count="18">
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_separating_invariants(action: Action, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]: ...</span></code></pre></div>
</div>
<p>These are apparently even more important when the invariant ring has bad properties, such as being non-finitely generated, which can happen for non-reductive groups. In that case, we may not be able to compute a full generating set, but we can still compute a separating set. Not sure if/when this will show up.</p>
</section>
</section>
<section id="structural-computations" class="level2">
<h2 class="anchored" data-anchor-id="structural-computations">Structural Computations</h2>
<p>These operations try to understand the algebraic structure of the invariant ring once invariants have been found.</p>
<section id="computing-the-hilbert-series" class="level3">
<h3 class="anchored" data-anchor-id="computing-the-hilbert-series">Computing the Hilbert Series</h3>
<p>The Hilbert series is a generating function that counts the invariants by dimension. It is defined as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t)%20=%20%5Csum_%7Bd=0%7D%5E%7B%5Cinfty%7D%20%5Cdim_k(k%5BV%5D%5EG_d)%20t%5Ed%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG_d"> is the space of homogeneous invariants of degree <img src="https://latex.codecogs.com/png.latex?d">. The Hilbert series encodes important information about the invariant ring and the structure of the invariants. For example, in the cases we care about here, the Hilbert series is rational once the invariant ring is finitely generated.</p>
<p>For finite groups, the Hilbert series can be computed using the Molien formula:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20%5Cfrac%7B1%7D%7B%5Cdet(I%20-%20t%20g)%7D%0A"></p>
<p>We will examine this in more detail in subsequent sections.</p>
<p>As an API operation:</p>
<div id="d210097b" class="cell" data-execution_count="19">
<div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_hilbert_series(action: Action, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]: ...</span></code></pre></div>
</div>
</section>
<section id="computing-structural-properties-of-kvg" class="level3">
<h3 class="anchored" data-anchor-id="computing-structural-properties-of-kvg">Computing Structural Properties of <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"></h3>
<p>There are several structural properties of the invariant ring that we may want to compute. These properties tell us how complicated the ring is, how many relations we should expect, and whether the ring admits especially efficient descriptions.</p>
<p>To define the most common ones, we first isolate the role of a polynomial subring inside the invariant ring.</p>
<div id="def-hsop" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 1</strong></span> Let <img src="https://latex.codecogs.com/png.latex?R%20=%20k%5BV%5D%5EG"> be a graded invariant ring. A collection of homogeneous elements <img src="https://latex.codecogs.com/png.latex?%0A%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%20%5Cin%20R%0A"></p>
<p>is called a homogeneous system of parameters (HSOP) if <img src="https://latex.codecogs.com/png.latex?R"> is finitely generated as a module over the polynomial subring <img src="https://latex.codecogs.com/png.latex?%0Ak%5B%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%5D%0A"></p>
<p>Intuitively, an HSOP is a choice of basic algebraically independent parameters over which the whole invariant ring is finite.</p>
</div>
<div id="def-cohen-macaulay" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 2</strong></span> We say that <img src="https://latex.codecogs.com/png.latex?R%20=%20k%5BV%5D%5EG"> is Cohen-Macaulay if, for some HSOP</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%20%5Cin%20R%0A"></p>
<p>the ring <img src="https://latex.codecogs.com/png.latex?R"> is a free module over the polynomial subring <img src="https://latex.codecogs.com/png.latex?k%5B%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%5D">. Equivalently, there exist homogeneous elements</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ceta_1,%20%5Cldots,%20%5Ceta_m%20%5Cin%20R%0A"></p>
<p>such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AR%20%5Ccong%20%5Cbigoplus_%7Bj=1%7D%5Em%20%5Ceta_j%20%5C,%20k%5B%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%5D%0A"></p>
<p>as a module over <img src="https://latex.codecogs.com/png.latex?k%5B%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%5D">.</p>
<p>This is important computationally because it means the invariant ring has a simple description in terms of primary and secondary invariants.</p>
</div>
<div id="def-gorenstein" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 3</strong></span> Assume <img src="https://latex.codecogs.com/png.latex?R%20=%20k%5BV%5D%5EG"> is Cohen-Macaulay, and let <img src="https://latex.codecogs.com/png.latex?%0AA%20=%20R/(%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d)%0A"></p>
<p>be the quotient by an HSOP. Then <img src="https://latex.codecogs.com/png.latex?A"> is a finite-dimensional graded algebra:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA%20=%20%5Cbigoplus_%7Bi%20%5Cge%200%7D%20A_i%0A"></p>
<p>Let <img src="https://latex.codecogs.com/png.latex?s"> be the largest degree for which <img src="https://latex.codecogs.com/png.latex?A_s%20%5Cneq%200">.</p>
<p>We say that <img src="https://latex.codecogs.com/png.latex?R"> is Gorenstein if <img src="https://latex.codecogs.com/png.latex?A_s"> is one-dimensional, and for every <img src="https://latex.codecogs.com/png.latex?i">, multiplication</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA_i%20%5Ctimes%20A_%7Bs-i%7D%20%5Cto%20A_s%0A"></p>
<p>is nondegenerate. By nondegenerate, we mean that 1. For every nonzero <img src="https://latex.codecogs.com/png.latex?a%20%5Cin%20A_i">, there exists some <img src="https://latex.codecogs.com/png.latex?b%20%5Cin%20A_%7Bs-i%7D"> such that <img src="https://latex.codecogs.com/png.latex?ab%20%5Cneq%200">, and 2. For every nonzero <img src="https://latex.codecogs.com/png.latex?b%20%5Cin%20A_%7Bs-i%7D">, there exists some <img src="https://latex.codecogs.com/png.latex?a%20%5Cin%20A_i"> such that <img src="https://latex.codecogs.com/png.latex?ab%20%5Cneq%200">.</p>
<p>Intuitively, this means after we quotient the polynomial part coming from the HSOP out, the remaining finite-dimensional algebra has a strong symmetry between complementary degrees.</p>
</div>
<div id="def-complete-intersection" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 4</strong></span> Suppose we present the invariant ring as <img src="https://latex.codecogs.com/png.latex?%0AR%20%5Ccong%20k%5By_1,%20%5Cldots,%20y_m%5D/I%0A"></p>
<p>where the <img src="https://latex.codecogs.com/png.latex?y_i"> correspond to chosen generators of <img src="https://latex.codecogs.com/png.latex?R">. Let <img src="https://latex.codecogs.com/png.latex?d"> be the number of parameters in an HSOP for <img src="https://latex.codecogs.com/png.latex?R">. We say that <img src="https://latex.codecogs.com/png.latex?R"> is a complete intersection if the ideal of relations <img src="https://latex.codecogs.com/png.latex?I"> can be generated by <img src="https://latex.codecogs.com/png.latex?%0Am%20-%20d%0A"></p>
<p>elements.</p>
<p>That is, once generators have been chosen, the ring is determined by as few relations as possible. This is one of the best possible situations computationally, since the presentation is controlled by a minimal number of equations.</p>
</div>
<p>As API operations, we might have:</p>
<div id="eac05fae" class="cell" data-execution_count="20">
<div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb20-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_cohen_macaulay(invariant_ring) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span>
<span id="cb20-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_gorenstein(invariant_ring) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span>
<span id="cb20-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_complete_intersection(invariant_ring) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span></code></pre></div>
</div>
</section>
</section>
<section id="presentation-computations" class="level2">
<h2 class="anchored" data-anchor-id="presentation-computations">Presentation Computations</h2>
<p>These operations try to find explicit presentations of the invariant ring in terms of generators and relations.</p>
<section id="computing-relations-among-generators" class="level3">
<h3 class="anchored" data-anchor-id="computing-relations-among-generators">Computing Relations Among Generators</h3>
<p>The simplest form of this problem is to compute the ideal of relations among a given set of generators. That is, if we have a set of generators <img src="https://latex.codecogs.com/png.latex?%5C%7Bf_1,%20%5Cldots,%20f_m%5C%7D"> for <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG">, we want to find the ideal <img src="https://latex.codecogs.com/png.latex?I"> such that:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AI%20=%20%5C%7B%20r%20%5Cin%20k%5Bf_1,%20%5Cldots,%20f_m%5D%20:%20r(f_1,%20%5Cldots,%20f_m)%20=%200%20%5C%7D%0A"></p>
<p>This is important because it gives us a complete presentation of the invariant ring as a quotient of a polynomial ring by the ideal of relations. We can use Gröbner bases to compute this ideal, which allows us to perform various algebraic operations on the invariant ring.</p>
<p>In terms of the API:</p>
<div id="8867060a" class="cell" data-execution_count="21">
<div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_relations(generators: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]: ...</span></code></pre></div>
</div>
</section>
<section id="computing-syzygies-among-relations" class="level3">
<h3 class="anchored" data-anchor-id="computing-syzygies-among-relations">Computing Syzygies Among Relations</h3>
<p>Once we have a set of relations <img src="https://latex.codecogs.com/png.latex?r_1,%20%5Cldots,%20r_k"> among the generators, we may have syzygies among the relations themselves. We can represent these as tuples of polynomials <img src="https://latex.codecogs.com/png.latex?(s_1,%20%5Cldots,%20s_k)"> such that:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0As_1%20r_1%20+%20s_2%20r_2%20+%20%5Ccdots%20+%20s_k%20r_k%20=%200%0A"></p>
<p>The trivial syzygies come from commutativity (<img src="https://latex.codecogs.com/png.latex?r_i%20%5Ccdot%20r_j%20-%20r_j%20%5Ccdot%20r_i%20=%200">). The non-trivial syzygies help show the structure in the relation ideal.</p>
<p>In the last post, we saw that the computations are finite (by Hilbert’s syzygy theorem), and the resolution encodes homological invariants like depth and projective dimension. Computationally, we can use Gröbner bases to compute the syzygies among the relations, which gives us a deeper understanding of the structure of the invariant ring and can help us compute further invariants.</p>
<div id="be35966c" class="cell" data-execution_count="22">
<div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb22-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_syzygies(relations: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]: ...  </span></code></pre></div>
</div>
</section>
<section id="solving-ideal-membership-and-normal-form-problems" class="level3">
<h3 class="anchored" data-anchor-id="solving-ideal-membership-and-normal-form-problems">Solving Ideal-Membership and Normal-Form Problems</h3>
<p>If we have a presentation of the invariant ring in terms of generators and relations, we can use this to solve ideal-membership problems. For example, given a polynomial <img src="https://latex.codecogs.com/png.latex?f"> and an ideal <img src="https://latex.codecogs.com/png.latex?I"> generated by some relations, we can test whether <img src="https://latex.codecogs.com/png.latex?f"> belongs to <img src="https://latex.codecogs.com/png.latex?I"> by computing the normal form of <img src="https://latex.codecogs.com/png.latex?f"> with respect to a Gröbner basis for <img src="https://latex.codecogs.com/png.latex?I">. If the normal form is zero, then <img src="https://latex.codecogs.com/png.latex?f%20%5Cin%20I">; else, <img src="https://latex.codecogs.com/png.latex?f%20%5Cnotin%20I">.</p>
<div id="918b1e9d" class="cell" data-execution_count="23">
<div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb23-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> normal_form(f: Poly, basis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly: ...</span>
<span id="cb23-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> in_ideal(f: Poly, basis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span></code></pre></div>
</div>
</section>
</section>
<section id="quotient-and-orbit-computations" class="level2">
<h2 class="anchored" data-anchor-id="quotient-and-orbit-computations">Quotient and Orbit Computations</h2>
<p>These operations try to compute the geometry of the quotient space <img src="https://latex.codecogs.com/png.latex?V/%5C!%5C!/G"> and the orbits of a group action.</p>
<section id="determining-whether-two-points-have-the-same-image-in-the-quotient" class="level3">
<h3 class="anchored" data-anchor-id="determining-whether-two-points-have-the-same-image-in-the-quotient">Determining Whether Two Points Have the Same Image in the Quotient</h3>
<p>Let’s say we have two points <img src="https://latex.codecogs.com/png.latex?v,%20u%20%5Cin%20V">, and we want to determine whether they map to the same point in the quotient <img src="https://latex.codecogs.com/png.latex?V/%5C!%5C!/G">. For finite groups, this is equivalent to asking whether there exists a group element <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G"> such that <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20v%20=%20u">.</p>
<p>We can run a computation:</p>
<div id="ef225fb2" class="cell" data-execution_count="24">
<div class="sourceCode cell-code" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb24-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> same_orbit(action: Action, v: np.ndarray, u: np.ndarray) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>: ...</span></code></pre></div>
</div>
</section>
<section id="separating-points-orbits-or-orbit-closures" class="level3">
<h3 class="anchored" data-anchor-id="separating-points-orbits-or-orbit-closures">Separating Points, Orbits, or Orbit Closures</h3>
<p>Similarly, we may want to determine whether two points lie in different orbits, or whether their orbit closures are different. This is related to the concept of separating invariants, which can distinguish between different orbits.</p>
<div id="4502e826" class="cell" data-execution_count="25">
<div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb25-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_separator(action: Action, v: np.ndarray, u: np.ndarray, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>: ...</span></code></pre></div>
</div>
</section>
</section>
<section id="example-workflow" class="level2">
<h2 class="anchored" data-anchor-id="example-workflow">Example Workflow</h2>
<p>Now that we know the types of computations we want to perform, we can outline the workflow we might use to actually compute invariants for a specific group action. This will help us understand how the different computational tasks fit together in practice.</p>
<p>Probably the sequence looks something like this:</p>
<ol type="1">
<li>Encode the group action and the structured object in our data structure.</li>
<li>Compute Hilbert/Molien data for the action to understand the structure of the invariant ring.</li>
<li>Search for candidate generators for the invariant ring, using the Hilbert series to guide our search.</li>
<li>Compute relations by Gröbner elimination to find a presentation of the invariant ring.</li>
</ol>
<p>Once we have the presentation, we can use it to gain understanding of the object:</p>
<ol start="5" type="1">
<li>Test ideal membership and compute normal forms. Given a polynomial <img src="https://latex.codecogs.com/png.latex?f">, determine whether it lies in the ideal of relations (equivalently: can it be written in terms of the generators?). The normal form gives a canonical representative.</li>
<li>Compute structural properties of the invariant ring, such as whether it is Cohen-Macaulay, Gorenstein, or a complete intersection.</li>
<li>Compute syzygies and higher-order relations among the generators.</li>
<li>Compute separating invariants and solve orbit-separation problems.</li>
<li>Compute primary and secondary invariants, and understand the module structure of the invariant ring over a polynomial subring.</li>
</ol>
<p>More advanced computations (which we may or may not do here) might include:</p>
<ol start="11" type="1">
<li>Compute the “Krull dimension” of the invariant ring. This is the number of algebraically independent generators, equal to <img src="https://latex.codecogs.com/png.latex?%5Cdim(V)%20-%20%5Cdim(%5Ctext%7Bgeneric%20orbit%7D)">. It tells you the dimension of the quotient variety <img src="https://latex.codecogs.com/png.latex?V/%5C!%5C!/G">.</li>
<li>Compute degree bounds. For finite groups in characteristic zero, all generators appear by degree <img src="https://latex.codecogs.com/png.latex?%7CG%7C"> (Noether’s bound). The Hilbert series predicts how many generators to expect in each degree before computing them, giving a stopping criterion.</li>
</ol>
<p>The generators and relations also determine the geometry of the quotient <img src="https://latex.codecogs.com/png.latex?V/%5C!%5C!/G"> (its defining equations, dimension, and singularities), but that is algebraic geometry proper, and we won’t pursue it here.</p>
<p>An example program putting this all together might look like this:</p>
<div id="9fd9bd0e" class="cell" data-execution_count="26">
<div class="sourceCode cell-code" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb26-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb26-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.groups.constructors <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> symmetric</span>
<span id="cb26-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.spaces <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> polynomial_ring</span>
<span id="cb26-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.action <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> (</span>
<span id="cb26-5">    invariant_theory, compute_generators,</span>
<span id="cb26-6">    compute_hilbert_series, in_null_cone, same_orbit,</span>
<span id="cb26-7">)</span>
<span id="cb26-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.groebner <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> compute_relations</span>
<span id="cb26-9"></span>
<span id="cb26-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. Encode the group action</span></span>
<span id="cb26-11">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetric(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb26-12">ring <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> polynomial_ring(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb26-13">action <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> invariant_theory(G, ring)</span>
<span id="cb26-14"></span>
<span id="cb26-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Hilbert series: how many invariants in each degree?</span></span>
<span id="cb26-16">hs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_hilbert_series(action, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>)</span>
<span id="cb26-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1, 1, 2, 3, 4, 5, 7]</span></span>
<span id="cb26-18"></span>
<span id="cb26-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. Find generators</span></span>
<span id="cb26-20">generators <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_generators(action, max_degree<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb26-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [x0+x1+x2, x0^2+x1^2+x2^2, x0^3+x1^3+x2^3]</span></span>
<span id="cb26-22"></span>
<span id="cb26-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. Relations among generators (Gröbner elimination)</span></span>
<span id="cb26-24">relations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_relations(generators, n_vars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb26-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [] — S_3 invariant ring is freely generated</span></span>
<span id="cb26-26"></span>
<span id="cb26-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. Orbit and quotient geometry</span></span>
<span id="cb26-28">v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>])</span>
<span id="cb26-29">u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>])</span>
<span id="cb26-30">same_orbit(action, v, u)                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True — same multiset</span></span>
<span id="cb26-31">in_null_cone(action, np.array([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]))   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True — all invariants vanish at origin</span></span></code></pre></div>
</div>
<p>For this particular example, the outputs look something like this:</p>
<pre><code>Hilbert series: [1, 1, 2, 3, 4, 5, 7]
Generators (3): [x0+x1+x2, x0^2+x1^2+x2^2, x0^3+x1^3+x2^3]
Relations: 0 (freely generated)
Same orbit (1,2,3) ~ (3,1,2): True
In null cone (0,0,0): True</code></pre>
</section>
</section>
<section id="hilbert-series" class="level1">
<h1>Hilbert Series</h1>
<p>In the code above, step 2 was “compute the Hilbert series.” Before we implement anything, we should define this properly, since it will guide every computation that follows.</p>
<p>For any graded algebra <img src="https://latex.codecogs.com/png.latex?R%20=%20%5Cbigoplus_%7Bd=0%7D%5E%7B%5Cinfty%7D%20R_d"> (where each <img src="https://latex.codecogs.com/png.latex?R_d"> is a finite-dimensional vector space), the <strong>Hilbert series</strong> is the generating function:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH_R(t)%20=%20%5Csum_%7Bd=0%7D%5E%7B%5Cinfty%7D%20%5Cdim_k(R_d)%20%5C,%20t%5Ed%0A"></p>
<p>Applied to the invariant ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG">, the coefficient of <img src="https://latex.codecogs.com/png.latex?t%5Ed"> counts the number of linearly independent invariant polynomials of degree <img src="https://latex.codecogs.com/png.latex?d">. This is the single most useful piece of information you can have before searching for generators: it tells you how many to expect in each degree, and when you can stop looking.</p>
<p>Each group type gives a different formula for the Hilbert series. For <strong>finite groups</strong>, Molien’s theorem expresses it as a sum over group elements. For <strong>tori</strong>, it reduces to counting lattice points in a cone. For <strong>compact Lie groups</strong>, it becomes an integral over the maximal torus (the Molien-Weyl formula). We will derive and implement each of these.</p>
<p>The Hilbert series also encodes structural information. If <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"> is Cohen-Macaulay (which it always is for finite groups in characteristic zero, by the Hochster-Roberts theorem), then the series factors as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t)%20=%20%5Cfrac%7Bh_0%20+%20h_1%20t%20+%20%5Ccdots%20+%20h_s%20t%5Es%7D%7B(1-t%5E%7Bd_1%7D)(1-t%5E%7Bd_2%7D)%5Ccdots(1-t%5E%7Bd_n%7D)%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?d_1,%20%5Cldots,%20d_n"> are the degrees of the primary invariants and the numerator counts secondary invariants. We will return to this decomposition in the section on primary and secondary invariants.</p>
</section>
<section id="tori" class="level1">
<h1>Tori</h1>
<p>Now that we’ve laid out the data structures and the Hilbert series as our main bookkeeping tool, we can start implementing. We begin with the simplest case: torus actions on a vector space.</p>
<section id="introduction-to-tori" class="level2">
<h2 class="anchored" data-anchor-id="introduction-to-tori">Introduction to Tori</h2>
<p>Let’s define the torus and its action on a vector space.</p>
<div class="{def-torus}">
<p>Given a group <img src="https://latex.codecogs.com/png.latex?G">, we say that <img src="https://latex.codecogs.com/png.latex?G"> is a torus if it is isomorphic to <img src="https://latex.codecogs.com/png.latex?(%5Cmathbb%7BC%7D%5E*)%5En"> (for some integer <img src="https://latex.codecogs.com/png.latex?n%20%3E%200">).</p>
</div>
<p>An element of <img src="https://latex.codecogs.com/png.latex?T"> is therefore a tuple <img src="https://latex.codecogs.com/png.latex?%0At%20=%20(t_1,%20%5Cldots,%20t_r)%0A"> <img src="https://latex.codecogs.com/png.latex?%0At_i%20%5Cin%20%5Cmathbb%7BC%7D%5E*%0A"></p>
<p>and multiplication is componentwise.</p>
<div id="prop-torus-diagonalization">
<p>Let <img src="https://latex.codecogs.com/png.latex?T"> act linearly on a finite-dimensional complex vector space <img src="https://latex.codecogs.com/png.latex?V">. Then we can choose a basis <img src="https://latex.codecogs.com/png.latex?%0Ae_1,%20%5Cldots,%20e_m%0A"></p>
<p>of <img src="https://latex.codecogs.com/png.latex?V"> such that each basis vector is just rescaled by the action. That is, for each basis vector <img src="https://latex.codecogs.com/png.latex?e_i"> and each <img src="https://latex.codecogs.com/png.latex?t%20%5Cin%20T">, there is some scalar <img src="https://latex.codecogs.com/png.latex?%5Clambda_i(t)%20%5Cin%20%5Cmathbb%7BC%7D%5E%5Ctimes"> such that <img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20e_i%20=%20%5Clambda_i(t)%20e_i%0A"></p>
</div>
<p><em>Proof</em>: See this StackExchange post <a href="https://math.stackexchange.com/questions/828359/proof-of-basic-fact-that-torus-actions-are-diagonalizable">here</a>, which shows that the action is diagonalizable. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>The group law forces these scalar functions to behave multiplicatively. So <img src="https://latex.codecogs.com/png.latex?%0A(ts)%5Ccdot%20e_i%20=%20t%20%5Ccdot%20(s%20%5Ccdot%20e_i)%0A"></p>
<p>implies</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clambda_i(ts)e_i%20=%20t%20%5Ccdot%20(%5Clambda_i(s)e_i)%20=%20%5Clambda_i(s)%5Clambda_i(t)e_i%0A"></p>
<p>so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clambda_i(ts)%20=%20%5Clambda_i(t)%5Clambda_i(s)%0A"></p>
<p>Thus each <img src="https://latex.codecogs.com/png.latex?%5Clambda_i%20:%20T%20%5Cto%20%5Cmathbb%7BC%7D%5E*"> is a group homomorphism.</p>
<div id="def-character" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 5</strong></span> A group homomorphism <img src="https://latex.codecogs.com/png.latex?%0A%5Clambda%20:%20T%20%5Cto%20%5Cmathbb%7BC%7D%5E*%0A"></p>
<p>is called a character of the torus.</p>
</div>
</section>
<section id="integer-linear-algebra-of-invariants" class="level2">
<h2 class="anchored" data-anchor-id="integer-linear-algebra-of-invariants">Integer Linear Algebra of Invariants</h2>
<p>So each basis vector <img src="https://latex.codecogs.com/png.latex?e_i"> comes with a character <img src="https://latex.codecogs.com/png.latex?%5Clambda_i">, and under the right basis, the action is of the form <img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20e_i%20=%20%5Clambda_i(t)e_i%0A"></p>
<p>Because <img src="https://latex.codecogs.com/png.latex?%0AT%20=%20(%5Cmathbb%7BC%7D%5E*)%5Er%0A"></p>
<p>every character is given by a monomial in the torus coordinates:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Clambda_i(t_1,%5Cldots,t_r)%20=%20t_1%5E%7Bw_%7Bi1%7D%7D%5Ccdots%20t_r%5E%7Bw_%7Bir%7D%7D%0A"></p>
<p>for some integers</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(w_%7Bi1%7D,%5Cldots,w_%7Bir%7D)%20%5Cin%20%5Cmathbb%7BZ%7D%5Er%0A"></p>
<p>These integers are called the weights of the action. So after choosing this basis, the torus action can be written as <img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20e_i%20=%20t_1%5E%7Bw_%7Bi1%7D%7D%5Ccdots%20t_r%5E%7Bw_%7Bir%7D%7D%20e_i%0A"></p>
<p>If we write a vector <img src="https://latex.codecogs.com/png.latex?%0Av%20=%20%5Csum_%7Bi=1%7D%5Em%20v_i%20e_i%0A"></p>
<p>then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20v%0A=%0A%5Csum_%7Bi=1%7D%5Em%20t_1%5E%7Bw_%7Bi1%7D%7D%5Ccdots%20t_r%5E%7Bw_%7Bir%7D%7D%20v_i%20e_i%0A"></p>
<p>So a torus action is encoded by a list of integer weight vectors</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aw_i%20=%20(w_%7Bi1%7D,%5Cldots,w_%7Bir%7D)%20%5Cin%20%5Cmathbb%7BZ%7D%5Er%0A"></p>
<p>or equivalently by an integer matrix of weights.</p>
<p>Now let</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_1,%20%5Cldots,%20x_m%0A"></p>
<p>denote the coordinates corresponding to the basis</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ae_1,%20%5Cldots,%20e_m%0A"></p>
<p>Since each basis vector is scaled by a weight, the coordinates transform by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20x_i%20=%20t%5E%7Bw_i%7D%20x_i%0A=%0At_1%5E%7Bw_%7Bi1%7D%7D%20%5Ccdots%20t_r%5E%7Bw_%7Bir%7D%7D%20x_i%0A"></p>
<p>where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aw_i%20=%20(w_%7Bi1%7D,%20%5Cldots,%20w_%7Bir%7D)%20%5Cin%20%5Cmathbb%7BZ%7D%5Er%0A"></p>
<p>Now take a monomial <img src="https://latex.codecogs.com/png.latex?%0Ax%5E%5Calpha%20=%20x_1%5E%7B%5Calpha_1%7D%5Ccdots%20x_m%5E%7B%5Calpha_m%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Calpha%20=%20(%5Calpha_1,%20%5Cldots,%20%5Calpha_m)%20%5Cin%20%5Cmathbb%7BN%7D%5Em%0A"></p>
<p>Then the torus acts on it by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20x%5E%5Calpha%0A=%0A(t%20%5Ccdot%20x_1)%5E%7B%5Calpha_1%7D%5Ccdots%20(t%20%5Ccdot%20x_m)%5E%7B%5Calpha_m%7D%0A"></p>
<p>Substituting in the weight formula gives</p>
<p><img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20x%5E%5Calpha%0A=%0A(t%5E%7Bw_1%7Dx_1)%5E%7B%5Calpha_1%7D%5Ccdots%20(t%5E%7Bw_m%7Dx_m)%5E%7B%5Calpha_m%7D%0A=%0At%5E%7B%5Calpha_1%20w_1%20+%20%5Ccdots%20+%20%5Calpha_m%20w_m%7D%20x%5E%5Calpha%0A"></p>
<p>So the monomial <img src="https://latex.codecogs.com/png.latex?x%5E%5Calpha"> is again scaled by a single weight (the “total weight” of the monomial): <img src="https://latex.codecogs.com/png.latex?%0A%5Calpha_1%20w_1%20+%20%5Ccdots%20+%20%5Calpha_m%20w_m%20%5Cin%20%5Cmathbb%7BZ%7D%5Er%0A"></p>
<p>If we assemble the weight vectors into a matrix <img src="https://latex.codecogs.com/png.latex?%0AW%20=%20%5Bw_1%20%5C%20%5Ccdots%20%5C%20w_m%5D%20%5Cin%20%5Coperatorname%7BMat%7D_%7Br%20%5Ctimes%20m%7D(%5Cmathbb%7BZ%7D),%0A"></p>
<p>then the total weight can be written as <img src="https://latex.codecogs.com/png.latex?%0AW%5Calpha%0A"></p>
<p>Thus</p>
<p><img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20x%5E%5Calpha%20=%20t%5E%7BW%5Calpha%7D%20x%5E%5Calpha%0A"></p>
<p>It follows that the monomial <img src="https://latex.codecogs.com/png.latex?x%5E%5Calpha"> is invariant if and only if its total weight is zero: <img src="https://latex.codecogs.com/png.latex?%0AW%5Calpha%20=%200%0A"></p>
<p>So to find invariant monomials, just have to solve the integer linear system</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AW%5Calpha%20=%200%0A"></p>
<p>subject to the constraint that the exponents are nonnegative integers:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Calpha%20%5Cin%20%5Cmathbb%7BN%7D%5Em%0A"></p>
<p>So the exponent vectors of invariant monomials form the set</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cker(W)%20%5Ccap%20%5Cmathbb%7BN%7D%5Em%0A"></p>
<p>This is a semigroup under addition, and the invariant ring is generated by the corresponding monomials.</p>
</section>
<section id="implementation" class="level2">
<h2 class="anchored" data-anchor-id="implementation">Implementation</h2>
<p>What do our data structures and computational tasks look like in the case of a torus action? Let’s take an (abbreviated) look.</p>
<section id="invariance-test" class="level4">
<h4 class="anchored" data-anchor-id="invariance-test">Invariance Test</h4>
<p>As we saw, for a torus action, we can represent the action by an integer weight matrix <img src="https://latex.codecogs.com/png.latex?W">. The invariant monomials correspond to integer solutions of the linear system <img src="https://latex.codecogs.com/png.latex?W%5Calpha%20=%200"> with <img src="https://latex.codecogs.com/png.latex?%5Calpha%20%5Cin%20%5Cmathbb%7BN%7D%5Em">. A polynomial is invariant if and only if every monomial in its support passes this test.</p>
<div id="fabfceb1" class="cell" data-execution_count="27">
<div class="sourceCode cell-code" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb28-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Torus:</span>
<span id="cb28-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Torus T = (C*)^r acting on C^m via weight matrix W."""</span></span>
<span id="cb28-3"></span>
<span id="cb28-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, W: np.ndarray):</span>
<span id="cb28-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.asarray(W, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>)</span>
<span id="cb28-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb28-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb28-8"></span>
<span id="cb28-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> monomial_weight(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, alpha: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> np.ndarray:</span>
<span id="cb28-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Total weight W @ alpha of monomial x^alpha."""</span></span>
<span id="cb28-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> np.array(alpha, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>)</span>
<span id="cb28-12"></span>
<span id="cb28-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_invariant_monomial(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, alpha: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb28-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">all</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.monomial_weight(alpha) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb28-15"></span>
<span id="cb28-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_invariant(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb28-17">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""A polynomial is torus-invariant iff every monomial has weight zero."""</span></span>
<span id="cb28-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">all</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.is_invariant_monomial(alpha) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> f)</span></code></pre></div>
</div>
<p>The penultimate method checks whether a monomial is invariant by checking if its weight is zero. The last method checks whether a polynomial is invariant by checking if every monomial in its support is invariant.</p>
</section>
<section id="enumerating-invariants" class="level4">
<h4 class="anchored" data-anchor-id="enumerating-invariants">Enumerating Invariants</h4>
<p>So given the invariance test, we can enumerate all invariant monomials of a given degree by filtering over all monomials. The Hilbert series (we will review in a subsequent section) counts how many there are in each degree:</p>
<div id="aee58c67" class="cell" data-execution_count="28">
<div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb29-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> invariants_of_degree(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, d: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]:</span>
<span id="cb29-2">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Basis of invariant monomials of degree d."""</span></span>
<span id="cb29-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb29-4">            poly.mono(alpha)</span>
<span id="cb29-5">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> poly.monomials_of_degree(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_vars, d)</span>
<span id="cb29-6">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.is_invariant_monomial(alpha)</span>
<span id="cb29-7">        ]</span>
<span id="cb29-8"></span>
<span id="cb29-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hilbert_coeffs(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, max_d: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]:</span>
<span id="cb29-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Number of invariant monomials in each degree."""</span></span>
<span id="cb29-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.invariants_of_degree(d)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)]</span></code></pre></div>
</div>
<p>The minimal generators of the semigroup of invariant monomials are the invariant monomials that are not products of simpler ones. These are the Hilbert basis of the semigroup:</p>
<div id="7fae9371" class="cell" data-execution_count="29">
<div class="sourceCode cell-code" id="cb30" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb30-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hilbert_basis(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, max_degree: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]]:</span>
<span id="cb30-2">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Minimal generators of the semigroup ker(W) ∩ N^m.</span></span>
<span id="cb30-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb30-4">        all_inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb30-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, max_degree <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb30-6">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> poly.monomials_of_degree(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_vars, d):</span>
<span id="cb30-7">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.is_invariant_monomial(alpha):</span>
<span id="cb30-8">                    all_inv.append(alpha)</span>
<span id="cb30-9"></span>
<span id="cb30-10">        inv_set <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(all_inv)</span>
<span id="cb30-11">        generators <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb30-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> all_inv:</span>
<span id="cb30-13">            a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(alpha)</span>
<span id="cb30-14">            is_reducible <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(</span>
<span id="cb30-15">                np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">all</span>(a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> np.array(beta) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb30-16">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> np.array(beta) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb30-17">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> np.array(beta)) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> inv_set</span>
<span id="cb30-18">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> beta <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> generators</span>
<span id="cb30-19">            )</span>
<span id="cb30-20">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> is_reducible:</span>
<span id="cb30-21">                generators.append(alpha)</span>
<span id="cb30-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> generators</span></code></pre></div>
</div>
</section>
<section id="example" class="level4">
<h4 class="anchored" data-anchor-id="example">Example</h4>
<p>Consider <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E*"> acting on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3"> with weights <img src="https://latex.codecogs.com/png.latex?%5B1,%201,%20-2%5D">.</p>
<p>So we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0At%20%5Ccdot%20(x_0,%20x_1,%20x_2)%20=%20(t%20x_0,%20t%20x_1,%20t%5E%7B-2%7D%20x_2)%0A"></p>
<p>We are looking for a polynomial <img src="https://latex.codecogs.com/png.latex?f(x_0,%20x_1,%20x_2)"> such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af(t%20x_0,%20t%20x_1,%20t%5E%7B-2%7D%20x_2)%20=%20f(x_0,%20x_1,%20x_2)%0A"></p>
<p>Given a monomial <img src="https://latex.codecogs.com/png.latex?x_0%5Ea%20x_1%5Eb%20x_2%5Ec">, we thus have:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_0%5Ea%20x_1%5Eb%20x_2%5Ec%20=%20t%5E%7Ba%20+%20b%20-%202c%7D%20x_0%5Ea%20x_1%5Eb%20x_2%5Ec%0A"></p>
<p>Which is invariant when <img src="https://latex.codecogs.com/png.latex?a%20+%20b%20-%202c%20=%200">, or <img src="https://latex.codecogs.com/png.latex?a%20+%20b%20=%202c">.</p>
<p>Therefore, if we set <img src="https://latex.codecogs.com/png.latex?a%20+%20b%20+%20c%20=%20d">, then we have <img src="https://latex.codecogs.com/png.latex?2c%20+%20c%20=%20d">, or <img src="https://latex.codecogs.com/png.latex?d%20=%203c">. Thus, since degrees are integers, we conclude that the invariant monomials exist only in degrees divisible by 3.</p>
<section id="degree-0" class="level5">
<h5 class="anchored" data-anchor-id="degree-0">Degree 0</h5>
<p><img src="https://latex.codecogs.com/png.latex?c%20=%200">, <img src="https://latex.codecogs.com/png.latex?a%20+%20b%20=%200"></p>
<p>One solution: <img src="https://latex.codecogs.com/png.latex?(0,0,0)">. So the only invariant monomial is the constant <img src="https://latex.codecogs.com/png.latex?1">.</p>
</section>
<section id="degrees-1-2" class="level5">
<h5 class="anchored" data-anchor-id="degrees-1-2">Degrees 1, 2</h5>
<p><img src="https://latex.codecogs.com/png.latex?c%20=%20d/3"> is not an integer. No invariants.</p>
</section>
<section id="degree-3" class="level5">
<h5 class="anchored" data-anchor-id="degree-3">Degree 3</h5>
<p><img src="https://latex.codecogs.com/png.latex?c%20=%201">, <img src="https://latex.codecogs.com/png.latex?a%20+%20b%20=%202">.</p>
<p>There are three solutions: <img src="https://latex.codecogs.com/png.latex?(2,0,1),%20(1,1,1),%20(0,2,1)">. Our invariant polynomials are <img src="https://latex.codecogs.com/png.latex?x_0%5E2%20x_2"> and <img src="https://latex.codecogs.com/png.latex?x_0%20x_1%20x_2,%20x_1%5E2%20x_2">.</p>
</section>
<section id="degree-6" class="level5">
<h5 class="anchored" data-anchor-id="degree-6">Degree 6</h5>
<p><img src="https://latex.codecogs.com/png.latex?c%20=%202">, <img src="https://latex.codecogs.com/png.latex?a%20+%20b%20=%204">. Now there are five solutions: <img src="https://latex.codecogs.com/png.latex?(4,0,2),%20(3,1,2),%20(2,2,2),%20(1,3,2),%20(0,4,2)">.</p>
</section>
<section id="general" class="level5">
<h5 class="anchored" data-anchor-id="general">General</h5>
<p>The degree <img src="https://latex.codecogs.com/png.latex?3k"> has <img src="https://latex.codecogs.com/png.latex?2k+1"> invariant monomials (choose how to split <img src="https://latex.codecogs.com/png.latex?a%20+%20b%20=%202k"> among two variables). So the Hilbert series is <img src="https://latex.codecogs.com/png.latex?1,%200,%200,%203,%200,%200,%205,%200,%200,%207,%20%5Cldots"></p>
<p>Let’s verify:</p>
<div id="9dba9933" class="cell" data-execution_count="30">
<div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb31-1">T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Torus(np.array([[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]]))</span>
<span id="cb31-2"></span>
<span id="cb31-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Hilbert series: count invariant monomials by degree</span></span>
<span id="cb31-4">T.hilbert_coeffs(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>)    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1, 0, 0, 3, 0, 0, 5]</span></span>
<span id="cb31-5"></span>
<span id="cb31-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Hilbert basis: minimal generators of the invariant semigroup</span></span>
<span id="cb31-7">T.hilbert_basis()      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [(2, 0, 1), (1, 1, 1), (0, 2, 1)]</span></span>
<span id="cb31-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># These correspond to x0^2*x2, x0*x1*x2, x1^2*x2</span></span></code></pre></div>
</div>
<p>Therefore, the three generators correspond to the monomials <img src="https://latex.codecogs.com/png.latex?x_0%5E2%20x_2">, <img src="https://latex.codecogs.com/png.latex?x_0%20x_1%20x_2">, and <img src="https://latex.codecogs.com/png.latex?x_1%5E2%20x_2">. Every invariant monomial is a product of these three.</p>
<p>For this example, it’s easy to see why these are the generators. The degree 6 invariants are all products of the degree 3 invariants, and the degree 3 invariants are not products of simpler invariants. So the degree 3 invariants are the minimal generators. We got the Hilbert basis just by brute force enumeration in this case, but in general we might need a more sophisticated algorithm to find the minimal generators of the semigroup.</p>
</section>
</section>
</section>
<section id="relations" class="level2">
<h2 class="anchored" data-anchor-id="relations">Relations</h2>
<p>Now that we have the three generators, we can ask about the relations among them.</p>
<p>In the example above, there are three generators <img src="https://latex.codecogs.com/png.latex?g_0%20=%20x_0%5E2%20x_2">, <img src="https://latex.codecogs.com/png.latex?g_1%20=%20x_0%20x_1%20x_2">, <img src="https://latex.codecogs.com/png.latex?g_2%20=%20x_1%5E2%20x_2">, which satisfy the relation <img src="https://latex.codecogs.com/png.latex?g_0%20g_2%20=%20g_1%5E2">.</p>
<p>It makes sense that there is one relation, as the space <img src="https://latex.codecogs.com/png.latex?V"> has dimension 3 and the torus has dimension 1, so the quotient <img src="https://latex.codecogs.com/png.latex?V/%5C!%5C!/T"> has dimension 3 - 1 = 2. With 3 generators being mapped to a 2-dimensional space, we expect 3 - 2 = 1 relation among them.</p>
<p>How can we find these relations? We need Gröbner elimination.</p>
<p>As a general introduction, introduce new variables <img src="https://latex.codecogs.com/png.latex?y_0,%20y_1,%20y_2"> (one per generator), form the ideal <img src="https://latex.codecogs.com/png.latex?(y_0%20-%20g_0,%20y_1%20-%20g_1,%20y_2%20-%20g_2)"> in the extended ring <img src="https://latex.codecogs.com/png.latex?k%5Bx_0,%20x_1,%20x_2,%20y_0,%20y_1,%20y_2%5D">, and eliminate <img src="https://latex.codecogs.com/png.latex?x_0,%20x_1,%20x_2">. The surviving polynomials in <img src="https://latex.codecogs.com/png.latex?k%5By_0,%20y_1,%20y_2%5D"> are the relations among the generators. In this case, we find the relation <img src="https://latex.codecogs.com/png.latex?y_0%20y_2%20-%20y_1%5E2%20=%200">.</p>
<div id="0b591358" class="cell" data-execution_count="31">
<div class="sourceCode cell-code" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb32-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.groebner <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> compute_relations</span>
<span id="cb32-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.poly <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> format_poly</span>
<span id="cb32-3"></span>
<span id="cb32-4">generators <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [poly.mono(a) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> T.hilbert_basis()]</span>
<span id="cb32-5">relations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_relations(generators, n_vars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb32-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># One relation: y0*y2 - y1^2</span></span></code></pre></div>
</div>
<p>Let’s understand Grobner elimination more deeply in the next section.</p>
</section>
</section>
<section id="gröbner-bases-and-presentations" class="level1">
<h1>Gröbner Bases and Presentations</h1>
<p>Let’s say we have a polynomial ring over a field <img src="https://latex.codecogs.com/png.latex?k">, and we have an ideal <img src="https://latex.codecogs.com/png.latex?I"> generated by some polynomials <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_r">. A Gröbner basis for <img src="https://latex.codecogs.com/png.latex?I"> is a particular kind of generating set that allows us to perform algorithmic operations on the ideal.</p>
<div id="def-groebner-basis" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 6</strong></span> A Gröbner basis for an ideal <img src="https://latex.codecogs.com/png.latex?I"> in a polynomial ring <img src="https://latex.codecogs.com/png.latex?k%5Bx_1,%20%5Cldots,%20x_n%5D"> is a generating set <img src="https://latex.codecogs.com/png.latex?%5C%7Bg_1,%20%5Cldots,%20g_m%5C%7D"> of <img src="https://latex.codecogs.com/png.latex?I"> such that the leading term <img src="https://latex.codecogs.com/png.latex?LT(f)"> for all <img src="https://latex.codecogs.com/png.latex?f%20%5Cin%20I"> is divisible by the leading term of some <img src="https://latex.codecogs.com/png.latex?g_i">. That is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(LT(I))%20=%20(LT(g_1),%20%5Cldots,%20LT(g_m))%0A"></p>
</div>
<div id="lemma-groebner-basis-rewriting-polynomials">
<p>Given a Gröbner basis <img src="https://latex.codecogs.com/png.latex?%5C%7Bg_1,%20%5Cldots,%20g_m%5C%7D"> for an ideal <img src="https://latex.codecogs.com/png.latex?I">, any polynomial <img src="https://latex.codecogs.com/png.latex?f"> can be uniquely expressed as: <img src="https://latex.codecogs.com/png.latex?%0Af%20=%20%5Csum_%7Bi=1%7D%5E%7Bm%7D%20q_i%20g_i%20+%20r%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?q_i"> are polynomials and <img src="https://latex.codecogs.com/png.latex?r"> is a polynomial that cannot be reduced further by the <img src="https://latex.codecogs.com/png.latex?g_i"> (i.e., no term of <img src="https://latex.codecogs.com/png.latex?r"> is divisible by any leading term of the <img src="https://latex.codecogs.com/png.latex?g_i">).</p>
</div>
<p><em>Proof Sketch</em>: This follows from the division algorithm for polynomials. We repeatedly divide <img src="https://latex.codecogs.com/png.latex?f"> by the <img src="https://latex.codecogs.com/png.latex?g_i"> until we can no longer reduce it, which gives us the desired expression.</p>
<section id="buchbergers-algorithm" class="level2">
<h2 class="anchored" data-anchor-id="buchbergers-algorithm">Buchberger’s Algorithm</h2>
<p>Define the S-polynomial of two polynomials <img src="https://latex.codecogs.com/png.latex?f"> and <img src="https://latex.codecogs.com/png.latex?g"> as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS(f,%20g)%20=%20%5Cfrac%7B%5Cmathrm%7Blcm%7D(LT(f),%20LT(g))%7D%7BLT(f)%7D%20%5Ccdot%20f%20-%20%5Cfrac%7B%5Cmathrm%7Blcm%7D(LT(f),%20LT(g))%7D%7BLT(g)%7D%20%5Ccdot%20g%0A"></p>
<p>The algorithm proceeds as follows:</p>
<div id="alg-buchberger">
<ol type="1">
<li>Start with a set of polynomials <img src="https://latex.codecogs.com/png.latex?F%20=%20%5C%7Bf_1,%20%5Cldots,%20f_r%5C%7D">.</li>
<li>For each pair of polynomials <img src="https://latex.codecogs.com/png.latex?f_i,%20f_j%20%5Cin%20F">, compute their S-polynomial <img src="https://latex.codecogs.com/png.latex?S(f_i,%20f_j)">.</li>
<li>Reduce the S-polynomial modulo the current set of polynomials.</li>
<li>If the reduction is non-zero, add it to the set <img src="https://latex.codecogs.com/png.latex?F">.</li>
<li>Repeat steps 2-4 until no new polynomials are added.</li>
</ol>
</div>
<p>We can think of this as similar to Gaussian elimination. In Gaussian elimination, we perform row operations on linear equations: <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign*%7D%0Aa_1x%20+%20b_1y%20+%20c_1z%20=%20d_1%20%5C%5C%0Aa_2x%20+%20b_2y%20+%20c_2z%20=%20d_2%20%5C%5C%0Aa_3x%20+%20b_3y%20+%20c_3z%20=%20d_3%0A%5Cend%7Balign*%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign*%7D%0Aa_1x%20+%20b_1y%20+%20c_1z%20=%20d_1%20%5C%5C%0A(a_2%20-%20a_1*a_2/a_1)x%20+%20(b_2%20-%20b_1*a_2/a_1)y%20+%20(c_2%20-%20c_1*a_2/a_1)z%20=%20d_2%20-%20d_1*a_2/a_1%20%5C%5C%0A(a_3%20-%20a_1*a_3/a_1)x%20+%20(b_3%20-%20b_1*a_3/a_1)y%20+%20(c_3%20-%20c_1*a_3/a_1)z%20=%20d_3%20-%20d_1*a_3/a_1%0A%5Cend%7Balign*%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign*%7D%0Aa_1x%20+%20b_1y%20+%20c_1z%20=%20d_1%20%5C%5C%0A0x%20+%20(b_2%20-%20b_1*a_2/a_1)y%20+%20(c_2%20-%20c_1*a_2/a_1)z%20=%20d_2%20-%20d_1*a_2/a_1%20%5C%5C%0A0x%20+%20(b_3%20-%20b_1*a_3/a_1)y%20+%20(c_3%20-%20c_1*a_3/a_1)z%20=%20d_3%20-%20d_1*a_3/a_1%0A%5Cend%7Balign*%7D%0A"></p>
<p>and so on, eliminating each variable in turn. In Buchberger’s algorithm, we perform similar operations on polynomials to eliminate leading terms and find a Gröbner basis.</p>
<p>As an example, suppose we have the ideal <img src="https://latex.codecogs.com/png.latex?I"> generated by the polynomials <img src="https://latex.codecogs.com/png.latex?f_1%20=%20x%5E2%20+%20y%20-%201"> and <img src="https://latex.codecogs.com/png.latex?f_2%20=%20xy%20+%201">. Impose an ordering on the monomials (<img src="https://latex.codecogs.com/png.latex?x%20%3E%20y">). <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign*%7D%0Af_1%20&amp;=%20x%5E2%20+%20y%20-%201%20%5C%5C%0Af_2%20&amp;=%20xy%20+%201%0A%5Cend%7Balign*%7D%0A"></p>
<p>The leading terms are <img src="https://latex.codecogs.com/png.latex?LT(f_1)%20=%20x%5E2"> and <img src="https://latex.codecogs.com/png.latex?LT(f_2)%20=%20xy">. The least common multiple of the leading terms is <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7Blcm%7D(x%5E2,%20xy)%20=%20x%5E2y">. Since <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7Blcm%7D/LT(f_1)%20=%20y"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7Blcm%7D/LT(f_2)%20=%20x">, multiply the first polynomial by <img src="https://latex.codecogs.com/png.latex?y"> and the second by <img src="https://latex.codecogs.com/png.latex?x"> to get:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign*%7D%0Af_1%20&amp;=%20x%5E2y%20+%20y%5E2%20-%20y%20%5C%5C%0Af_2%20&amp;=%20x%5E2y%20+%20x%0A%5Cend%7Balign*%7D%0A"></p>
<p>Now subtract the second from the first (this is where the S-polynomial comes from): <img src="https://latex.codecogs.com/png.latex?%0AS(f_1,%20f_2)%20=%20(x%5E2y%20+%20y%5E2%20-%20y)%20-%20(x%5E2y%20+%20x)%20=%20y%5E2%20-%20y%20-%20x%0A"></p>
<p>If we fix the maximum degree of the polynomials we want to consider in addition to the ordering, we can even use a matrix representation of the polynomials and perform Gaussian elimination on the coefficients to find the Gröbner basis. Essentially, we’ve replaced the notion of “leading term” with the notion of “leading monomial” in the context of polynomials (by constructing some symbols that represent each monomial), and we perform operations to eliminate these leading monomials until we have a basis that allows us to rewrite any polynomial in the ideal in a unique way.</p>
<p>The primary gotcha is that in Buchberger’s algorithm, we can end up with polynomials of a higher degree than the original generators, which can lead to combinatorial explosion. This is one of the main computational challenges in using Gröbner bases for invariant theory.</p>
<p>Here’s what this looks like in our Python code:</p>
<div id="1866a334" class="cell" data-execution_count="32">
<div class="sourceCode cell-code" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb33-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> s_poly(f: Poly, g: Poly, order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb33-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""S-polynomial of f and g"""</span></span>
<span id="cb33-3">    lm_f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_monomial(f, order)</span>
<span id="cb33-4">    lm_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_monomial(g, order)</span>
<span id="cb33-5">    lc_f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_coefficient(f, order)</span>
<span id="cb33-6">    lc_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_coefficient(g, order)</span>
<span id="cb33-7">    gamma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono_lcm(lm_f, lm_g)</span>
<span id="cb33-8">    t_f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono(poly.mono_div(gamma, lm_f), Fraction(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lc_f)</span>
<span id="cb33-9">    t_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono(poly.mono_div(gamma, lm_g), Fraction(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lc_g)</span>
<span id="cb33-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> poly.sub(poly.mul(t_f, f), poly.mul(t_g, g))</span>
<span id="cb33-11"></span>
<span id="cb33-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> buchberger(F: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]:</span>
<span id="cb33-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Compute a reduced Gröbner basis for the ideal generated by F."""</span></span>
<span id="cb33-14">    G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> F <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> g]</span>
<span id="cb33-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> G:</span>
<span id="cb33-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> []</span>
<span id="cb33-17">    pairs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [(i, j) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(G)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(G))]</span>
<span id="cb33-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> pairs:</span>
<span id="cb33-19">        i, j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pairs.pop(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb33-20">        sp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> s_poly(G[i], G[j], order)</span>
<span id="cb33-21">        r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reduce</span>(sp, G, order)</span>
<span id="cb33-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> r:</span>
<span id="cb33-23">            k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(G)</span>
<span id="cb33-24">            pairs.extend((m, k) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(k))</span>
<span id="cb33-25">            G.append(r)</span>
<span id="cb33-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> _reduce_basis(G, order)</span></code></pre></div>
</div>
</section>
<section id="key-operations" class="level2">
<h2 class="anchored" data-anchor-id="key-operations">Key Operations</h2>
<p>Using Grobner bases, we can perform several key reusable functions that are essential for computational invariant theory.</p>
<section id="computation-of-normal-forms" class="level3">
<h3 class="anchored" data-anchor-id="computation-of-normal-forms">Computation of Normal Forms</h3>
<div id="def-normal-form" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 7</strong></span> Let <img src="https://latex.codecogs.com/png.latex?S%20%5Csubset%20k%5Bx_1,%20%5Cldots,%20x_n%5D"> be a set of polynomials and let <img src="https://latex.codecogs.com/png.latex?I%20=%20%3CS%3E"> be the ideal generated by <img src="https://latex.codecogs.com/png.latex?S">. The normal form of a polynomial <img src="https://latex.codecogs.com/png.latex?f"> with respect to <img src="https://latex.codecogs.com/png.latex?I"> is the unique polynomial <img src="https://latex.codecogs.com/png.latex?r"> such that no term of <img src="https://latex.codecogs.com/png.latex?r"> is divisible by the leading term of any polynomial in a Gröbner basis for <img src="https://latex.codecogs.com/png.latex?I">, and such that <img src="https://latex.codecogs.com/png.latex?f%20-%20r%20%5Cin%20I">.</p>
<p>In other words, the normal form of <img src="https://latex.codecogs.com/png.latex?f"> is the “remainder” when <img src="https://latex.codecogs.com/png.latex?f"> is reduced by the Gröbner basis of <img src="https://latex.codecogs.com/png.latex?I">. It is a canonical representative of the equivalence class of <img src="https://latex.codecogs.com/png.latex?f"> in the quotient ring <img src="https://latex.codecogs.com/png.latex?k%5Bx_1,%20%5Cldots,%20x_n%5D/I">.</p>
</div>
<p>We can compute the normal form with an algorithm:</p>
<div id="alg-normal-form">
<ol type="1">
<li>Start with a polynomial <img src="https://latex.codecogs.com/png.latex?f"> and a Gröbner basis <img src="https://latex.codecogs.com/png.latex?%5C%7Bg_1,%20%5Cldots,%20g_m%5C%7D"> for an ideal <img src="https://latex.codecogs.com/png.latex?I">.</li>
<li>Initialize <img src="https://latex.codecogs.com/png.latex?r%20=%20f">.</li>
<li>While there exists a <img src="https://latex.codecogs.com/png.latex?g_i"> such that the leading term of <img src="https://latex.codecogs.com/png.latex?g_i"> divides a term in <img src="https://latex.codecogs.com/png.latex?r">:
<ol type="a">
<li>Let <img src="https://latex.codecogs.com/png.latex?t"> be the term in <img src="https://latex.codecogs.com/png.latex?r"> that is divisible by <img src="https://latex.codecogs.com/png.latex?LT(g_i)">.</li>
<li>Replace <img src="https://latex.codecogs.com/png.latex?t"> in <img src="https://latex.codecogs.com/png.latex?r"> with <img src="https://latex.codecogs.com/png.latex?t%20-%20%5Cfrac%7Bt%7D%7BLT(g_i)%7D%20g_i">.</li>
</ol></li>
<li>Return <img src="https://latex.codecogs.com/png.latex?r"> as the normal form of <img src="https://latex.codecogs.com/png.latex?f"> with respect to <img src="https://latex.codecogs.com/png.latex?I">.</li>
</ol>
</div>
<p>Basically, this is just the division algorithm for polynomials. The normal form is important because it allows us to test whether a polynomial belongs to the ideal (if the normal form is zero) and to find unique representatives of equivalence classes in the quotient ring.</p>
<p>In our Python code, we can implement this as follows:</p>
<div id="001f9d9a" class="cell" data-execution_count="33">
<div class="sourceCode cell-code" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb34-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reduce</span>(f: Poly, G: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb34-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Reduce f modulo G by repeated leading-term cancellation.</span></span>
<span id="cb34-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns the remainder (normal form if G is a Groebner basis).</span></span>
<span id="cb34-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb34-5">    r: Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb34-6">    p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(f)</span>
<span id="cb34-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> p:</span>
<span id="cb34-8">        reduced <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb34-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> G:</span>
<span id="cb34-10">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> g:</span>
<span id="cb34-11">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb34-12">            lm_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_monomial(g, order)</span>
<span id="cb34-13">            lc_g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_coefficient(g, order)</span>
<span id="cb34-14">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(p.keys(), key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>order, reverse<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>):</span>
<span id="cb34-15">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> poly.mono_divides(lm_g, alpha):</span>
<span id="cb34-16">                    quot_mono <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono_div(alpha, lm_g)</span>
<span id="cb34-17">                    quot_coeff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[alpha] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lc_g</span>
<span id="cb34-18">                    shift <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mul(poly.mono(quot_mono, quot_coeff), g)</span>
<span id="cb34-19">                    p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.sub(p, shift)</span>
<span id="cb34-20">                    reduced <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb34-21">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb34-22">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> reduced:</span>
<span id="cb34-23">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb34-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> reduced:</span>
<span id="cb34-25">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> p:</span>
<span id="cb34-26">                lm_p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_monomial(p, order)</span>
<span id="cb34-27">                lc_p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p[lm_p]</span>
<span id="cb34-28">                r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(r, poly.mono(lm_p, lc_p))</span>
<span id="cb34-29">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">del</span> p[lm_p]</span>
<span id="cb34-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> r</span></code></pre></div>
</div>
</section>
<section id="ideal-membership-testing" class="level3">
<h3 class="anchored" data-anchor-id="ideal-membership-testing">Ideal Membership Testing</h3>
<p>Based on our last section, we can also test for ideal membership. Given a polynomial <img src="https://latex.codecogs.com/png.latex?f"> and an ideal <img src="https://latex.codecogs.com/png.latex?I"> generated by a set of polynomials, we can test whether <img src="https://latex.codecogs.com/png.latex?f"> belongs to <img src="https://latex.codecogs.com/png.latex?I"> by computing the normal form of <img src="https://latex.codecogs.com/png.latex?f"> with respect to a Gröbner basis for <img src="https://latex.codecogs.com/png.latex?I">. If the normal form is zero, then <img src="https://latex.codecogs.com/png.latex?f%20%5Cin%20I">; otherwise, <img src="https://latex.codecogs.com/png.latex?f%20%5Cnotin%20I">.</p>
<p>In code:</p>
<div id="f73fe0e0" class="cell" data-execution_count="34">
<div class="sourceCode cell-code" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb35-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> normal_form(f: Poly, basis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb35-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Normal form of f with respect to a Gröbner basis."""</span></span>
<span id="cb35-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reduce</span>(f, basis, order)</span>
<span id="cb35-4"></span>
<span id="cb35-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> in_ideal(f: Poly, basis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb35-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Test whether f belongs to the ideal generated by basis."""</span></span>
<span id="cb35-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> normal_form(f, basis, order)</span></code></pre></div>
</div>
</section>
<section id="ideal-intersection-and-quotients" class="level3">
<h3 class="anchored" data-anchor-id="ideal-intersection-and-quotients">Ideal Intersection and Quotients</h3>
<p>We can also use elimination to compute the intersection <img src="https://latex.codecogs.com/png.latex?I%20%5Ccap%20J"> of two ideals. Introduce a new variable <img src="https://latex.codecogs.com/png.latex?t"> and form the ideal <img src="https://latex.codecogs.com/png.latex?tI%20+%20(1-t)J"> (i.e., <img src="https://latex.codecogs.com/png.latex?(tf_1,%20%5Cldots,%20tf_r,%20(1-t)g_1,%20%5Cldots,%20(1-t)g_s)">). Then eliminate <img src="https://latex.codecogs.com/png.latex?t">, so the surviving polynomials generate <img src="https://latex.codecogs.com/png.latex?I%20%5Ccap%20J">. This works because a polynomial lies in <img src="https://latex.codecogs.com/png.latex?I%20%5Ccap%20J"> if and only if it can be written as both a combination of the <img src="https://latex.codecogs.com/png.latex?f_i"> and a combination of the <img src="https://latex.codecogs.com/png.latex?g_j">.</p>
<p>Note that taking the union of generators gives the <em>sum</em> <img src="https://latex.codecogs.com/png.latex?I%20+%20J">, not the intersection. The intersection requires the elimination trick above.</p>
<div id="0498b57b" class="cell" data-execution_count="35">
<div class="sourceCode cell-code" id="cb36" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb36-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> ideal_intersection(F: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], G: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]:</span>
<span id="cb36-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Intersection of ideals I = (F) and J = (G).</span></span>
<span id="cb36-3"></span>
<span id="cb36-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Introduces variable t, forms (t*f_i, (1-t)*g_j), eliminates t.</span></span>
<span id="cb36-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb36-6">    t_var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, n_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb36-7">    one_minus_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.sub(poly.const(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), t_var)</span>
<span id="cb36-8"></span>
<span id="cb36-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> embed(f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb36-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> alpha: c <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha, c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> f.items()}</span>
<span id="cb36-11"></span>
<span id="cb36-12">    gens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [poly.mul(t_var, embed(f)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> f <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> F]</span>
<span id="cb36-13">    gens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> [poly.mul(one_minus_t, embed(g)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> G]</span>
<span id="cb36-14"></span>
<span id="cb36-15">    elim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> eliminate(gens, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n_vars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>n_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb36-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [{alpha[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]: c <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha, c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> g.items()} <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> elim]</span></code></pre></div>
</div>
<p>As an example, take <img src="https://latex.codecogs.com/png.latex?I%20=%20(x%5E2,%20y)"> and <img src="https://latex.codecogs.com/png.latex?J%20=%20(x,%20y%5E2)"> in <img src="https://latex.codecogs.com/png.latex?k%5Bx,%20y%5D">. A polynomial is in <img src="https://latex.codecogs.com/png.latex?I"> if it’s divisible by <img src="https://latex.codecogs.com/png.latex?x%5E2"> or <img src="https://latex.codecogs.com/png.latex?y">, and it’s in <img src="https://latex.codecogs.com/png.latex?J"> if it’s divisible by <img src="https://latex.codecogs.com/png.latex?x"> or <img src="https://latex.codecogs.com/png.latex?y%5E2">. The intersection consists of polynomials in both: <img src="https://latex.codecogs.com/png.latex?I%20%5Ccap%20J%20=%20(x%5E2,%20xy,%20y%5E2)">.</p>
<div id="bd427e78" class="cell" data-execution_count="36">
<div class="sourceCode cell-code" id="cb37" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb37-1">I <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)), poly.var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)]   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (x^2, y)</span></span>
<span id="cb37-2">J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [poly.var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>), poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))]   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (x, y^2)</span></span>
<span id="cb37-3"></span>
<span id="cb37-4">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ideal_intersection(I, J, n_vars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb37-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [x^2, xy, y^2]</span></span></code></pre></div>
</div>
</section>
<section id="elimination-of-variables" class="level3">
<h3 class="anchored" data-anchor-id="elimination-of-variables">Elimination of Variables</h3>
<p>Suppose we want to eliminate a variable <img src="https://latex.codecogs.com/png.latex?x_n"> from an ideal <img src="https://latex.codecogs.com/png.latex?I"> in <img src="https://latex.codecogs.com/png.latex?k%5Bx_1,%20%5Cldots,%20x_n%5D">. We can do this by computing a Gröbner basis for <img src="https://latex.codecogs.com/png.latex?I"> with respect to an elimination ordering that prioritizes <img src="https://latex.codecogs.com/png.latex?x_n"> last. The resulting Gröbner basis will contain polynomials that do not involve <img src="https://latex.codecogs.com/png.latex?x_n">, and these polynomials will generate the elimination ideal <img src="https://latex.codecogs.com/png.latex?I%20%5Ccap%20k%5Bx_1,%20%5Cldots,%20x_%7Bn-1%7D%5D">.</p>
<div id="2b3c8c3f" class="cell" data-execution_count="37">
<div class="sourceCode cell-code" id="cb38" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb38-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> eliminate(generators: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], k: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]:</span>
<span id="cb38-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Eliminate the first k variables.</span></span>
<span id="cb38-3"></span>
<span id="cb38-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Computes a Gröbner basis with an elimination ordering that pushes</span></span>
<span id="cb38-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    x_0, ..., x_{k-1} to the top, then returns only the polynomials</span></span>
<span id="cb38-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    that live in k[x_k, ..., x_{n-1}].</span></span>
<span id="cb38-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb38-8">    order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.elimination_order(k)</span>
<span id="cb38-9">    gb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> buchberger(generators, order)</span>
<span id="cb38-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> gb <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> _involves_only(g, k, n_vars)]</span></code></pre></div>
</div>
<p>Of course, this is just a special case of the ideal intersection method, since we can think of the elimination ideal as the intersection of <img src="https://latex.codecogs.com/png.latex?I"> with the subring that does not involve <img src="https://latex.codecogs.com/png.latex?x_n">.</p>
</section>
<section id="computation-of-relations-between-polynomial-generators" class="level3">
<h3 class="anchored" data-anchor-id="computation-of-relations-between-polynomial-generators">Computation of Relations between Polynomial Generators</h3>
<p>Given generators <img src="https://latex.codecogs.com/png.latex?g_1,%20%5Cldots,%20g_s"> in <img src="https://latex.codecogs.com/png.latex?k%5Bx_0,%20%5Cldots,%20x_%7Bn-1%7D%5D">, we want to find all the polynomial relations among them.</p>
<p>Introduce new variables <img src="https://latex.codecogs.com/png.latex?y_0,%20%5Cldots,%20y_%7Bs-1%7D">, form the ideal <img src="https://latex.codecogs.com/png.latex?(y_i%20-%20g_i)"> in <img src="https://latex.codecogs.com/png.latex?k%5Bx,%20y%5D">, and eliminate <img src="https://latex.codecogs.com/png.latex?x_0,%20%5Cldots,%20x_%7Bn-1%7D">. The surviving polynomials in <img src="https://latex.codecogs.com/png.latex?k%5By%5D"> are exactly the relations.</p>
<div id="402c7bbb" class="cell" data-execution_count="38">
<div class="sourceCode cell-code" id="cb39" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb39-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_relations(generators: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>,</span>
<span id="cb39-2">                      order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]:</span>
<span id="cb39-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Compute relations among polynomial generators.</span></span>
<span id="cb39-4"></span>
<span id="cb39-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Given generators g_1, ..., g_s in k[x_0, ..., x_{n-1}], introduce</span></span>
<span id="cb39-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    new variables y_0, ..., y_{s-1} and compute the kernel of the map</span></span>
<span id="cb39-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    k[y_0, ..., y_{s-1}] -&gt; k[x_0, ..., x_{n-1}] sending y_i -&gt; g_i.</span></span>
<span id="cb39-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb39-9">    s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(generators)</span>
<span id="cb39-10">    total_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> n_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> s</span>
<span id="cb39-11"></span>
<span id="cb39-12">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># embed each g_i into k[x_0,...,x_{n-1}, y_0,...,y_{s-1}]</span></span>
<span id="cb39-13">    elim_gens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb39-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(generators):</span>
<span id="cb39-15">        y_alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(s))</span>
<span id="cb39-16">        yi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono(y_alpha)</span>
<span id="cb39-17">        g_emb: Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb39-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha, c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> g.items():</span>
<span id="cb39-19">            new_alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> s</span>
<span id="cb39-20">            g_emb[new_alpha] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> c</span>
<span id="cb39-21">        elim_gens.append(poly.sub(yi, g_emb))</span>
<span id="cb39-22"></span>
<span id="cb39-23">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># eliminate x_0, ..., x_{n-1}</span></span>
<span id="cb39-24">    elim_order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.elimination_order(n_vars)</span>
<span id="cb39-25">    gb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> buchberger(elim_gens, elim_order)</span>
<span id="cb39-26"></span>
<span id="cb39-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># keep only polynomials in k[y_0, ..., y_{s-1}]</span></span>
<span id="cb39-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> gb <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> _involves_only(g, n_vars, total_vars)]</span></code></pre></div>
</div>
</section>
<section id="computation-of-syzygies" class="level3">
<h3 class="anchored" data-anchor-id="computation-of-syzygies">Computation of Syzygies</h3>
<p>Given generators <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_s"> of an ideal, a syzygy is a tuple <img src="https://latex.codecogs.com/png.latex?(h_1,%20%5Cldots,%20h_s)"> of polynomials such that <img src="https://latex.codecogs.com/png.latex?h_1%20f_1%20+%20%5Ccdots%20+%20h_s%20f_s%20=%200">. The syzygies encode the dependencies among the generators.</p>
<p>The S-polynomial construction we looked at above already produces some syzygies. If <img src="https://latex.codecogs.com/png.latex?S(f_i,%20f_j)"> reduces to zero modulo the generators, the reduction path gives a relation <img src="https://latex.codecogs.com/png.latex?h_1%20f_1%20+%20%5Ccdots%20+%20h_s%20f_s%20=%200">. The routine below should be read as a partial syzygy computation rather than a complete one.</p>
<div id="b71d55d9" class="cell" data-execution_count="39">
<div class="sourceCode cell-code" id="cb40" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb40-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compute_syzygies(generators: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>,</span>
<span id="cb40-2">                     order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]:</span>
<span id="cb40-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Compute syzygies among generators of an ideal.</span></span>
<span id="cb40-4"></span>
<span id="cb40-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    For each pair (i,j), if the S-polynomial reduces to zero,</span></span>
<span id="cb40-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    record the corresponding syzygy in k[x, e_0, ..., e_{s-1}].</span></span>
<span id="cb40-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb40-8">    s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(generators)</span>
<span id="cb40-9">    syzygies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb40-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(s):</span>
<span id="cb40-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, s):</span>
<span id="cb40-12">            fi, fj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generators[i], generators[j]</span>
<span id="cb40-13">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> fi <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> fj:</span>
<span id="cb40-14">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb40-15">            sp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> s_poly(fi, fj, order)</span>
<span id="cb40-16">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reduce</span>(sp, generators, order):</span>
<span id="cb40-17">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build syzygy: (lcm/LT(fi))/lc_i * e_i - (lcm/LT(fj))/lc_j * e_j</span></span>
<span id="cb40-18">                lm_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_monomial(fi, order)</span>
<span id="cb40-19">                lm_j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_monomial(fj, order)</span>
<span id="cb40-20">                gamma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono_lcm(lm_i, lm_j)</span>
<span id="cb40-21">                qi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono_div(gamma, lm_i)</span>
<span id="cb40-22">                qj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono_div(gamma, lm_j)</span>
<span id="cb40-23">                lc_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_coefficient(fi, order)</span>
<span id="cb40-24">                lc_j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.leading_coefficient(fj, order)</span>
<span id="cb40-25">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># encode in k[x_0,...,x_{n-1}, e_0,...,e_{s-1}]</span></span>
<span id="cb40-26">                ei <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(s))</span>
<span id="cb40-27">                ej <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> j <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(s))</span>
<span id="cb40-28">                syz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.sub(</span>
<span id="cb40-29">                    poly.mono(qi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> ei, Fraction(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lc_i),</span>
<span id="cb40-30">                    poly.mono(qj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> ej, Fraction(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lc_j),</span>
<span id="cb40-31">                )</span>
<span id="cb40-32">                syzygies.append(syz)</span>
<span id="cb40-33">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> syzygies</span>
<span id="cb40-34"></span>
<span id="cb40-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Example: syzygies of (x, y) in k[x,y]</span></span>
<span id="cb40-36"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Returns y*e_0 - x*e_1, i.e., y*x - x*y = 0</span></span></code></pre></div>
</div>
</section>
<section id="computation-of-hilbert-series" class="level3">
<h3 class="anchored" data-anchor-id="computation-of-hilbert-series">Computation of Hilbert Series</h3>
<p>Given a Gröbner basis for an ideal <img src="https://latex.codecogs.com/png.latex?I">, the Hilbert function of the quotient ring <img src="https://latex.codecogs.com/png.latex?k%5Bx_1,%20%5Cldots,%20x_n%5D/I"> counts the monomials in each degree that are not divisible by any leading monomial of the basis. This works because the leading term ideal <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BLT%7D(I)"> has the same Hilbert function as <img src="https://latex.codecogs.com/png.latex?I">, and monomials not in <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BLT%7D(I)"> form a vector space basis for <img src="https://latex.codecogs.com/png.latex?k%5Bx%5D/I">.</p>
<div id="88201581" class="cell" data-execution_count="40">
<div class="sourceCode cell-code" id="cb41" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb41-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hilbert_function(basis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], n_vars: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, max_d: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>,</span>
<span id="cb41-2">                     order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]:</span>
<span id="cb41-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Hilbert function of k[x]/I from degree 0 to max_d.</span></span>
<span id="cb41-4"></span>
<span id="cb41-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Counts monomials of each degree not divisible by any leading</span></span>
<span id="cb41-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    monomial of the Gröbner basis.</span></span>
<span id="cb41-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb41-8">    leading_monomials <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [poly.leading_monomial(g, order) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> basis <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> g]</span>
<span id="cb41-9">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb41-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb41-11">        count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb41-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> poly.monomials_of_degree(n_vars, d):</span>
<span id="cb41-13">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(poly.mono_divides(lm, alpha) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> lm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> leading_monomials):</span>
<span id="cb41-14">                count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb41-15">        result.append(count)</span>
<span id="cb41-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span>
<span id="cb41-17"></span>
<span id="cb41-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Example: GB of (x^2 + y - 1, xy + 1) has LT ideal (x^2, xy, y^2)</span></span>
<span id="cb41-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Hilbert function: [1, 2, 0, 0, ...] — quotient is 3-dimensional</span></span></code></pre></div>
</div>
</section>
<section id="nullstellensatz-style-problems" class="level3">
<h3 class="anchored" data-anchor-id="nullstellensatz-style-problems">Nullstellensatz-Style Problems</h3>
<p>The Nullstellensatz, which we saw in the last post, says that a system of polynomial equations <img src="https://latex.codecogs.com/png.latex?f_1%20=%20%5Ccdots%20=%20f_r%20=%200"> has no common solution if and only if <img src="https://latex.codecogs.com/png.latex?1"> lies in the ideal <img src="https://latex.codecogs.com/png.latex?(f_1,%20%5Cldots,%20f_r)">. We can test this by computing a Gröbner basis. If the basis contains a nonzero constant, the ideal is all of <img src="https://latex.codecogs.com/png.latex?k%5Bx%5D"> and the system is inconsistent.</p>
<div id="d38df434" class="cell" data-execution_count="41">
<div class="sourceCode cell-code" id="cb42" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb42-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> has_common_root(basis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly], order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly.grlex) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb42-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Test whether V(I) is nonempty.</span></span>
<span id="cb42-3"></span>
<span id="cb42-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    By the Nullstellensatz, V(I) = {} iff 1 ∈ I iff GB = {1}.</span></span>
<span id="cb42-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb42-6">    gb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> buchberger(basis, order)</span>
<span id="cb42-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> gb:</span>
<span id="cb42-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">all</span>(e <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> poly.leading_monomial(g, order)):</span>
<span id="cb42-9">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1 ∈ I, no common root</span></span>
<span id="cb42-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb42-11"></span>
<span id="cb42-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Example: has_common_root([x, 1-x]) returns False (no solution)</span></span></code></pre></div>
</div>
</section>
</section>
</section>
<section id="finite-groups-1" class="level1">
<h1>Finite Groups</h1>
<p>Now that we have a bit more general infra from the last two sections, let’s work on another computationally friendly class of examples: finite groups.</p>
<section id="reynolds-operator" class="level2">
<h2 class="anchored" data-anchor-id="reynolds-operator">Reynolds Operator</h2>
<p>In the last post, we saw that for certain kinds of groups (like finite groups) <img src="https://latex.codecogs.com/png.latex?G"> acting on vector spaces <img src="https://latex.codecogs.com/png.latex?V">, we could define the Reynolds operator, which is an idempotent operator that takes any polynomial and averages over the group action to produce an invariant polynomial:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AR(f)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20g%20%5Ccdot%20f%0A"></p>
<p>If we have a polynomial <img src="https://latex.codecogs.com/png.latex?f"> that is not invariant, applying the Reynolds operator will give us an invariant polynomial.</p>
<p>Recall that we represented a finite group as a list of matrices that act on the variables. To apply the Reynolds operator, we can take any polynomial and apply each group element to it by substituting the linear forms corresponding to the group action. Then we average these transformed polynomials to get an invariant.</p>
<p>Start with a set of polynomials that generate the polynomial ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D"> and apply the Reynolds operator to each of these polynomials to project them onto the invariant ring. This will give us a set of invariant polynomials, which may generate the invariant ring or at least give us a starting point for finding generators.</p>
<p>Let’s implement the Reynolds operator in code, which involves summing over the group elements and applying the group action to the polynomial. While computationally expensive for large groups, it’s a straightforward way to find invariants.</p>
<p>The key subroutine is <code>apply_to_poly</code>. Given a matrix <img src="https://latex.codecogs.com/png.latex?g"> and a polynomial <img src="https://latex.codecogs.com/png.latex?f">, we compute <img src="https://latex.codecogs.com/png.latex?(g%20%5Ccdot%20f)(x)%20=%20f(g%5E%7B-1%7Dx)"> by substituting linear forms for each variable. Reynolds then just averages over all group elements.</p>
<div id="9c5e9fd5" class="cell" data-execution_count="42">
<div class="sourceCode cell-code" id="cb43" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb43-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> apply_to_poly(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: np.ndarray, f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb43-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""(g · f)(x) = f(g^{-1} x) via variable substitution."""</span></span>
<span id="cb43-3">    g_inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.inv(g)</span>
<span id="cb43-4">    n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_vars</span>
<span id="cb43-5">    sub_polys <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb43-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n):</span>
<span id="cb43-7">        row: Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb43-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n):</span>
<span id="cb43-9">            c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Fraction(g_inv[i, j]).limit_denominator(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span>)</span>
<span id="cb43-10">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb43-11">                row[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> j <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n))] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> c</span>
<span id="cb43-12">        sub_polys.append(row)</span>
<span id="cb43-13"></span>
<span id="cb43-14">    result: Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb43-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha, coeff <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> f.items():</span>
<span id="cb43-16">        term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.const(coeff, n)</span>
<span id="cb43-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(alpha):</span>
<span id="cb43-18">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(e):</span>
<span id="cb43-19">                term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mul(term, sub_polys[i])</span>
<span id="cb43-20">        result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(result, term)</span>
<span id="cb43-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span>
<span id="cb43-22"></span>
<span id="cb43-23"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> reynolds(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb43-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Reynolds operator: R(f) = (1/|G|) sum_{g in G} g · f."""</span></span>
<span id="cb43-25">    total: Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb43-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.matrices:</span>
<span id="cb43-27">        total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(total, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.apply_to_poly(g, f))</span>
<span id="cb43-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> poly.scale(Fraction(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.order), total)</span></code></pre></div>
</div>
<p>For example, take <img src="https://latex.codecogs.com/png.latex?S_3"> acting on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3"> by permuting coordinates. The monomial <img src="https://latex.codecogs.com/png.latex?x_0%5E2"> is not invariant (permutations move it to <img src="https://latex.codecogs.com/png.latex?x_1%5E2"> or <img src="https://latex.codecogs.com/png.latex?x_2%5E2">). Applying Reynolds averages over all 6 permutations:</p>
<div id="66f4975e" class="cell" data-execution_count="43">
<div class="sourceCode cell-code" id="cb44" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb44-1">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetric(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb44-2">f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))          </span>
<span id="cb44-3">Rf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> G.reynolds(f)</span>
<span id="cb44-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rf = 1/3*x0^2 + 1/3*x1^2 + 1/3*x2^2</span></span>
<span id="cb44-5"></span>
<span id="cb44-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> G.is_invariant(Rf)          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True</span></span>
<span id="cb44-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> G.reynolds(Rf) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> Rf        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># idempotent: R(R(f)) = R(f)</span></span></code></pre></div>
</div>
<p>The output <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7B3%7D(x_0%5E2%20+%20x_1%5E2%20+%20x_2%5E2)"> is the power sum <img src="https://latex.codecogs.com/png.latex?p_2/3">, which is obviously symmetric. Note that Reynolds is idempotent, so applying it to an already-invariant polynomial returns the same polynomial.</p>
</section>
<section id="orbit-sums" class="level2">
<h2 class="anchored" data-anchor-id="orbit-sums">Orbit Sums</h2>
<p>Reynolds averaging is a special case of a more general construction called orbit sums. Given a polynomial <img src="https://latex.codecogs.com/png.latex?f">, we can consider the orbit of <img src="https://latex.codecogs.com/png.latex?f"> under the group action:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7BOrb%7D(f)%20=%20%5C%7Bg%20%5Ccdot%20f%20%5Cmid%20g%20%5Cin%20G%5C%7D%0A"></p>
<p>The orbit sum is the sum of all elements in the orbit:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS(f)%20=%20%5Csum_%7Bg%20%5Cin%20G%7D%20g%20%5Ccdot%20f%0A"></p>
<p>This is similar to the Reynolds operator, but without the normalization factor of <img src="https://latex.codecogs.com/png.latex?1/%7CG%7C">.</p>
<p>Since the orbit sum is just the sum of elements in the orbit, and orbits are invariant under the group action (since the summands are just permuted), the orbit sum is also invariant under the group action.</p>
<p>That is, for any <img src="https://latex.codecogs.com/png.latex?h%20%5Cin%20G">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ah%20%5Ccdot%20S(f)%20=%20h%20%5Ccdot%20%5Cleft(%20%5Csum_%7Bg%20%5Cin%20G%7D%20g%20%5Ccdot%20f%20%5Cright)%20=%20%5Csum_%7Bg%20%5Cin%20G%7D%20h%20%5Ccdot%20(g%20%5Ccdot%20f)%20=%20%5Csum_%7Bg'%20%5Cin%20G%7D%20g'%20%5Ccdot%20f%20=%20S(f)%0A"></p>
<p>For degree <img src="https://latex.codecogs.com/png.latex?d"> polynomials, this works cleanly when the action sends monomials to monomials (for example permutation actions): then the invariance condition forces the coefficients to be constant on monomial orbits, so every homogeneous invariant is a linear combination of orbit sums. For a general linear action, orbit sums of monomials are better viewed as a convenient source of candidate invariants than as a general basis theorem.</p>
<p>So in the monomial/permutation cases, the orbit sums of monomials form a basis for the space of homogeneous invariants of degree <img src="https://latex.codecogs.com/png.latex?d">. This gives us a way to find a basis for the invariant ring degree by degree in those cases.</p>
<p>In code, we can implement this as follows:</p>
<div id="56811795" class="cell" data-execution_count="44">
<div class="sourceCode cell-code" id="cb45" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb45-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> orbit_sum(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, f: Poly) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Poly:</span>
<span id="cb45-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Sum over distinct images of f under G."""</span></span>
<span id="cb45-3">    seen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>()</span>
<span id="cb45-4">    total: Poly <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb45-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.matrices:</span>
<span id="cb45-6">        gf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.apply_to_poly(g, f)</span>
<span id="cb45-7">        key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">frozenset</span>(gf.items())</span>
<span id="cb45-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> key <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> seen:</span>
<span id="cb45-9">            seen.add(key)</span>
<span id="cb45-10">            total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(total, gf)</span>
<span id="cb45-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> total</span>
<span id="cb45-12"></span>
<span id="cb45-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> invariants_of_degree(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, d: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Poly]:</span>
<span id="cb45-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Degree-d invariants via orbit sums of monomials."""</span></span>
<span id="cb45-15">    seen_orbits: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">frozenset</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>()</span>
<span id="cb45-16">    basis <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb45-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> alpha <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> poly.monomials_of_degree(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_vars, d):</span>
<span id="cb45-18">        f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono(alpha)</span>
<span id="cb45-19">        orbit_key <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">frozenset</span>(</span>
<span id="cb45-20">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">frozenset</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.apply_to_poly(g, f).items())</span>
<span id="cb45-21">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.matrices</span>
<span id="cb45-22">        )</span>
<span id="cb45-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> orbit_key <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> seen_orbits:</span>
<span id="cb45-24">            seen_orbits.add(orbit_key)</span>
<span id="cb45-25">            os <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.orbit_sum(f)</span>
<span id="cb45-26">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> os:</span>
<span id="cb45-27">                basis.append(os)</span>
<span id="cb45-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> basis</span></code></pre></div>
</div>
<p>The <code>orbit_sum</code> method tracks which images have already been seen (via <code>frozenset</code> of the polynomial’s items) to avoid double-counting when the stabilizer of <img src="https://latex.codecogs.com/png.latex?f"> is nontrivial. The <code>invariants_of_degree</code> method groups monomials by orbit, computes one orbit sum per orbit, and collects them as a basis.</p>
<p>Continuing with <img src="https://latex.codecogs.com/png.latex?S_3"> on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3">, the degree-2 monomials are <img src="https://latex.codecogs.com/png.latex?x_0%5E2,%20x_0%20x_1,%20x_0%20x_2,%20x_1%5E2,%20x_1%20x_2,%20x_2%5E2">. Under <img src="https://latex.codecogs.com/png.latex?S_3">, these fall into two orbits: <img src="https://latex.codecogs.com/png.latex?%5C%7Bx_0%5E2,%20x_1%5E2,%20x_2%5E2%5C%7D"> and <img src="https://latex.codecogs.com/png.latex?%5C%7Bx_0%20x_1,%20x_0%20x_2,%20x_1%20x_2%5C%7D">. The orbit sums give two linearly independent degree-2 invariants:</p>
<div id="d43413ed" class="cell" data-execution_count="45">
<div class="sourceCode cell-code" id="cb46" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb46-1">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetric(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb46-2"></span>
<span id="cb46-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Orbit sum of x0^2: hits x0^2, x1^2, x2^2</span></span>
<span id="cb46-4">G.orbit_sum(poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)))</span>
<span id="cb46-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># x0^2 + x1^2 + x2^2</span></span>
<span id="cb46-6"></span>
<span id="cb46-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Orbit sum of x0*x1: hits x0*x1, x0*x2, x1*x2</span></span>
<span id="cb46-8">G.orbit_sum(poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)))</span>
<span id="cb46-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># x0*x1 + x0*x2 + x1*x2</span></span>
<span id="cb46-10"></span>
<span id="cb46-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># All degree-2 invariants at once</span></span>
<span id="cb46-12">G.invariants_of_degree(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb46-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [x0^2 + x1^2 + x2^2, x0*x1 + x0*x2 + x1*x2]</span></span></code></pre></div>
</div>
<p>These are the power sum <img src="https://latex.codecogs.com/png.latex?p_2%20=%20x_0%5E2%20+%20x_1%5E2%20+%20x_2%5E2"> and the elementary symmetric polynomial <img src="https://latex.codecogs.com/png.latex?e_2%20=%20x_0%20x_1%20+%20x_0%20x_2%20+%20x_1%20x_2">.</p>
</section>
<section id="molien-series" class="level2">
<h2 class="anchored" data-anchor-id="molien-series">Molien Series</h2>
<p>Since the Reynolds operator projects onto the invariant ring, we know that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bdim%7D_k(k%5BV%5D%5EG_d)%20=%20%5Ctext%7Bdim%7D_k(R(k%5BV%5D_d))%20=%20%5Ctext%7Btr%7D(R%7C_%7Bk%5BV%5D_d%7D)%0A"></p>
<p>(since the trace of a projection operator is the dimension of its image<sup>1</sup>).</p>
<p>Thus we can compute the trace as follows:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Btr%7D(R%7C_%7Bk%5BV%5D_d%7D)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20%5Ctext%7Btr%7D(g%7C_%7Bk%5BV%5D_d%7D)%0A"></p>
<p>Since <img src="https://latex.codecogs.com/png.latex?k%5BV%5D_d"> is the space of homogeneous degree <img src="https://latex.codecogs.com/png.latex?d"> polynomials, we can identify it with the <img src="https://latex.codecogs.com/png.latex?d">-th symmetric power of the dual space <img src="https://latex.codecogs.com/png.latex?V%5E*">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ak%5BV%5D_d%20%5Ccong%20%5Coperatorname%7BSym%7D%5Ed(V%5E*)%0A"></p>
<p>The trace of <img src="https://latex.codecogs.com/png.latex?g"> on <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7BSym%7D%5Ed(V%5E*)"> can be computed using the eigenvalues of <img src="https://latex.codecogs.com/png.latex?g"> on <img src="https://latex.codecogs.com/png.latex?V%5E*">. If the eigenvalues of <img src="https://latex.codecogs.com/png.latex?g"> on <img src="https://latex.codecogs.com/png.latex?V%5E*"> are <img src="https://latex.codecogs.com/png.latex?%5Clambda_1,%20%5Cldots,%20%5Clambda_n">, then the trace of <img src="https://latex.codecogs.com/png.latex?g"> on <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7BSym%7D%5Ed(V%5E*)"> is given by the complete homogeneous symmetric polynomial of degree <img src="https://latex.codecogs.com/png.latex?d"> in the eigenvalues:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Btr%7D(g%7C_%7Bk%5BV%5D_d%7D)%20=%20h_d(%5Clambda_1,%20%5Cldots,%20%5Clambda_n)%0A"></p>
<p>The generating function for the complete homogeneous symmetric polynomials is given by<sup>2</sup>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Csum_%7Bd=0%7D%5E%7B%5Cinfty%7D%20h_d(%5Clambda_1,%20%5Cldots,%20%5Clambda_n)%20t%5Ed%20=%20%5Cprod_%7Bi=1%7D%5En%20%5Cfrac%7B1%7D%7B1%20-%20%5Clambda_i%20t%7D%20=%20%5Cfrac%7B1%7D%7B%5Cdet(I%20-%20t%20g)%7D%0A"></p>
<p>This motivates the Molien formula for the Hilbert series of the invariant ring<sup>3</sup>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20%5Cfrac%7B1%7D%7B%5Cdet(I%20-%20t%20g)%7D%0A"></p>
<p>To use the Molien formula computationally, we expand <img src="https://latex.codecogs.com/png.latex?1/%5Cdet(I%20-%20tg)"> as a power series in <img src="https://latex.codecogs.com/png.latex?t"> for each group element <img src="https://latex.codecogs.com/png.latex?g">. Since <img src="https://latex.codecogs.com/png.latex?%5Cdet(I%20-%20tg)%20=%20%5Cprod_i%20(1%20-%20%5Clambda_i%20t)"> where <img src="https://latex.codecogs.com/png.latex?%5Clambda_i"> are the eigenvalues of <img src="https://latex.codecogs.com/png.latex?g">, the series expansion is a convolution of geometric series. We truncate at degree <code>max_d</code>, sum over all group elements, divide by <img src="https://latex.codecogs.com/png.latex?%7CG%7C">, and round to the nearest integer (the result is exact for rational eigenvalues; rounding handles floating-point eigenvalues from rotation matrices).</p>
<div id="3adc7162" class="cell" data-execution_count="46">
<div class="sourceCode cell-code" id="cb47" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb47-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> molien_coeffs(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, max_d: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]:</span>
<span id="cb47-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Molien series: H(t) = (1/|G|) sum_{g in G} 1/det(I - tg)."""</span></span>
<span id="cb47-3">    coeffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros(max_d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">complex</span>)</span>
<span id="cb47-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.matrices:</span>
<span id="cb47-5">        eigenvalues <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.eigvals(g)</span>
<span id="cb47-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Expand 1/prod(1 - lambda_i * t) as power series</span></span>
<span id="cb47-7">        series <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros(max_d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">complex</span>)</span>
<span id="cb47-8">        series[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb47-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> lam <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> eigenvalues:</span>
<span id="cb47-10">            powers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([lam<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>d <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)])</span>
<span id="cb47-11">            new_series <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros(max_d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">complex</span>)</span>
<span id="cb47-12">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb47-13">                new_series[d] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(series[k] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> powers[d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> k]</span>
<span id="cb47-14">                                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb47-15">            series <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> new_series</span>
<span id="cb47-16">        coeffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> series</span>
<span id="cb47-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>((coeffs[d] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.order).real)</span>
<span id="cb47-18">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)]</span></code></pre></div>
</div>
<p>For <img src="https://latex.codecogs.com/png.latex?S_3"> on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3">, the group has 6 elements.</p>
<p>The identity has eigenvalues <img src="https://latex.codecogs.com/png.latex?(1,1,1)">, contributing <img src="https://latex.codecogs.com/png.latex?1/(1-t)%5E3">.</p>
<p>The transpositions have eigenvalues <img src="https://latex.codecogs.com/png.latex?(1,1,-1)">, contributing <img src="https://latex.codecogs.com/png.latex?1/((1-t)%5E2(1+t))">.</p>
<p>The 3-cycles have eigenvalues <img src="https://latex.codecogs.com/png.latex?(1,%20%5Comega,%20%5Comega%5E2)"> where <img src="https://latex.codecogs.com/png.latex?%5Comega%20=%20e%5E%7B2%5Cpi%20i/3%7D">, contributing <img src="https://latex.codecogs.com/png.latex?1/((1-t)(1-%5Comega%20t)(1-%5Comega%5E2%20t))%20=%201/(1-t%5E3)">.</p>
<p>Averaging over all 6 elements:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t)%20=%20%5Cfrac%7B1%7D%7B6%7D%5Cleft(%5Cfrac%7B1%7D%7B(1-t)%5E3%7D%20+%20%5Cfrac%7B3%7D%7B(1-t)%5E2(1+t)%7D%20+%20%5Cfrac%7B2%7D%7B(1-t%5E3)%7D%5Cright)%0A"></p>
<p>which simplifies to :</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t)%20=%20%5Cfrac%7B1%7D%7B(1-t)(1-t%5E2)(1-t%5E3)%7D%0A"></p>
<p>So the invariant ring is a polynomial ring in three generators of degrees 1, 2, 3 (the power sums <img src="https://latex.codecogs.com/png.latex?p_1,%20p_2,%20p_3">). Verify numerically:</p>
<div id="fdcabeff" class="cell" data-execution_count="47">
<div class="sourceCode cell-code" id="cb48" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb48-1">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetric(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb48-2">G.molien_coeffs(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)</span>
<span id="cb48-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1, 1, 2, 3, 4, 5, 7, 8, 10, 12, 14]</span></span></code></pre></div>
</div>
<p>If we expanded <img src="https://latex.codecogs.com/png.latex?1/((1-t)(1-t%5E2)(1-t%5E3))"> as a power series, we would get the same coefficients, confirming that the Molien series correctly predicts the dimensions of the invariant ring in each degree.</p>
</section>
<section id="noether-bound" class="level2">
<h2 class="anchored" data-anchor-id="noether-bound">Noether Bound</h2>
<p>The Molien series gives us the dimensions of the graded pieces of the invariant ring, which tells us how many invariants we should expect in each degree. The orbit sums give us explicit invariants to fill those dimensions. But how do we know the search terminates?</p>
<div id="thm-noether-bound" class="theorem">
<p><span class="theorem-title"><strong>Theorem 1</strong></span> Let <img src="https://latex.codecogs.com/png.latex?G"> be a finite group acting linearly on a finite-dimensional vector space <img src="https://latex.codecogs.com/png.latex?V"> over a field of characteristic zero. Then the invariant ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"> is generated by invariants of degree at most <img src="https://latex.codecogs.com/png.latex?%7CG%7C">.</p>
</div>
<p><em>Proof</em>: See Fleischmann, <a href="https://doi.org/10.1006/aima.2000.1952">“The Noether Bound in Invariant Theory of Finite Groups,”</a> <em>Advances in Mathematics</em> 156 (2000), which also proves the sharper bound <img src="https://latex.codecogs.com/png.latex?%3C%20%7CG%7C"> for non-cyclic groups. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>In practice, this means we can search for generators by calling <code>invariants_of_degree(d)</code> for <img src="https://latex.codecogs.com/png.latex?d%20=%201,%20%5Cldots,%20%7CG%7C"> and be guaranteed to find them all.</p>
<p>For <img src="https://latex.codecogs.com/png.latex?S_3"> with <img src="https://latex.codecogs.com/png.latex?%7CG%7C%20=%206">, we only need to check up to degree 6 (and in fact all three generators appear by degree 3).</p>
</section>
<section id="practical-issues" class="level2">
<h2 class="anchored" data-anchor-id="practical-issues">Practical Issues</h2>
<p>There’s some practical problems with the computational approach. The main computational bottleneck for finite groups is <code>apply_to_poly</code>, which substitutes linear forms for each variable. For a polynomial with <img src="https://latex.codecogs.com/png.latex?T"> terms in <img src="https://latex.codecogs.com/png.latex?n"> variables, each group element costs <img src="https://latex.codecogs.com/png.latex?O(T%20%5Ccdot%20n%20%5Ccdot%20%5Cdeg(f))"> polynomial multiplications. Summing over all <img src="https://latex.codecogs.com/png.latex?%7CG%7C"> elements, the total cost of Reynolds or orbit sums is <img src="https://latex.codecogs.com/png.latex?O(%7CG%7C%20%5Ccdot%20T%20%5Ccdot%20n%20%5Ccdot%20%5Cdeg(f))">. For <img src="https://latex.codecogs.com/png.latex?S_n"> with <img src="https://latex.codecogs.com/png.latex?%7CG%7C%20=%20n!">, this becomes infeasible quickly.</p>
<p>Orbit sums (a bit) cheaper than Reynolds since they avoid the <img src="https://latex.codecogs.com/png.latex?1/%7CG%7C"> normalization (keeping coefficients as integers) and deduplicate images. The effective cost is proportional to the orbit size rather than <img src="https://latex.codecogs.com/png.latex?%7CG%7C">.</p>
<p>The Molien series is cheap by comparison, as it only requires eigenvalue computations (<img src="https://latex.codecogs.com/png.latex?O(%7CG%7C%20%5Ccdot%20n%5E3)">) with no polynomial arithmetic.</p>
<p>So probably we compute the Hilbert series first, then use it as a guide to say how many generators to expect in each degree, then do the relatively expensive orbit sum computation.</p>
<p>I’m not gonna try to optimize all these algorithms in this post, I’m still learning how it works myself. But in practice, for large groups, I assume we could optimize the <code>apply_to_poly</code> method, maybe by caching intermediate results or using better data structures for polynomials.</p>
</section>
</section>
<section id="primary-and-secondary-invariants" class="level1">
<h1>Primary and Secondary Invariants</h1>
<p>With the Hilbert series, we can start to understand the structure of the invariant ring. In particular, we want to understand how the invariant ring can be decomposed, typically into a polynomial part and a “remainder” part.</p>
<p>This leads to the concepts of primary and secondary invariants. The idea is that we can find a smaller set of invariants (the primary invariants) that generate a polynomial subring, and then the rest of the invariant ring can be expressed as a module over this polynomial subring, with the secondary invariants serving as module generators. By writing in terms of the primary invariants, we can get a more compact description of the invariant ring, and the secondary invariants capture the “extra” structure that isn’t captured by the primary invariants alone.</p>
<section id="homogeneous-systems-of-parameters-hsop" class="level2">
<h2 class="anchored" data-anchor-id="homogeneous-systems-of-parameters-hsop">Homogeneous Systems of Parameters (HSOP)</h2>
<p>Before we can define primary and secondary invariants, we need to introduce the notion of a homogeneous system of parameters (HSOP), which help understand the structure of graded rings.</p>
<div id="def-homogeneous-system-of-parameters" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 8</strong></span> A homogeneous system of parameters (HSOP) for a graded invariant ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"> is a collection of homogeneous elements <img src="https://latex.codecogs.com/png.latex?%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%20%5Cin%20k%5BV%5D%5EG"> such that <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"> is finitely generated as a module over the polynomial subring <img src="https://latex.codecogs.com/png.latex?k%5B%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%5D">.</p>
</div>
<p>For finite groups, we can always find an HSOP consisting of algebraically independent homogeneous invariants. In general, finding an HSOP can be more difficult. In the settings we care about here, the issue is not existence so much as actually finding one.</p>
<p>The point of a HSOP is that it gives us subring of the invariant ring that is “simple” to understand, and we can then study the structure of the full invariant ring as a module over this polynomial subring.</p>
<p>Also note that the HSOP isn’t unique necessarily.</p>
<p>To find an HSOP, we can use a greedy process to collect invariants. In practice, we look for invariants that appear algebraically independent and then do additional checks:</p>
<div id="913a5146" class="cell" data-execution_count="48">
<div class="sourceCode cell-code" id="cb49" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb49-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.action <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> find_hsop, find_secondaries, primary_secondary</span>
<span id="cb49-2"></span>
<span id="cb49-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># S_3 on C^3</span></span>
<span id="cb49-4">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetric(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb49-5">action <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> invariant_theory(G, polynomial_ring(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb49-6">hsop <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> find_hsop(action, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb49-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [x0+x1+x2, x0^2+x1^2+x2^2, x0^3+x1^3+x2^3]</span></span></code></pre></div>
</div>
<p>As an example, we can look at <img src="https://latex.codecogs.com/png.latex?S_3">. The invariant ring is polynomial on three generators, and we can take the three power sums <img src="https://latex.codecogs.com/png.latex?p_1%20=%20x_0%20+%20x_1%20+%20x_2">, <img src="https://latex.codecogs.com/png.latex?p_2%20=%20x_0%5E2%20+%20x_1%5E2%20+%20x_2%5E2">, <img src="https://latex.codecogs.com/png.latex?p_3%20=%20x_0%5E3%20+%20x_1%5E3%20+%20x_2%5E3">. So they form an HSOP.</p>
</section>
<section id="cohenmacaulay" class="level2">
<h2 class="anchored" data-anchor-id="cohenmacaulay">Cohen–Macaulay</h2>
<p>Given we have an HSOP <img src="https://latex.codecogs.com/png.latex?%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d">, and we know that <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"> is finitely generated as a module over <img src="https://latex.codecogs.com/png.latex?k%5B%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%5D">, we can ask about the structure of this module. In the best cases, the invariant ring is a free module over the polynomial subring generated by the HSOP, which means it can be written uniquely as a direct sum of copies of the polynomial subring:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ak%5BV%5D%5EG%20=%20%5Cbigoplus_%7Bj=1%7D%5E%7Bm%7D%20%5Ceta_j%20%5Ccdot%20k%5B%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d%5D%0A"></p>
<p>so every element can be written</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af%20=%20%5Csum_%7Bj=1%7D%5E%7Bm%7D%20%5Ceta_j%20f_j(%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d)%0A"></p>
<p>This is the Cohen-Macaulay property. It’s not necessarily true for all invariant rings. We could instead have nontrivial relations among the <img src="https://latex.codecogs.com/png.latex?%5Ceta_j"> such as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d)%20%5Ceta_1%20+%20q(%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d)%20%5Ceta_2%20=%200%0A"></p>
<div id="thm-hochster-roberts" class="theorem">
<p><span class="theorem-title"><strong>Theorem 2</strong></span> Let <img src="https://latex.codecogs.com/png.latex?G"> be a reductive group acting on a finite-dimensional vector space <img src="https://latex.codecogs.com/png.latex?V"> over a field of characteristic zero. Then the invariant ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG"> is Cohen-Macaulay.</p>
</div>
<p><em>Proof</em>. See Hochster and Roberts.<sup>4</sup> <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
</section>
<section id="computing-primary-and-secondary-invariants-1" class="level2">
<h2 class="anchored" data-anchor-id="computing-primary-and-secondary-invariants-1">Computing Primary and Secondary Invariants</h2>
<p>When the Cohen-Macaulay property holds, we call the HSOP elements <img src="https://latex.codecogs.com/png.latex?%5Ctheta_1,%20%5Cldots,%20%5Ctheta_d"> primary invariants, and the module generators <img src="https://latex.codecogs.com/png.latex?%5Ceta_1,%20%5Cldots,%20%5Ceta_m"> secondary invariants. The primary invariants generate a polynomial subring, and the secondary invariants fill in the remainder.</p>
<p>Computationally, this compresses the problem. Instead of searching for all generators of <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG">, we can first find an HSOP and then loop through the degrees to find secondary invariants using the Hilbert series (which tells us how many per degree).</p>
<p>For <img src="https://latex.codecogs.com/png.latex?S_3"> on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3">, the invariant ring is polynomial, so the only secondary invariant is <img src="https://latex.codecogs.com/png.latex?1">:</p>
<div id="f1856f53" class="cell" data-execution_count="49">
<div class="sourceCode cell-code" id="cb50" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb50-1">primaries, secondaries <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> primary_secondary(action, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb50-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># primaries: [x0+x1+x2, x0^2+x1^2+x2^2, x0^3+x1^3+x2^3]</span></span>
<span id="cb50-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># secondaries: [1]</span></span>
<span id="cb50-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># k[V]^G = 1 · k[p1, p2, p3]  — free module of rank 1</span></span></code></pre></div>
</div>
<p>A more interesting example. Consider <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BZ%7D/2"> acting on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E2"> by <img src="https://latex.codecogs.com/png.latex?(x,%20y)%20%5Cmapsto%20(-x,%20-y)">. The invariant ring is <img src="https://latex.codecogs.com/png.latex?k%5Bx%5E2,%20xy,%20y%5E2%5D"> with relation <img src="https://latex.codecogs.com/png.latex?x%5E2%20y%5E2%20=%20(xy)%5E2">, so it is not a polynomial ring:</p>
<div id="5d6a06b3" class="cell" data-execution_count="50">
<div class="sourceCode cell-code" id="cb51" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb51-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.groups.finite <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> FiniteGroup</span>
<span id="cb51-2">G_z2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FiniteGroup([np.eye(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>), <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.eye(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)])</span>
<span id="cb51-3">action_z2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> invariant_theory(G_z2, polynomial_ring(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb51-4"></span>
<span id="cb51-5">primaries_z2, secondaries_z2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> primary_secondary(action_z2, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb51-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># primaries: [x0^2, x1^2]</span></span>
<span id="cb51-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># secondaries: [1, x0*x1]</span></span>
<span id="cb51-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># k[V]^G = 1 · k[x^2, y^2] (direct_sum) xy · k[x^2, y^2]  — free module of rank 2</span></span></code></pre></div>
</div>
<p>Here we have two secondary invariants, <img src="https://latex.codecogs.com/png.latex?1"> and <img src="https://latex.codecogs.com/png.latex?xy">. So we know every invariant can be written uniquely as <img src="https://latex.codecogs.com/png.latex?1%20%5Ccdot%20p(x%5E2,%20y%5E2)%20+%20xy%20%5Ccdot%20q(x%5E2,%20y%5E2)">. The generator <img src="https://latex.codecogs.com/png.latex?xy"> isn’t expressible as a polynomial in the primary invariants <img src="https://latex.codecogs.com/png.latex?x%5E2,%20y%5E2">, but the full ring is still a free module over <img src="https://latex.codecogs.com/png.latex?k%5Bx%5E2,%20y%5E2%5D">, as guaranteed by Hochster-Roberts.</p>
<p>We can go further and consider the degree-4 invariants. Under <img src="https://latex.codecogs.com/png.latex?(x,y)%20%5Cmapsto%20(-x,-y)">, a monomial <img src="https://latex.codecogs.com/png.latex?x%5Ea%20y%5Eb"> is invariant iff <img src="https://latex.codecogs.com/png.latex?a%20+%20b"> is even (i.e.&nbsp;the entire monomial is of even degree), so the degree-4 invariants are all the even-degree monomials in <img src="https://latex.codecogs.com/png.latex?x"> and <img src="https://latex.codecogs.com/png.latex?y">: <img src="https://latex.codecogs.com/png.latex?x%5E4,%20x%5E3y,%20x%5E2y%5E2,%20xy%5E3,%20y%5E4">. Each decomposes:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?x%5E4%20=%201%20%5Ccdot%20(x%5E2)%5E2"></li>
<li><img src="https://latex.codecogs.com/png.latex?x%5E3%20y%20=%20xy%20%5Ccdot%20x%5E2"></li>
<li><img src="https://latex.codecogs.com/png.latex?x%5E2%20y%5E2%20=%201%20%5Ccdot%20x%5E2%20y%5E2"></li>
<li><img src="https://latex.codecogs.com/png.latex?x%20y%5E3%20=%20xy%20%5Ccdot%20y%5E2"></li>
<li><img src="https://latex.codecogs.com/png.latex?y%5E4%20=%201%20%5Ccdot%20(y%5E2)%5E2"></li>
</ul>
<p>So every invariant can be written as either <img src="https://latex.codecogs.com/png.latex?1%20%5Ccdot%20k%5Bx%5E2,%20y%5E2%5D"> or <img src="https://latex.codecogs.com/png.latex?xy%20%5Ccdot%20k%5Bx%5E2,%20y%5E2%5D">.</p>
</section>
</section>
<section id="classical-groups-and-first-fundamental-theorems" class="level1">
<h1>Classical Groups and First Fundamental Theorems</h1>
<p>For finite groups, we computed invariants from scratch via the Reynolds operator, orbit sums, and the Molien series.</p>
<p>For classical groups like <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BO%7D(n)">, <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSL%7D(n)">, and <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSp%7D(2n)">, the First Fundamental Theorems gives us the generators. Since these have been derived in the past, we can just hard-code them.</p>
<section id="molien-weyl-formula" class="level2">
<h2 class="anchored" data-anchor-id="molien-weyl-formula">Molien-Weyl Formula</h2>
<p>Before moving on, how <em>would</em> we compute these?</p>
<p>We would need a Hilbert series for continuous groups. Luckily, the Molien formula generalizes. For a compact group <img src="https://latex.codecogs.com/png.latex?G"> acting on <img src="https://latex.codecogs.com/png.latex?V">, the Hilbert series is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t)%20=%20%5Cint_G%20%5Cfrac%7B1%7D%7B%5Cdet(I%20-%20tg)%7D%20%5C,%20d%5Cmu(g)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmu"> is the Haar measure. This is over an infinite group, but the Weyl integration formula reduces it to an integral over the “maximal torus” <img src="https://latex.codecogs.com/png.latex?T">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t)%20=%20%5Cfrac%7B1%7D%7B%7CW%7C%7D%20%5Cint_T%20%5Cfrac%7B%7C%5CDelta(g)%7C%5E2%7D%7B%5Cdet(I%20-%20tg)%7D%20%5C,%20dg%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?W"> is the (finite) “Weyl group” and <img src="https://latex.codecogs.com/png.latex?%5CDelta"> is the “Weyl denominator” (whatever those are). The torus integral becomes a Laurent polynomial residue computation.</p>
<p>The point is, computing the Hilbert series for a compact Lie group reduces to a torus computation plus a finite group average.</p>
<p>In practice, since we know the generators, we can just compute the Hilbert series by counting monomials in those generators.</p>
</section>
<section id="the-orthogonal-group-mathrmon" class="level2">
<h2 class="anchored" data-anchor-id="the-orthogonal-group-mathrmon">The Orthogonal Group <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BO%7D(n)"></h2>
<p>Consider <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BO%7D(n)"> acting diagonally on <img src="https://latex.codecogs.com/png.latex?k"> copies of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5En">. We represent this with <img src="https://latex.codecogs.com/png.latex?kn"> variables, organized as <img src="https://latex.codecogs.com/png.latex?k"> vectors <img src="https://latex.codecogs.com/png.latex?v_1,%20%5Cldots,%20v_k"> of length <img src="https://latex.codecogs.com/png.latex?n">. The First Fundamental Theorem says the invariant ring is generated by all inner products <img src="https://latex.codecogs.com/png.latex?%5Clangle%20v_i,%20v_j%20%5Crangle%20=%20%5Csum_%7Ba=0%7D%5E%7Bn-1%7D%20v_i%5Ea%20v_j%5Ea">.</p>
<p>There are <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7Bk+1%7D%7B2%7D"> generators (the inner products are symmetric: <img src="https://latex.codecogs.com/png.latex?%5Clangle%20v_i,%20v_j%20%5Crangle%20=%20%5Clangle%20v_j,%20v_i%20%5Crangle">), all of degree 2 in the original variables. Degree-<img src="https://latex.codecogs.com/png.latex?d"> invariants come from degree-<img src="https://latex.codecogs.com/png.latex?d/2"> polynomials in these generators (so only the even degrees have invariants).</p>
<div id="bd9320cc" class="cell" data-execution_count="51">
<div class="sourceCode cell-code" id="cb52" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb52-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.classical <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> orthogonal_action</span>
<span id="cb52-2"></span>
<span id="cb52-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># O(3) acting on 2 copies of C^3 (6 variables)</span></span>
<span id="cb52-4">action <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> orthogonal_action(n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb52-5"></span>
<span id="cb52-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Generators: &lt;v1,v1&gt;, &lt;v1,v2&gt;, &lt;v2,v2&gt;  — 3 inner products</span></span>
<span id="cb52-7">gens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> action.invariants_of_degree(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb52-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [x0^2+x1^2+x2^2, x0*x3+x1*x4+x2*x5, x3^2+x4^2+x5^2]</span></span></code></pre></div>
</div>
<p>As an example, label the 6 variables as <img src="https://latex.codecogs.com/png.latex?v_1%20=%20(x_0,%20x_1,%20x_2)"> and <img src="https://latex.codecogs.com/png.latex?v_2%20=%20(x_3,%20x_4,%20x_5)">. The three generators are:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5Clangle%20v_1,%20v_1%20%5Crangle%20&amp;=%20x_0%5E2%20+%20x_1%5E2%20+%20x_2%5E2%20%5C%5C%0A%5Clangle%20v_1,%20v_2%20%5Crangle%20&amp;=%20x_0%20x_3%20+%20x_1%20x_4%20+%20x_2%20x_5%20%5C%5C%0A%5Clangle%20v_2,%20v_2%20%5Crangle%20&amp;=%20x_3%5E2%20+%20x_4%5E2%20+%20x_5%5E2%0A%5Cend%7Baligned%7D%0A"></p>
<p>Every <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BO%7D(3)">-invariant polynomial in the 6 variables is a polynomial in these three inner products.</p>
<p>If we extended to degree-4 invariants, we would have the 6 products of pairs:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A&amp;%5Clangle%20v_1,%20v_1%20%5Crangle%5E2,%20%5Cquad%20%5Clangle%20v_1,%20v_1%20%5Crangle%20%5Clangle%20v_1,%20v_2%20%5Crangle,%20%5Cquad%20%5Clangle%20v_1,%20v_1%20%5Crangle%20%5Clangle%20v_2,%20v_2%20%5Crangle,%20%5C%5C%0A&amp;%5Clangle%20v_1,%20v_2%20%5Crangle%5E2,%20%5Cquad%20%5Clangle%20v_1,%20v_2%20%5Crangle%20%5Clangle%20v_2,%20v_2%20%5Crangle,%20%5Cquad%20%5Clangle%20v_2,%20v_2%20%5Crangle%5E2%0A%5Cend%7Baligned%7D%0A"></p>
<p>Intuitively, this makes sense, as <img src="https://latex.codecogs.com/png.latex?O(3)"> preserves angles and lengths. The only way to get an invariant is to combine the inner products such that the orientation of the vectors is immaterial.</p>
</section>
<section id="mathrmsln-and-bracket-invariants" class="level2">
<h2 class="anchored" data-anchor-id="mathrmsln-and-bracket-invariants"><img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSL%7D(n)"> and Bracket Invariants</h2>
<p>Now consider <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSL%7D(n)"> acting on <img src="https://latex.codecogs.com/png.latex?k"> copies of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5En">. The generators are all <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> minors, which are determinants formed by choosing <img src="https://latex.codecogs.com/png.latex?n"> of the <img src="https://latex.codecogs.com/png.latex?k"> vectors. For <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSL%7D(2)">, these are the brackets <img src="https://latex.codecogs.com/png.latex?%5Bij%5D%20=%20x_i%20y_j%20-%20y_i%20x_j"> (we showed this in the previous post).</p>
<p>There are <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7Bk%7D%7Bn%7D"> generators, each of degree <img src="https://latex.codecogs.com/png.latex?n"> in the original variables. When <img src="https://latex.codecogs.com/png.latex?k%20%3C%20n">, there are no invariants at all (since you can’t form an <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> determinant from less than <img src="https://latex.codecogs.com/png.latex?n"> vectors).</p>
<div id="9c4cb656" class="cell" data-execution_count="52">
<div class="sourceCode cell-code" id="cb53" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb53-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.classical <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sl_action</span>
<span id="cb53-2"></span>
<span id="cb53-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># SL(2) acting on 3 copies of C^2 (6 variables)</span></span>
<span id="cb53-4">action <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sl_action(n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb53-5"></span>
<span id="cb53-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Generators: [12], [13], [23]  — 3 brackets</span></span>
<span id="cb53-7">gens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> action.invariants_of_degree(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb53-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [x0*x3 - x1*x2, x0*x5 - x1*x4, x2*x5 - x3*x4]</span></span></code></pre></div>
</div>
<p>As an example, we have three vectors in <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E2">: <img src="https://latex.codecogs.com/png.latex?v_1%20=%20(x_0,%20x_1)">, <img src="https://latex.codecogs.com/png.latex?v_2%20=%20(x_2,%20x_3)">, <img src="https://latex.codecogs.com/png.latex?v_3%20=%20(x_4,%20x_5)">. Each bracket is a <img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%202"> determinant:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5B12%5D%20&amp;=%20%5Cdet%5Cbegin%7Bpmatrix%7D%20x_0%20&amp;%20x_2%20%5C%5C%20x_1%20&amp;%20x_3%20%5Cend%7Bpmatrix%7D%20=%20x_0%20x_3%20-%20x_1%20x_2%20%5C%5C%5B4pt%5D%0A%5B13%5D%20&amp;=%20%5Cdet%5Cbegin%7Bpmatrix%7D%20x_0%20&amp;%20x_4%20%5C%5C%20x_1%20&amp;%20x_5%20%5Cend%7Bpmatrix%7D%20=%20x_0%20x_5%20-%20x_1%20x_4%20%5C%5C%5B4pt%5D%0A%5B23%5D%20&amp;=%20%5Cdet%5Cbegin%7Bpmatrix%7D%20x_2%20&amp;%20x_4%20%5C%5C%20x_3%20&amp;%20x_5%20%5Cend%7Bpmatrix%7D%20=%20x_2%20x_5%20-%20x_3%20x_4%0A%5Cend%7Baligned%7D%0A"></p>
<p>These are “signed areas” of the parallelograms spanned by pairs of vectors.</p>
<p>Intuitively,<img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSL%7D(2)"> preserves areas (since it has determinant 1), so the signed areas are invariant.</p>
<p>With 3 vectors, there are <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7B3%7D%7B2%7D%20=%203"> brackets and no relations among them, meaning that the invariant ring is freely generated.</p>
<p>The Second Fundamental Theorem (also in the previous post) gives us the relations between them, called the Plücker relations. For <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSL%7D(2)"> on 4 vectors, the single relation is <img src="https://latex.codecogs.com/png.latex?%5B12%5D%5B34%5D%20-%20%5B13%5D%5B24%5D%20+%20%5B14%5D%5B23%5D%20=%200">. We can now verify this in code:</p>
<div id="f3100463" class="cell" data-execution_count="53">
<div class="sourceCode cell-code" id="cb54" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb54-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.groebner <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> compute_relations</span>
<span id="cb54-2"></span>
<span id="cb54-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># SL(2) on 4 copies of C^2: 6 brackets, 1 Plücker relation</span></span>
<span id="cb54-4">action4 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sl_action(n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb54-5">gens4 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> action4.invariants_of_degree(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb54-6">relations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_relations(gens4, n_vars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb54-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># One relation: y0*y5 - y1*y4 + y2*y3  (the Plücker relation)</span></span></code></pre></div>
</div>
<p>With 4 vectors we would get <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7B4%7D%7B2%7D%20=%206"> brackets: <img src="https://latex.codecogs.com/png.latex?%5B12%5D">, <img src="https://latex.codecogs.com/png.latex?%5B13%5D">, <img src="https://latex.codecogs.com/png.latex?%5B14%5D">, <img src="https://latex.codecogs.com/png.latex?%5B23%5D">, <img src="https://latex.codecogs.com/png.latex?%5B24%5D">, <img src="https://latex.codecogs.com/png.latex?%5B34%5D">. However, these are not algebraically independent.</p>
<p>Expanding the Plücker relation by hand:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5B12%5D%5B34%5D%20-%20%5B13%5D%5B24%5D%20+%20%5B14%5D%5B23%5D%20=%20(x_0%20x_3%20-%20x_1%20x_2)(x_4%20x_7%20-%20x_5%20x_6)%20-%20(x_0%20x_5%20-%20x_1%20x_4)(x_2%20x_7%20-%20x_3%20x_6)%20+%20(x_0%20x_7%20-%20x_1%20x_6)(x_2%20x_5%20-%20x_3%20x_4)%0A"></p>
<p>Everything cancels out. The invariant ring is <img src="https://latex.codecogs.com/png.latex?k%5Cbigl%5B%5B12%5D,%5B13%5D,%5B14%5D,%5B23%5D,%5B24%5D,%5B34%5D%5Cbigr%5D%20/%20(%5B12%5D%5B34%5D%20-%20%5B13%5D%5B24%5D%20+%20%5B14%5D%5B23%5D)">. The Gröbner computation in <code>compute_relations</code> rediscovers exactly this.</p>
</section>
<section id="symplectic-and-other-classical-groups" class="level2">
<h2 class="anchored" data-anchor-id="symplectic-and-other-classical-groups">Symplectic and Other Classical Groups</h2>
<p>The same pattern works for <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BSp%7D(2n)"> acting on <img src="https://latex.codecogs.com/png.latex?k"> copies of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E%7B2n%7D">, where the generators are the symplectic pairings <img src="https://latex.codecogs.com/png.latex?%5Comega(v_i,%20v_j)%20=%20%5Csum_%7Ba=0%7D%5E%7Bn-1%7D%20(v_i%5Ea%20v_j%5E%7Bn+a%7D%20-%20v_i%5E%7Bn+a%7D%20v_j%5Ea)">. This is essentially identical to the orthogonal case, but with an antisymmetric bilinear form rather than a symmetric one.</p>
</section>
</section>
<section id="further-reductive-groups" class="level1">
<h1>Further Reductive Groups</h1>
<p>There are additional reductive groups beyond the ones we looked at in the last section. We explored some of the theory in the previous post.</p>
<p>Reductive groups often require idiosyncratic analysis via representation theory and First Fundamental Theorems. However, many important reductive groups have known FFTs that could be hard-coded in the same way as the classical groups above.</p>
<p>The most salient example is <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BGL%7D(n)">, which acts on matrices by conjugation. It’s generators are traces of products. So we would compute <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Btr%7D(A)">, <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Btr%7D(A%5E2)">, <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Btr%7D(AB)">, <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Btr%7D(ABA)">, etc. The implementation pattern would be the same as the other groups.</p>
<p>These are out of scope for now. In the future I may come back and add some of these.</p>
</section>
<section id="orbits-quotients-and-separation" class="level1">
<h1>Orbits, Quotients, and Separation</h1>
<p>Thus far, the primary question under consideration has been to understand the generators of the invariant ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG">. However, another key (related) question is to understand the orbits of the group action on <img src="https://latex.codecogs.com/png.latex?V">. The invariant ring encodes the geometry of the quotient space <img src="https://latex.codecogs.com/png.latex?V/%5C!%5C!/G">. The quotient map <img src="https://latex.codecogs.com/png.latex?%5Cpi:%20V%20%5Cto%20V/%5C!%5C!/G"> sends each point <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20V"> to the values of the invariants at that point, effectively classifying points according to their orbit closures.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi(v)%20=%20(f_1(v),%20%5Cldots,%20f_s(v))%0A"></p>
<p>Two points have the same image under <img src="https://latex.codecogs.com/png.latex?%5Cpi"> if and only if their orbit closures intersect. For reductive groups (which includes all finite groups and tori), closed orbits are separated. This means that if <img src="https://latex.codecogs.com/png.latex?%5Cpi(v)%20=%20%5Cpi(w)">, both orbits are closed, the orbits are equal.</p>
<p>So invariants can be used to classify which points are equivalent under some symmetry.</p>
<section id="the-quotient-map" class="level2">
<h2 class="anchored" data-anchor-id="the-quotient-map">The Quotient Map</h2>
<p>Suppose we have the generators of the invariant ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG">. Then the quotient map <img src="https://latex.codecogs.com/png.latex?%5Cpi:%20V%20%5Cto%20V/%5C!%5C!/G"> is given by evaluating these generators at each point. That is, if <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_s"> are generators of <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG">, then we can use them to define a map:</p>
<div id="da18e541" class="cell" data-execution_count="54">
<div class="sourceCode cell-code" id="cb55" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb55-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.orbits <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> quotient_map, same_image</span>
<span id="cb55-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> poly</span>
<span id="cb55-3"></span>
<span id="cb55-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># S_3 generators: power sums p1, p2, p3</span></span>
<span id="cb55-5">p1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(poly.add(poly.var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), poly.var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)), poly.var(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb55-6">p2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(poly.add(poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)), poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))), poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)))</span>
<span id="cb55-7">p3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.add(poly.add(poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)), poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))), poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)))</span>
<span id="cb55-8">gens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [p1, p2, p3]</span>
<span id="cb55-9"></span>
<span id="cb55-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Same orbit =&gt; same image</span></span>
<span id="cb55-11">quotient_map(gens, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [6, 14, 36]</span></span>
<span id="cb55-12">quotient_map(gens, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [6, 14, 36]</span></span>
<span id="cb55-13">same_image(gens, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True</span></span>
<span id="cb55-14"></span>
<span id="cb55-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Different orbits =&gt; different image</span></span>
<span id="cb55-16">same_image(gens, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># False</span></span></code></pre></div>
</div>
<p>In this example, we are looking at the action of <img src="https://latex.codecogs.com/png.latex?S_3"> on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3"> by permuting coordinates. The generators of the invariant ring are the power sums <img src="https://latex.codecogs.com/png.latex?p_1,%20p_2,%20p_3">, which we established earlier as <img src="https://latex.codecogs.com/png.latex?p_1%20=%20x_0%20+%20x_1%20+%20x_2">, <img src="https://latex.codecogs.com/png.latex?p_2%20=%20x_0%5E2%20+%20x_1%5E2%20+%20x_2%5E2">, and <img src="https://latex.codecogs.com/png.latex?p_3%20=%20x_0%5E3%20+%20x_1%5E3%20+%20x_2%5E3">.</p>
<p>The quotient map evaluates these invariants at a point. For points in the same orbit (like <img src="https://latex.codecogs.com/png.latex?(1,%202,%203)"> and <img src="https://latex.codecogs.com/png.latex?(3,%201,%202)">), we get the same image under the quotient map. For points in different orbits (like <img src="https://latex.codecogs.com/png.latex?(1,%202,%203)"> and <img src="https://latex.codecogs.com/png.latex?(1,%201,%204)">), we get different images.</p>
<p>How do we interpret these images?</p>
<p>For <img src="https://latex.codecogs.com/png.latex?(1,%202,%203)">, we have: - <img src="https://latex.codecogs.com/png.latex?p_1(1,%202,%203)%20=%201%20+%202%20+%203%20=%206"> - <img src="https://latex.codecogs.com/png.latex?p_2(1,%202,%203)%20=%201%5E2%20+%202%5E2%20+%203%5E2%20=%2014"> - <img src="https://latex.codecogs.com/png.latex?p_3(1,%202,%203)%20=%201%5E3%20+%202%5E3%20+%203%5E3%20=%2036"></p>
<p>However, for <img src="https://latex.codecogs.com/png.latex?(1,%201,%204)">, we have: - <img src="https://latex.codecogs.com/png.latex?p_1(1,%201,%204)%20=%201%20+%201%20+%204%20=%206"> - <img src="https://latex.codecogs.com/png.latex?p_2(1,%201,%204)%20=%201%5E2%20+%201%5E2%20+%204%5E2%20=%2018"> - <img src="https://latex.codecogs.com/png.latex?p_3(1,%201,%204)%20=%201%5E3%20+%201%5E3%20+%204%5E3%20=%2066"></p>
<p>So these points do not lie in the same orbit, as they have different images under the quotient map. The invariants <img src="https://latex.codecogs.com/png.latex?p_1,%20p_2,%20p_3"> are able to distinguish these orbits.</p>
<p>And, as we can tell by inspection, there is no way to permute the coordinates of <img src="https://latex.codecogs.com/png.latex?(1,%202,%203)"> to get <img src="https://latex.codecogs.com/png.latex?(1,%201,%204)">, confirming that they are indeed in different orbits.</p>
</section>
<section id="orbit-separation" class="level2">
<h2 class="anchored" data-anchor-id="orbit-separation">Orbit Separation</h2>
<p>In the finite-group case, when two points are in different orbits, we can find a specific invariant that witnesses this:</p>
<div id="77e9cd08" class="cell" data-execution_count="55">
<div class="sourceCode cell-code" id="cb56" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb56-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.orbits <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> find_separator</span>
<span id="cb56-2"></span>
<span id="cb56-3">sep <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> find_separator(gens, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb56-4">poly.show(sep)</span>
<span id="cb56-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 'x0^2 + x1^2 + x2^2'</span></span>
<span id="cb56-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># p2(1,2,3) = 14, p2(1,1,4) = 18</span></span></code></pre></div>
</div>
<p>For reductive groups, if two orbits are closed and distinct, some polynomial invariant distinguishes them. This is a useful constructive theorem, as it allows us to find explicit invariants that separate orbits and thereby classify objects up to symmetry.</p>
</section>
<section id="null-cones" class="level2">
<h2 class="anchored" data-anchor-id="null-cones">Null Cones</h2>
<p>The null cone <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BN%7D(V)"> is the fiber <img src="https://latex.codecogs.com/png.latex?%5Cpi%5E%7B-1%7D(%5Cpi(0))">, which is the set of all points that invariants cannot distinguish from the origin.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BN%7D(V)%20=%20%5C%7B%20v%20%5Cin%20V%20:%20f(v)%20=%200%20%5Ctext%7B%20for%20all%20homogeneous%20%7D%20f%20%5Cin%20k%5BV%5D%5EG%20%5Ctext%7B%20with%20%7D%20%5Cdeg%20f%20%3E%200%20%5C%7D%0A"></p>
<p>This is important as all the points in the null cone have orbit closures that contain the origin, so they all collapse to the same point in the quotient.</p>
<p>Testing null cone membership is just evaluating the generators:</p>
<div id="f9124d09" class="cell" data-execution_count="56">
<div class="sourceCode cell-code" id="cb57" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb57-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.orbits <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> in_null_cone</span>
<span id="cb57-2"></span>
<span id="cb57-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># C* on C^2, weights [1, -1]. Invariant: x0*x1</span></span>
<span id="cb57-4">gen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly.mono((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb57-5"></span>
<span id="cb57-6">in_null_cone([gen], (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True — origin</span></span>
<span id="cb57-7">in_null_cone([gen], (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True — coordinate axis</span></span>
<span id="cb57-8">in_null_cone([gen], (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True — coordinate axis</span></span>
<span id="cb57-9">in_null_cone([gen], (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># False — generic point</span></span>
<span id="cb57-10">in_null_cone([gen], (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># False — generic point</span></span></code></pre></div>
</div>
<p>For this example, the null cone is <img src="https://latex.codecogs.com/png.latex?%5C%7Bx_0%20x_1%20=%200%5C%7D">, the union of the two coordinate axes. Geometrically, these are the orbits of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E*"> that degenerate: <img src="https://latex.codecogs.com/png.latex?t%20%5Ccdot%20(v_0,%200)%20=%20(tv_0,%200)">, which approaches the origin as <img src="https://latex.codecogs.com/png.latex?t%20%5Cto%200">. The generic orbits with <img src="https://latex.codecogs.com/png.latex?x_0%20x_1%20%5Cneq%200"> are closed and do not contain the origin in their closure, so they are not in the null cone.</p>
</section>
<section id="separating-invariants" class="level2">
<h2 class="anchored" data-anchor-id="separating-invariants">Separating Invariants</h2>
<p>If we have a full generating set for the invariant ring, then we can use it to separate orbits. However, it may be difficult to produce the full set. In practice, we might only need some of the generators to separate orbits. The hope is that the separating set is much smaller than the full generating set.</p>
<p>We can find a minimal separating subset by greedy set cover (there’s probably faster algorithms, especially if we have some structure or are willing to do some random sampling):</p>
<div id="bb6fd3a9" class="cell" data-execution_count="57">
<div class="sourceCode cell-code" id="cb58" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb58-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> invariants.orbits <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> is_separating, minimal_separating_subset</span>
<span id="cb58-2"></span>
<span id="cb58-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># S_3 generators</span></span>
<span id="cb58-4">gens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [p1, p2, p3]  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># three power sums</span></span>
<span id="cb58-5"></span>
<span id="cb58-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Test pairs, test points in different S_3 orbits</span></span>
<span id="cb58-7">pairs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb58-8">    ((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same sum, different orbits</span></span>
<span id="cb58-9">    ((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same sum, different orbits</span></span>
<span id="cb58-10">    ((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># different sum, easy</span></span>
<span id="cb58-11">]</span>
<span id="cb58-12"></span>
<span id="cb58-13">is_separating(gens, pairs)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True, so full set works</span></span>
<span id="cb58-14"></span>
<span id="cb58-15">minimal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> minimal_separating_subset(gens, pairs)</span>
<span id="cb58-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [x0^2 + x1^2 + x2^2] - p2 alone separates all three pairs!</span></span></code></pre></div>
</div>
<p>For <img src="https://latex.codecogs.com/png.latex?S_3"> acting on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3">, the power sum <img src="https://latex.codecogs.com/png.latex?p_2%20=%20x_0%5E2%20+%20x_1%5E2%20+%20x_2%5E2"> separates these test pairs.</p>
<p>Note that <img src="https://latex.codecogs.com/png.latex?p_2"> doesn’t separate all orbits (e.g.&nbsp;<img src="https://latex.codecogs.com/png.latex?(1,%202,%200)"> and <img src="https://latex.codecogs.com/png.latex?(0,%201,%202)"> have the same <img src="https://latex.codecogs.com/png.latex?p_2"> value).</p>
</section>
</section>
<section id="scaling" class="level1">
<h1>Scaling</h1>
<p>Some quick notes here on scaling properties of the algorithms we’ve discussed, which have some practical issues, and other techniques used in practice that I’ve omitted.</p>
<section id="degree-explosion" class="level2">
<h2 class="anchored" data-anchor-id="degree-explosion">Degree Explosion</h2>
<p>The fundamental bottleneck in many cases is degree.</p>
<p>For a finite group <img src="https://latex.codecogs.com/png.latex?G"> acting on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5En">, Noether’s bound guarantees generators appear by degree <img src="https://latex.codecogs.com/png.latex?%7CG%7C">, but the number of monomials in degree <img src="https://latex.codecogs.com/png.latex?d"> is <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7Bn+d-1%7D%7Bd%7D">, which grows polynomially in <img src="https://latex.codecogs.com/png.latex?d"> for fixed <img src="https://latex.codecogs.com/png.latex?n">.</p>
<p>Even for <img src="https://latex.codecogs.com/png.latex?S_5"> (order 120) acting on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E5">, degree 120 has <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7B124%7D%7B120%7D%20%5Capprox%2010%5E7"> monomials. It’s not practical to use an algorithm like Buchberger’s algorithm on ideals with generators of this degree.</p>
</section>
<section id="rational-invariants" class="level2">
<h2 class="anchored" data-anchor-id="rational-invariants">Rational Invariants</h2>
<p>A rational invariant is a ratio <img src="https://latex.codecogs.com/png.latex?f/h"> of polynomials that is invariant under the group action:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bf(g%20%5Ccdot%20v)%7D%7Bh(g%20%5Ccdot%20v)%7D%20=%20%5Cfrac%7Bf(v)%7D%7Bh(v)%7D%20%5Ctext%7B%20for%20all%20%7D%20g%20%5Cin%20G,%20v%20%5Cin%20V%0A"></p>
<p>Sometimes, when the polynomial invariant ring requires generators of very high degree or has complicated relations, rational invariants require fewer generators with lower degree and simpler relations. The tradeoff is that there could be poles, so we have to work on limited domains. This apparently comes up in non-reductive groups, where the polynomial invariant ring may not be finitely generated but the rational invariant field can be.</p>
<p>I didn’t investigate this too heavily.</p>
</section>
<section id="other-techniques" class="level2">
<h2 class="anchored" data-anchor-id="other-techniques">Other Techniques</h2>
<p>To help scale the library or extend it to new situations, there are a combination of different techniques.</p>
<p>We saw some of these earlier. For example, we saw:</p>
<ul>
<li>the Molien formula trick (where we compute the Hilbert series and get a roadmap for how many invariants to expect in each degree)</li>
<li>using orbit sums instead of the Reynolds operator to avoid the <img src="https://latex.codecogs.com/png.latex?1/%7CG%7C"> normalization and keep coefficients as integers</li>
<li>using separating invariants instead of full generating sets to reduce the number of invariants we need to find</li>
<li>Noether’s bound to limit the search to a finite degree</li>
<li>for tori, reducing to a combinatorial problem of finding integer solutions to linear equations (the weight decomposition), which is more efficient than Gröbner basis computations</li>
</ul>
<p>Some techniques we haven’t implemented but are commonly used in practice include:</p>
<ul>
<li>using representation theory to decompose the polynomial ring into irreducible representations and extract invariants from the trivial summands, which can be more efficient than brute-force Gröbner basis computations.</li>
<li>for compact Lie groups, using the Molien-Weyl formula to reduce the problem to an integral over the maximal torus, which can be computed using our existing Torus machinery</li>
<li>SAGBI bases, a variant of Gröbner bases designed for subalgebras rather than ideals. Where Buchberger’s algorithm answers “is this polynomial in the ideal?”, SAGBI answers “is this polynomial expressible in terms of known generators?”</li>
<li>Derksen’s algorithm, a general-purpose method that works for any group given by matrix generators, without needing to know anything about the group’s structure. It reduces invariant computation to a single (expensive) Gröbner basis calculation.</li>
</ul>
<p>Some of these techniques are in Derksen and Kemper’s book, and some are in the research literature. This post is too long, but I may come back to them.</p>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>In this post and the last post we have covered the (classical) theory of invariants and some of the computational tasks that arise in invariant theory. We have also implemented some code to perform these computations in specific cases.</p>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>Claude (Anthropic) assisted with code and the initial draft, working from my notes and the companion code repository. ChatGPT (OpenAI) provided a mathematical review pass. I edited the final text and verified the mathematical content.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>According to <a href="https://mathoverflow.net/questions/13526/geometric-interpretation-of-trace">this</a> MathOverflow answer by Terry Tao, the projection property <img src="https://latex.codecogs.com/png.latex?R%5E2%20=%20R"> implies that the eigenvalues of <img src="https://latex.codecogs.com/png.latex?R"> are either 0 or 1. The trace, which is the sum of the eigenvalues, counts how many 1’s there are, which is exactly the dimension of the image of <img src="https://latex.codecogs.com/png.latex?R">. This is enough to unique determine the trace, since the trace is linear and the projection property constrains the eigenvalues to be 0 or 1. So the trace of <img src="https://latex.codecogs.com/png.latex?R"> on <img src="https://latex.codecogs.com/png.latex?k%5BV%5D_d"> is exactly the dimension of the invariant subspace, which is what we want to compute. Apparently this comes up in “noncommutative probability.” Cool.↩︎</p></li>
<li id="fn2"><p>This generating function is a partition function in the sense of statistical mechanics. Recall that the partition function <img src="https://latex.codecogs.com/png.latex?Z%20=%20%5Csum_%7B%5Ctext%7Bstates%7D%7D%20e%5E%7B-%5Cbeta%20E%7D"> counts states weighted by energy. Here, <img src="https://latex.codecogs.com/png.latex?t"> plays the role of the Boltzmann weight <img src="https://latex.codecogs.com/png.latex?e%5E%7B-%5Cbeta%7D">, the “energy” of a monomial is its degree, and each factor <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7B1-%5Clambda_i%20t%7D%20=%201%20+%20%5Clambda_i%20t%20+%20%5Clambda_i%5E2%20t%5E2%20+%20%5Ccdots"> counts the states contributed by one variable (one for each power). Multiplying <img src="https://latex.codecogs.com/png.latex?n"> factors counts all monomials in <img src="https://latex.codecogs.com/png.latex?n"> variables by degree. After averaging over the group, <img src="https://latex.codecogs.com/png.latex?%5Cdim(k%5BV%5D%5EG_d)"> is the number of independent symmetry-invariant observables at degree <img src="https://latex.codecogs.com/png.latex?d">: the Molien formula counts only the “physical” quantities that are unchanged by the symmetry.↩︎</p></li>
<li id="fn3"><p>The Molien formula is fundamentally a residue computation. Extracting the <img src="https://latex.codecogs.com/png.latex?d">-th coefficient of <img src="https://latex.codecogs.com/png.latex?H(t)"> is <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7B2%5Cpi%20i%7D%5Coint%20%5Cfrac%7BH(t)%7D%7Bt%5E%7Bd+1%7D%7D%20dt">. In practice, this means we can compute Molien coefficients in closed form via partial fractions: decompose <img src="https://latex.codecogs.com/png.latex?H(t)%20=%20%5Csum%20%5Cfrac%7Bc%7D%7B(1-%5Calpha%20t)%5Ek%7D">, and each term contributes <img src="https://latex.codecogs.com/png.latex?c%5Cbinom%7Bd+k-1%7D%7Bk-1%7D%5Calpha%5Ed"> to the <img src="https://latex.codecogs.com/png.latex?d">-th coefficient. For compact Lie groups, the Molien formula generalizes to the Molien-Weyl integral over the maximal torus, which is a multivariate residue computation in the torus coordinates.↩︎</p></li>
<li id="fn4"><p>M. Hochster and J. L. Roberts, <a href="https://doi.org/10.1016/0001-8708(74)90067-X" class="external" target="_blank">“Rings of invariants of reductive groups acting on regular rings are Cohen-Macaulay,”</a> <em>Advances in Mathematics</em> <strong>13</strong> (1974), 115–175.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Symmetry and Structure</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/symmetry/posts/computational_invariant_theory/</guid>
  <pubDate>Tue, 17 Mar 2026 04:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/symmetry/posts/computational_invariant_theory/arithmetic-composition-1929.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Survey of Classical Invariant Theory</title>
  <link>https://demonstrandom.com/symmetry/posts/invariant_theory/</link>
  <description><![CDATA[ 





<p><a href="https://en.wikipedia.org/wiki/The_Horse_in_Motion"><img src="https://demonstrandom.com/symmetry/posts/invariant_theory/muybridge_horse_in_motion.jpg" class="img-fluid" style="width:75.0%" alt="Eadweard Muybridge. *The Horse in Motion*. (1878)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>Invariant theory is the study of how symmetries constrain the structure of mathematical objects (similar to <a href="../../../game_theory/posts/noether_geometric_controls/index.html">Noether’s theorem</a>). In this post, I will give a brief introduction to invariant theory and its applications.</p>
<p>Invariant theory is a vast field. I’m pulling mainly from the book <em>Invariant Theory</em> by Peter Olver (but with changes in order and notation), but will also thread in and ultimately work with computational approaches, such as those described in the book <em>Computational Invariant Theory</em> by Harm Derksen and Gregor Kemper. Throughout the post, I’ll use the running example of binary forms (homogeneous polynomials in two variables) and the action of <img src="https://latex.codecogs.com/png.latex?GL(2)"> on them, which is the classical setting for invariant theory<sup>1</sup>.</p>
<p>I also use make use of Claude and GPT where appropriate (although I have personally reviewed all the outputs). These are my notes, not a textbook or peer-reviewed paper. As always, be cautious of mistakes in my exposition, check them against the sources, and <a href="../../../contact.html">let me know</a> if you find any errors.</p>
</section>
<section id="background" class="level1">
<h1>Background</h1>
<p>Invariant theory began with the study of polynomials and their geometric properties (those properties that do not depend on a particular choice of coordinates). For example, the multiplicity patterns of roots of a polynomial are invariant under changes of coordinates. The discriminant of a polynomial is an invariant that tells us whether the roots are distinct or not. The coefficients of a polynomial are not invariant, but they transform in a specific way under changes of coordinates. If you have a systematic way to determine the invariants of a polynomial, you can classify and understand its geometric properties without reference to a particular coordinate system.</p>
</section>
<section id="homogeneous-polynomials" class="level1">
<h1>Homogeneous Polynomials</h1>
<p>We start by considering homogeneous polynomials (with coefficients drawn from a field <img src="https://latex.codecogs.com/png.latex?k"> of characteristic zero), also called “forms”. A binary form is an <img src="https://latex.codecogs.com/png.latex?n">-degree polynomial in two variables, defined as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AQ(x,y)%20=%20%5Csum_%7Bi=0%7D%5En%20%7Bn%20%5Cchoose%20i%7D%20a_i%20x%5E%7Bn-i%7D%20y%5Ei%0A"></p>
<p>We are interested in the geometric properties of these forms, by which we mean properties that do not depend on a particular choice of coordinates. Since we don’t care about the choice of coordinates, we should consider the transformations that can change those coordinates. In this case, linear changes of variables:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbar%7Bx%7D%20=%20a%20x%20+%20b%20y%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cbar%7By%7D%20=%20c%20x%20+%20d%20y%0A"> <img src="https://latex.codecogs.com/png.latex?%0Aad%20-%20bc%20%5Cneq%200%0A"></p>
<p>This should remind you of an (invertible) matrix transformation (with non-singular determinant), and indeed we can write this as: <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bbmatrix%7D%5Cbar%7Bx%7D%20%5C%5C%20%5Cbar%7By%7D%5Cend%7Bbmatrix%7D%20=%20%5Cbegin%7Bbmatrix%7Da%20&amp;%20b%20%20%5C%5C%20c%20&amp;%20d%5Cend%7Bbmatrix%7D%20%5Cbegin%7Bbmatrix%7Dx%20%5C%5C%20y%5Cend%7Bbmatrix%7D%0A"></p>
<p>So we can transform one binary form to another by:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbar%7BQ%7D(%5Cbar%7Bx%7D,%20%5Cbar%7By%7D)%20=%20Q(a%20x%20+%20b%20y,%20c%20x%20+%20d%20y)%20=%20%5Csum_%7Bi=0%7D%5En%20%7Bn%20%5Cchoose%20i%7D%20a_i%20(a%20x%20+%20b%20y)%5E%7Bn-i%7D%20(c%20x%20+%20d%20y)%5Ei%0A"></p>
<p>Olver gives an explicit formula for the coefficients of the transformed form, but we won’t need it here. The point is that we can transform one form to another by applying a linear transformation to the variables.</p>
<p>For each homogeneous polynomial (of fixed degree <img src="https://latex.codecogs.com/png.latex?n">), and given a scalar variable <img src="https://latex.codecogs.com/png.latex?p">, we can also associate a corresponding inhomogenous polynomial <sup>2</sup> in <img src="https://latex.codecogs.com/png.latex?p"> by substituting <img src="https://latex.codecogs.com/png.latex?x%20=%20py"> and <img src="https://latex.codecogs.com/png.latex?y%20=%201">. That is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AQ(x,%20y)%20=%20y%5En%0A"> <img src="https://latex.codecogs.com/png.latex?%0Aq(p)%20=%20Q(p,%201)%20=%201%0A"></p>
<p>Conversely, we can associate any inhomogenous polynomial in one variable, <img src="https://latex.codecogs.com/png.latex?Q(p)">, with a binary form by substituting <img src="https://latex.codecogs.com/png.latex?p%20=%20x/y">. That is, if we specify <img src="https://latex.codecogs.com/png.latex?n"> in advance, we have:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AQ(x,y)%20=%20y%5En%20Q(x/y)%20=%20%5Csum_%7Bi=0%7D%5En%20%7Bn%20%5Cchoose%20i%7D%20a_i%20x%5E%7Bn-i%7D%20y%5Ei%0A"></p>
<p>We now have two transformations, one on coordinates and one between homogeneous and inhomogeneous polynomials. We can combine these two transformations to get a transformation on inhomogeneous polynomials.</p>
<p>This leads us to view our transformation as a linear fractional transformation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbar%7Bp%7D%20=%20%5Cfrac%7Ba%20p%20+%20b%7D%7Bc%20p%20+%20d%7D%0A"></p>
<p>such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbar%7BQ%7D(p)%20=%20(c%20p%20+%20d)%5En%20Q(%5Cbar%7Bp%7D)%20=%20(c%20p%20+%20d)%5En%20Q%5Cleft(%5Cfrac%7Ba%20p%20+%20b%7D%7Bc%20p%20+%20d%7D%5Cright)%0A"></p>
</section>
<section id="invariants-and-covariants" class="level1">
<h1>Invariants and Covariants</h1>
<p>We are now ready to define an invariant for a binary form. An invariant is a function of the coefficients of the form that is unchanged by the linear transformation.</p>
<div id="def-invariant" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 1 (Invariant)</strong></span> An invariant of a binary form of degree <img src="https://latex.codecogs.com/png.latex?n"> is a function <img src="https://latex.codecogs.com/png.latex?I(a_0,%20a_1,%20%5Cldots,%20a_n)"> of the coefficients such that (up to some factor) it does not change under linear transformation.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AI(a_0,%20a_1,%20%5Cldots,%20a_n)%20=%20(ad%20-%20bc)%5Ek%20I(%5Cbar%7Ba%7D_0,%20%5Cbar%7Ba%7D_1,%20%5Cldots,%20%5Cbar%7Ba%7D_n)%0A"></p>
</div>
<p>The power <img src="https://latex.codecogs.com/png.latex?k"> is called the weight of the invariant. If <img src="https://latex.codecogs.com/png.latex?k=0">, then the invariant is called an absolute invariant, since it is completely unchanged by the transformation.</p>
<p>We can also define a covariant, which is a function of the coefficients and the variables that transforms in a specific way under the linear transformation.</p>
<div id="def-covariant" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 2 (Covariant)</strong></span> A covariant of weight <img src="https://latex.codecogs.com/png.latex?k"> of a binary form of degree <img src="https://latex.codecogs.com/png.latex?n"> is a function <img src="https://latex.codecogs.com/png.latex?J(a_0,%20a_1,%20%5Cldots,%20a_n;%20x,%20y)"> such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ(a_0,%20a_1,%20%5Cldots,%20a_n;%20x,%20y)%20=%20(ad%20-%20bc)%5Ek%20%5Cbar%7BJ%7D(%5Cbar%7Ba%7D_0,%20%5Cbar%7Ba%7D_1,%20%5Cldots,%20%5Cbar%7Ba%7D_n;%20%5Cbar%7Bx%7D,%20%5Cbar%7By%7D)%0A"></p>
</div>
<p>So an invariant is a covariant that does not depend on the variables.</p>
<div id="lemma-product-covariant">
<section id="product-of-covariants" class="level2">
<h2 class="anchored" data-anchor-id="product-of-covariants">Product of Covariants</h2>
<p>Given two covariants <img src="https://latex.codecogs.com/png.latex?J_1,%20J_2"> of weight <img src="https://latex.codecogs.com/png.latex?k"> and <img src="https://latex.codecogs.com/png.latex?l"> respectively, their product is a covariant of weight <img src="https://latex.codecogs.com/png.latex?k%20+%20l">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ_1(%5Cmathbf%7Ba%7D;%20x,%20y)%20%5Ccdot%20J_2(%5Cmathbf%7Ba%7D;%20x,%20y)%20=%20(ad%20-%20bc)%5E%7Bk+l%7D%20%5C,%20%5Cbar%7BJ%7D_1(%5Cbar%7B%5Cmathbf%7Ba%7D%7D;%20%5Cbar%7Bx%7D,%20%5Cbar%7By%7D)%20%5Ccdot%20%5Cbar%7BJ%7D_2(%5Cbar%7B%5Cmathbf%7Ba%7D%7D;%20%5Cbar%7Bx%7D,%20%5Cbar%7By%7D)%0A"></p>
</section>
</div>
<div id="lemma-sum-covariant">
<section id="sum-of-covariants" class="level2">
<h2 class="anchored" data-anchor-id="sum-of-covariants">Sum of Covariants</h2>
<p>Given two covariants <img src="https://latex.codecogs.com/png.latex?J_1,%20J_2"> of the same weight <img src="https://latex.codecogs.com/png.latex?k">, their sum is also a covariant of weight <img src="https://latex.codecogs.com/png.latex?k">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0AJ_1(%5Cmathbf%7Ba%7D;%20x,%20y)%20+%20J_2(%5Cmathbf%7Ba%7D;%20x,%20y)%0A&amp;=%20(ad%20-%20bc)%5Ek%20%5C,%20%5Cbar%7BJ%7D_1(%5Cbar%7B%5Cmathbf%7Ba%7D%7D;%20%5Cbar%7Bx%7D,%20%5Cbar%7By%7D)%20+%20(ad%20-%20bc)%5Ek%20%5C,%20%5Cbar%7BJ%7D_2(%5Cbar%7B%5Cmathbf%7Ba%7D%7D;%20%5Cbar%7Bx%7D,%20%5Cbar%7By%7D)%20%5C%5C%0A&amp;=%20(ad%20-%20bc)%5Ek%20%5Cleft(%20%5Cbar%7BJ%7D_1(%5Cbar%7B%5Cmathbf%7Ba%7D%7D;%20%5Cbar%7Bx%7D,%20%5Cbar%7By%7D)%20+%20%5Cbar%7BJ%7D_2(%5Cbar%7B%5Cmathbf%7Ba%7D%7D;%20%5Cbar%7Bx%7D,%20%5Cbar%7By%7D)%20%5Cright)%0A%5Cend%7Baligned%7D%0A"></p>
</section>
</div>
<p>The constant <img src="https://latex.codecogs.com/png.latex?0"> is trivially a covariant of any weight, and the constant <img src="https://latex.codecogs.com/png.latex?1"> is a covariant of weight <img src="https://latex.codecogs.com/png.latex?0">. Therefore, covariants of a fixed weight form a vector space, and the set of all covariants forms a ring, graded by weight. We call this the algebra of polynomial covariants in <img src="https://latex.codecogs.com/png.latex?k%5Ba_0,%20%5Cldots,%20a_n,%20x,%20y%5D"> (over a field <img src="https://latex.codecogs.com/png.latex?k"> of characteristic zero). The invariants (covariants of weight <img src="https://latex.codecogs.com/png.latex?0"> that do not depend on <img src="https://latex.codecogs.com/png.latex?x,%20y">) form a subring in <img src="https://latex.codecogs.com/png.latex?k%5Ba_0,%20%5Cldots,%20a_n%5D">.</p>
</section>
<section id="representation-theory" class="level1">
<h1>Representation Theory</h1>
<p>We are interested in how GL(2) acts on the space of binary forms. Since binary forms are already polynomials, we can think of them as vectors in a vector space. So we have a group (GL(2)) acting on a vector space (the space of binary forms).</p>
<p>Since this is a map from the group GL(2) to the general linear group of the vector space of binary forms, it is a representation (by definition). Therefore, can use tools from the representation theory of GL(2) to help understand what kinds of actions GL(2) can perform on the polynomials.</p>
<p>(Note that this entails finding <em>other</em> matrices, not necessarily of dimension 2x2, that represent the group elements of GL(2)).</p>
<p>Furthermore, if we could decompose said representation into irreducible subrepresentations, we will know which subspaces of the space of binary forms “map to themselves” under the action of GL(2) (or even better, are trivial). By viewing the subspaces, we can find invariants and covariants.</p>
<p>So let’s start by reviewing some basic definitions from representation theory, and then apply them to the specific case of binary forms.</p>
<p>(Before we proceed, note that we may switch between <img src="https://latex.codecogs.com/png.latex?SL(2)"> and <img src="https://latex.codecogs.com/png.latex?GL(2)">. For representation theory (Clebsch-Gordan, Schur’s lemma), it is cleaner to work with <img src="https://latex.codecogs.com/png.latex?SL(2)">, since it removes the determinant character and makes the symmetric powers <img src="https://latex.codecogs.com/png.latex?V(n)=%5Cmathrm%7BSym%7D%5En(%5Cmathbb%20C%5E2)"> irreducible representations. For classical invariant theory, the natural transformation group on binary forms is <img src="https://latex.codecogs.com/png.latex?GL(2)">, and invariants for <img src="https://latex.codecogs.com/png.latex?GL(2)"> typically transform by a power of <img src="https://latex.codecogs.com/png.latex?%5Cdet(g)">. Equivalently, <img src="https://latex.codecogs.com/png.latex?GL(2)">-invariants are <img src="https://latex.codecogs.com/png.latex?SL(2)">-invariants with additional bookkeeping for the determinant weights.)</p>
<!-- So there are two questions we need to answer. The first is, what are the irreducible subrepresentations of the representation "GL(2) acting on the space of binary forms"? It turns out there is exactly one irreducible subrepresentation for each degree $n$ of the space of binary forms, so we can analyze the action of GL(2) on each such subspace.

The second is, how do we decompose the representation of "GL(2) acting on the space of binary forms" into irreducible representations? The answer is given by the Clebsch-Gordan formula. -->
<section id="representations" class="level2">
<h2 class="anchored" data-anchor-id="representations">Representations</h2>
<div id="def-representation" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 3 (Representation)</strong></span> A representation of a group <img src="https://latex.codecogs.com/png.latex?G"> on a vector space <img src="https://latex.codecogs.com/png.latex?V"> is a homomorphism <img src="https://latex.codecogs.com/png.latex?%5Crho:%20G%20%5Cto%20GL(V)">.</p>
<p>We define for each <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G"> an invertible linear map <img src="https://latex.codecogs.com/png.latex?%5Crho(g):%20V%20%5Cto%20V">, such that <img src="https://latex.codecogs.com/png.latex?%5Crho(gh)%20=%20%5Crho(g)%5Crho(h)"> and <img src="https://latex.codecogs.com/png.latex?%5Crho(e)%20=%20%5Ctext%7Bid%7D">, where <img src="https://latex.codecogs.com/png.latex?e"> is the identity element of <img src="https://latex.codecogs.com/png.latex?G">.</p>
</div>
<p>We often suppress <img src="https://latex.codecogs.com/png.latex?%5Crho"> and write <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20v"> for <img src="https://latex.codecogs.com/png.latex?%5Crho(g)(v)">. Convention refers to <img src="https://latex.codecogs.com/png.latex?V"> as a “representation of <img src="https://latex.codecogs.com/png.latex?G">” (<img src="https://latex.codecogs.com/png.latex?V"> by itself is just a vector space. Technically “a representation” means the map <img src="https://latex.codecogs.com/png.latex?%5Crho">, but since <img src="https://latex.codecogs.com/png.latex?G"> is usually fixed, sometimes it is labelled by the target space).</p>
<p>A subrepresentation of <img src="https://latex.codecogs.com/png.latex?V"> (with action <img src="https://latex.codecogs.com/png.latex?%5Crho">) is a subspace <img src="https://latex.codecogs.com/png.latex?W%20%5Csubseteq%20V"> such that for all <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">, <img src="https://latex.codecogs.com/png.latex?w%20%5Cin%20W"> we have <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20w%20%5Cin%20W"> (<img src="https://latex.codecogs.com/png.latex?W"> is closed under the action of <img src="https://latex.codecogs.com/png.latex?G">).</p>
<p>It’s worth noting that if we choose a basis for <img src="https://latex.codecogs.com/png.latex?V">, each <img src="https://latex.codecogs.com/png.latex?%5Crho(g)"> can be associated with an invertible matrix. The representation is then a map <img src="https://latex.codecogs.com/png.latex?%5Crho:%20G%20%5Cto%20GL(n)">, where <img src="https://latex.codecogs.com/png.latex?n%20=%20%5Cdim%20V">. The choice of basis is unique up to conjugation by an element of <img src="https://latex.codecogs.com/png.latex?GL(n)">, so the representation is really a homomorphism into <img src="https://latex.codecogs.com/png.latex?GL(n)"> up to conjugation.</p>
</section>
<section id="irreducibility-and-complete-reducibility" class="level2">
<h2 class="anchored" data-anchor-id="irreducibility-and-complete-reducibility">Irreducibility and Complete Reducibility</h2>
<div id="def-irreducible" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 4 (Irreducible Representation)</strong></span> We say that a representation <img src="https://latex.codecogs.com/png.latex?V"> is irreducible if its only subrepresentations are <img src="https://latex.codecogs.com/png.latex?%5C%7B0%5C%7D"> and <img src="https://latex.codecogs.com/png.latex?V"> itself.</p>
</div>
<div id="def-completely-reducible" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 5 (Completely Reducible Representation)</strong></span> We say that a representation <img src="https://latex.codecogs.com/png.latex?V"> is completely reducible if it decomposes as a direct sum of irreducible representations:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV%20%5Ccong%20V_1%20%5Coplus%20V_2%20%5Coplus%20%5Ccdots%20%5Coplus%20V_k%0A"></p>
<p>where each <img src="https://latex.codecogs.com/png.latex?V_i"> is a subspace of <img src="https://latex.codecogs.com/png.latex?V"> and <img src="https://latex.codecogs.com/png.latex?G"> acts on each <img src="https://latex.codecogs.com/png.latex?V_i"> by restricting the original action.</p>
</div>
<p>In other words, a representation <img src="https://latex.codecogs.com/png.latex?V"> is completely reducible if every vector in <img src="https://latex.codecogs.com/png.latex?V"> can be uniquely written as a sum of vectors from the various <img src="https://latex.codecogs.com/png.latex?V_i">’s.</p>
<p>As an example, consider the action of <img src="https://latex.codecogs.com/png.latex?S_3"> on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3">, where <img src="https://latex.codecogs.com/png.latex?S_3"> permutes coordinates.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5Csigma%20%5Ccdot%20(v_1,%20v_2,%20v_3)%20&amp;=%20(v_%7B%5Csigma%5E%7B-1%7D(1)%7D,%20v_%7B%5Csigma%5E%7B-1%7D(2)%7D,%20v_%7B%5Csigma%5E%7B-1%7D(3)%7D)%0A%5Cend%7Baligned%7D%0A"></p>
<section id="complete-reducibility-example" class="level3">
<h3 class="anchored" data-anchor-id="complete-reducibility-example">Complete Reducibility Example</h3>
<p>Consider the subspace <img src="https://latex.codecogs.com/png.latex?%5C%7B(v_1,%20v_2,%20v_3)%20%5Cmid%20v_1%20+%20v_2%20+%20v_3%20=%200%5C%7D">, where <img src="https://latex.codecogs.com/png.latex?e_i"> is the standard basis vector with a 1 in the <img src="https://latex.codecogs.com/png.latex?i">-th coordinate and 0 elsewhere.</p>
<p>All elements of this subspace can be written as (<img src="https://latex.codecogs.com/png.latex?v_1,%20v_2,%20-v_1%20-%20v_2">) for some <img src="https://latex.codecogs.com/png.latex?v_1,%20v_2%20%5Cin%20%5Cmathbb%7BC%7D">.</p>
<p>If we apply <img src="https://latex.codecogs.com/png.latex?%5Csigma"> to a vector in this subspace, we get:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Csigma%20%5Ccdot%20(v_1,%20v_2,%20v_3)%20=%20(v_%7B%5Csigma%5E%7B-1%7D(1)%7D,%20v_%7B%5Csigma%5E%7B-1%7D(2)%7D,%20v_%7B%5Csigma%5E%7B-1%7D(3)%7D)%0A"> <img src="https://latex.codecogs.com/png.latex?%0A=%20(v_%7B%5Csigma%5E%7B-1%7D(1)%7D,%20v_%7B%5Csigma%5E%7B-1%7D(2)%7D,%20-%20v_%7B%5Csigma%5E%7B-1%7D(1)%7D%20-%20v_%7B%5Csigma%5E%7B-1%7D(2)%7D)%0A"></p>
<p>which is an element of the same subspace.</p>
<p>Now consider the subspace <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSpan%7D%5C%7B%20(1,1,1)%20%5C%7D"> (i.e.&nbsp;all elements the same size). This is also closed under the action of <img src="https://latex.codecogs.com/png.latex?S_3">.</p>
<p>Since the two subspaces only intersect at the zero vector, the two representations are complementary. Since they are two-dimensional and one-dimensional, respectively, we have a direct sum decomposition:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%7BC%7D%5E3%20=%20%5Ctext%7Bspan%7D%7B(a*(e_1,%20e_2,%20e_3)%20%7C%20a%20%5Cin%20%5Cmathbb%7BC%7D)%7D%20%5Coplus%20%5C%7B(v_1,%20v_2,%20v_3)%20%5Cmid%20v_1%20+%20v_2%20+%20v_3%20=%200%5C%7D%0A"></p>
<p>So the action of <img src="https://latex.codecogs.com/png.latex?S_3"> on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E3"> is completely reducible because it decomposes as a direct sum of the trivial representation (where <img src="https://latex.codecogs.com/png.latex?S_3"> fixes everything) and an irreducible 2-dimensional representation.</p>
</section>
<section id="irreducibility-example" class="level3">
<h3 class="anchored" data-anchor-id="irreducibility-example">Irreducibility Example</h3>
<p>Not every representation is completely reducible. Consider the action of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BZ%7D"> on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E2"> given by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0An%20%5Ccdot%20(v_1,%20v_2)%20=%20(v_1%20+%20n%20v_2,%20v_2)%0A"></p>
<p>The subspace <img src="https://latex.codecogs.com/png.latex?%5C%7B(v_1,%200)%20%5Cmid%20v_1%20%5Cin%20%5Cmathbb%7BC%7D%5C%7D"> is closed under the action of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BZ%7D">, so it is a subrepresentation. However, there is no complementary subrepresentation, since under the action of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BZ%7D">, any subspace that contains a vector of the form <img src="https://latex.codecogs.com/png.latex?(0,%20v_2)"> must also contain all vectors of the form <img src="https://latex.codecogs.com/png.latex?(n%20v_2,%20v_2)"> for <img src="https://latex.codecogs.com/png.latex?n%20%5Cin%20%5Cmathbb%7BZ%7D">, which is the entire space. So this representation is not completely reducible.</p>
</section>
</section>
<section id="schurs-lemma" class="level2">
<h2 class="anchored" data-anchor-id="schurs-lemma">Schur’s Lemma</h2>
<p>The same group <img src="https://latex.codecogs.com/png.latex?G"> can act on different vector spaces in different ways. Each such action is a representation. Schur’s lemma tells us what maps between two such representations can look like.</p>
<div id="def-equivariant-linear-map" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 6 (Equivariant Linear Map)</strong></span> Consider a linear map <img src="https://latex.codecogs.com/png.latex?%5Cphi:%20V%20%5Cto%20W">, where <img src="https://latex.codecogs.com/png.latex?V"> and <img src="https://latex.codecogs.com/png.latex?W"> are representations of a group <img src="https://latex.codecogs.com/png.latex?G">. If for all <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G"> and <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20V">, we have <img src="https://latex.codecogs.com/png.latex?%5Cphi(g%20%5Ccdot%20v)%20=%20g%20%5Ccdot%20%5Cphi(v)">, we say that <img src="https://latex.codecogs.com/png.latex?%5Cphi"> is a <img src="https://latex.codecogs.com/png.latex?G">-equivariant linear map.</p>
</div>
<p>This should remind you of our earlier exploration of <a href="../../../game_theory/posts/noether_time/index.html">equivariance</a>. The idea is that the representation <img src="https://latex.codecogs.com/png.latex?%5Cphi"> “commutes” with the action of <img src="https://latex.codecogs.com/png.latex?G">.</p>
<div id="lem-schur" class="theorem lemma">
<p><span class="theorem-title"><strong>Lemma 1 (Schur’s Lemma)</strong></span> Let <img src="https://latex.codecogs.com/png.latex?V"> and <img src="https://latex.codecogs.com/png.latex?W"> be irreducible representations of <img src="https://latex.codecogs.com/png.latex?G"> over an algebraically-closed field <img src="https://latex.codecogs.com/png.latex?k">. If <img src="https://latex.codecogs.com/png.latex?%5Cphi:%20V%20%5Cto%20W"> is a <img src="https://latex.codecogs.com/png.latex?G">-equivariant linear map, then: 1. <img src="https://latex.codecogs.com/png.latex?%5Cphi"> is either zero or an isomorphism. 2. If <img src="https://latex.codecogs.com/png.latex?V%20=%20W">, then <img src="https://latex.codecogs.com/png.latex?%5Cphi%20=%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D"> for some scalar <img src="https://latex.codecogs.com/png.latex?%5Clambda">.</p>
</div>
<p><em>Proof.</em></p>
<p>(1): If <img src="https://latex.codecogs.com/png.latex?%5Cphi(v)%20=%200">, then <img src="https://latex.codecogs.com/png.latex?%5Cphi(g%20%5Ccdot%20v)%20=%20g%20%5Ccdot%20%5Cphi(v)%20=%200">, and so the kernel <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi"> is an invariant subspace of <img src="https://latex.codecogs.com/png.latex?V">. By irreducibility <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi"> is either <img src="https://latex.codecogs.com/png.latex?%5C%7B0%5C%7D"> or <img src="https://latex.codecogs.com/png.latex?V">, as those are the only subspaces of <img src="https://latex.codecogs.com/png.latex?V">.</p>
<p>Similarly, the image <img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bim%7D(%5Cphi)"> is an invariant subspace of <img src="https://latex.codecogs.com/png.latex?W">, so it is either <img src="https://latex.codecogs.com/png.latex?%5C%7B0%5C%7D"> or <img src="https://latex.codecogs.com/png.latex?W">. If <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi%20=%20%5C%7B0%5C%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bim%7D(%5Cphi)%20=%20W">, then <img src="https://latex.codecogs.com/png.latex?%5Cphi"> is an isomorphism. Otherwise <img src="https://latex.codecogs.com/png.latex?%5Cphi%20=%200">.</p>
<p>(2): Consider the map <img src="https://latex.codecogs.com/png.latex?%5Cphi%20-%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D">.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?k"> is algebraically closed, the polynomial <img src="https://latex.codecogs.com/png.latex?%5Cdet(%5Cphi%20-%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D)"> has at least one root. So the kernel of this map is nontrivial.</p>
<p>Since we have <img src="https://latex.codecogs.com/png.latex?(%5Cphi%20-%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D)(g%20%5Ccdot%20v)%20=%20%5Cphi(g%20%5Ccdot%20v)%20-%20%5Clambda%20(g%20%5Ccdot%20v)%20=%20g%20%5Ccdot%20%5Cphi(v)%20-%20%5Clambda%20(g%20%5Ccdot%20v)%20=%20g%20%5Ccdot%20(%5Cphi%20-%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D)(v)">, the map <img src="https://latex.codecogs.com/png.latex?%5Cphi%20-%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D"> is equivariant.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?%5Cphi%20-%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D"> is equivariant and <img src="https://latex.codecogs.com/png.latex?V"> is irreducible, by part (1) it must be zero or an isomorphism. Since the kernel is nontrivial, <img src="https://latex.codecogs.com/png.latex?%5Cphi%20-%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D"> is zero. So <img src="https://latex.codecogs.com/png.latex?%5Cphi%20=%20%5Clambda%20%5Ccdot%20%5Ctext%7Bid%7D">. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>Schur’s lemma constrains maps between irreducible representations. The space of equivariant maps <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BHom%7D_G(V,%20W)"> is zero if <img src="https://latex.codecogs.com/png.latex?V%20%5Cnot%5Ccong%20W"> or one-dimensional if <img src="https://latex.codecogs.com/png.latex?V%20%5Ccong%20W">. So given some representation <img src="https://latex.codecogs.com/png.latex?U">, the projection of <img src="https://latex.codecogs.com/png.latex?U"> onto it decomposition <img src="https://latex.codecogs.com/png.latex?U%20%5Ccong%20%5Cbigoplus%20V_i"> (by projecting it onto each irreducible summand) is unique (up to scaling).</p>
<p>Basically, the combination of irreducibility and the group structure reduces linear algebra to scalar algebra, which is why representation theory is so powerful for reductive groups.</p>
</section>
<section id="symmetric-powers" class="level2">
<h2 class="anchored" data-anchor-id="symmetric-powers">Symmetric Powers</h2>
<p>Let us now apply some of these ideas to the specific case of binary forms. We want to understand how <img src="https://latex.codecogs.com/png.latex?GL(2)"> acts on the space of binary forms of degree <img src="https://latex.codecogs.com/png.latex?n">. We’ll use <img src="https://latex.codecogs.com/png.latex?SL(2)"> instead of <img src="https://latex.codecogs.com/png.latex?GL(2)"> to remove the determinant factor, which will make things simpler.</p>
<div id="def-symmetric-power" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 7</strong></span> Denote the space of homogeneous polynomials of degree <img src="https://latex.codecogs.com/png.latex?n"> in two variables as <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)">.</p>
<p>We call this the “<img src="https://latex.codecogs.com/png.latex?n">-th symmetric power of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E2">”.</p>
</div>
<p>If <img src="https://latex.codecogs.com/png.latex?V%20=%20%5Cmathbb%7BC%7D%5E2"> with basis <img src="https://latex.codecogs.com/png.latex?%5C%7Be_1,%20e_2%5C%7D"> and coordinates <img src="https://latex.codecogs.com/png.latex?x,%20y">, then <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)"> has basis <img src="https://latex.codecogs.com/png.latex?%5C%7Bx%5En,%20x%5E%7Bn-1%7Dy,%20%5Cldots,%20y%5En%5C%7D"> and dimension <img src="https://latex.codecogs.com/png.latex?n%20+%201">.</p>
<p>Basically, this is just another set of notation for the space of binary forms of degree <img src="https://latex.codecogs.com/png.latex?n">.</p>
<p>If <img src="https://latex.codecogs.com/png.latex?G"> acts on <img src="https://latex.codecogs.com/png.latex?V"> (one representation), this induces a different action of <img src="https://latex.codecogs.com/png.latex?G"> on <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(V)"> (a new representation, on a bigger space) by:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(g%20%5Ccdot%20f)(v)%20=%20f(g%5E%7B-1%7D%20%5Ccdot%20v)%0A"></p>
<p>This is exactly the action of <img src="https://latex.codecogs.com/png.latex?GL(2)"> on binary forms that we have been studying.</p>
</section>
<div id="prop-sym-irrep">
<section id="symmetric-powers-are-irreducible-representations-of-sl2" class="level2">
<h2 class="anchored" data-anchor-id="symmetric-powers-are-irreducible-representations-of-sl2">Symmetric Powers are Irreducible Representations of <img src="https://latex.codecogs.com/png.latex?SL(2)"></h2>
<p>The action of <img src="https://latex.codecogs.com/png.latex?SL(2)"> on <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)"> by coordinate substitution is an irreducible representation.</p>
</section>
</div>
<p>To be more specific, each <img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%202"> matrix in <img src="https://latex.codecogs.com/png.latex?SL(2)"> induces an <img src="https://latex.codecogs.com/png.latex?(n+1)%20%5Ctimes%20(n+1)"> matrix on the space of degree-<img src="https://latex.codecogs.com/png.latex?n"> binary forms, and there is no proper subspace of degree-<img src="https://latex.codecogs.com/png.latex?n"> binary forms that all of these <img src="https://latex.codecogs.com/png.latex?(n+1)%20%5Ctimes%20(n+1)"> matrices simultaneously preserve.</p>
<p>So we are representing elements of <img src="https://latex.codecogs.com/png.latex?SL(2)"> (which are 2x2 matrices) by <img src="https://latex.codecogs.com/png.latex?(n+1)%5Ctimes(n+1)"> dimensional matrices acting as linear transformations on the space of binary forms.</p>
<p><em>Proof.</em></p>
<p><img src="https://latex.codecogs.com/png.latex?SL(2)"> acts on <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)"> by linear substitution. For <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20SL(2)"> and <img src="https://latex.codecogs.com/png.latex?f(x,y)%20%5Cin%20%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)">, we have:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(g%20%5Ccdot%20f)(x,y)%20=%20f%5C!%5Cleft(g%5E%7B-1%7D%20%5Cbegin%7Bpmatrix%7D%20x%20%5C%5C%20y%20%5Cend%7Bpmatrix%7D%5Cright)%0A"></p>
<p>This is the action on binary forms from earlier (the <img src="https://latex.codecogs.com/png.latex?g%5E%7B-1%7D"> ensures associativity <img src="https://latex.codecogs.com/png.latex?(gh)%20%5Ccdot%20f%20=%20g%20%5Ccdot%20(h%20%5Ccdot%20f)">).</p>
<p>So <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)"> is a representation of <img src="https://latex.codecogs.com/png.latex?SL(2)">.</p>
<p>We want to show it is irreducible. To do this, we need to show that its only invariant subspaces are <img src="https://latex.codecogs.com/png.latex?%5C%7B0%5C%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)"> itself.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?W%20%5Csubseteq%20%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)"> be some nonzero invariant subspace. We must show that <img src="https://latex.codecogs.com/png.latex?W%20=%20%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)">.</p>
<p>Every nonzero polynomial in <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)"> factors (over <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D">) as a product of <img src="https://latex.codecogs.com/png.latex?n"> linear forms.</p>
<ol type="1">
<li><em>The <img src="https://latex.codecogs.com/png.latex?n">-th powers are transitive under the action of <img src="https://latex.codecogs.com/png.latex?SL(2)">.</em></li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?SL(2)"> acts transitively on the nonzero vectors of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D%5E2">. Given nonzero <img src="https://latex.codecogs.com/png.latex?v,%20w%20%5Cin%20%5Cmathbb%7BC%7D%5E2">, pick <img src="https://latex.codecogs.com/png.latex?v'"> such that <img src="https://latex.codecogs.com/png.latex?%5Cdet%5Bv%20%5Cmid%20v'%5D%20=%201"> and <img src="https://latex.codecogs.com/png.latex?w'"> such that <img src="https://latex.codecogs.com/png.latex?%5Cdet%5Bw%20%5Cmid%20w'%5D%20=%201">. Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag%20=%20%5Bw%20%5Cmid%20w'%5D%5Bv%20%5Cmid%20v'%5D%5E%7B-1%7D%20%5Cin%20SL(2)%0A"> <img src="https://latex.codecogs.com/png.latex?%0Agv%20=%20w%0A"></p>
<p>A linear form <img src="https://latex.codecogs.com/png.latex?%5Cell(x,y)%20=%20%5Calpha%20x%20+%20%5Cbeta%20y"> is determined by its coefficient vector <img src="https://latex.codecogs.com/png.latex?(%5Calpha,%20%5Cbeta)">. Since <img src="https://latex.codecogs.com/png.latex?(g%5E%7B-1%7D)%5ET%20%5Cin%20SL(2)"> whenever <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20SL(2)">, the fact that the coefficient vectors are transitive implies that the <img src="https://latex.codecogs.com/png.latex?n">th powers are transitive.</p>
<p>So, for any nonzero linear forms <img src="https://latex.codecogs.com/png.latex?%5Cell,%20m"> there exists <img src="https://latex.codecogs.com/png.latex?g"> with <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20%5Cell%5En%20=%20m%5En">.</p>
<ol start="2" type="1">
<li><em>The <img src="https://latex.codecogs.com/png.latex?n">-th powers span <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)">.</em></li>
</ol>
<p>Every monomial <img src="https://latex.codecogs.com/png.latex?x%5E%7Bn-k%7Dy%5Ek"> can be written as a linear combination of <img src="https://latex.codecogs.com/png.latex?n">-th powers. Expand</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(%5Calpha%20x%20+%20y)%5En%20=%20%5Csum_%7Bk=0%7D%5En%20%5Cbinom%7Bn%7D%7Bk%7D%20%5Calpha%5E%7Bn-k%7D%20x%5E%7Bn-k%7D%20y%5Ek%0A"></p>
<p>and evaluate at <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%200,%201,%202,%20%5Cldots,%20n">. This gives <img src="https://latex.codecogs.com/png.latex?n+1"> equations in the <img src="https://latex.codecogs.com/png.latex?n+1"> unknowns <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7Bn%7D%7Bk%7D%20x%5E%7Bn-k%7D%20y%5Ek">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bpmatrix%7D%200%5En%20&amp;%200%5E%7Bn-1%7D%20&amp;%20%5Ccdots%20&amp;%201%20%5C%5C%201%5En%20&amp;%201%5E%7Bn-1%7D%20&amp;%20%5Ccdots%20&amp;%201%20%5C%5C%202%5En%20&amp;%202%5E%7Bn-1%7D%20&amp;%20%5Ccdots%20&amp;%201%20%5C%5C%20%5Cvdots%20&amp;%20&amp;%20&amp;%20%5Cvdots%20%5C%5C%20n%5En%20&amp;%20n%5E%7Bn-1%7D%20&amp;%20%5Ccdots%20&amp;%201%20%5Cend%7Bpmatrix%7D%20%5Cbegin%7Bpmatrix%7D%20%5Cbinom%7Bn%7D%7B0%7D%20x%5En%20%5C%5C%20%5Cbinom%7Bn%7D%7B1%7D%20x%5E%7Bn-1%7Dy%20%5C%5C%20%5Cvdots%20%5C%5C%20%5Cbinom%7Bn%7D%7Bn%7D%20y%5En%20%5Cend%7Bpmatrix%7D%20=%20%5Cbegin%7Bpmatrix%7D%20y%5En%20%5C%5C%20(x+y)%5En%20%5C%5C%20(2x+y)%5En%20%5C%5C%20%5Cvdots%20%5C%5C%20(nx+y)%5En%20%5Cend%7Bpmatrix%7D%0A"></p>
<p>The matrix has <img src="https://latex.codecogs.com/png.latex?(i,j)">-entry <img src="https://latex.codecogs.com/png.latex?i%5E%7Bn-j%7D"> for <img src="https://latex.codecogs.com/png.latex?i%20=%200,%20%5Cldots,%20n"> and <img src="https://latex.codecogs.com/png.latex?j%20=%200,%20%5Cldots,%20n">. Its determinant is <img src="https://latex.codecogs.com/png.latex?%5Cprod_%7B0%20%5Cleq%20i%20%3C%20j%20%5Cleq%20n%7D(j%20-%20i)%20%5Cneq%200">, so the system is invertible and each monomial is a linear combination of the <img src="https://latex.codecogs.com/png.latex?n">-th powers on the right-hand side.</p>
<p>(Conclusion)</p>
<p>So, if <img src="https://latex.codecogs.com/png.latex?W"> contains any single <img src="https://latex.codecogs.com/png.latex?n">-th power, then it contains all <img src="https://latex.codecogs.com/png.latex?n">-th powers (1), and these span the whole space (2).</p>
<p>So we just need to show <img src="https://latex.codecogs.com/png.latex?W"> contains a single <img src="https://latex.codecogs.com/png.latex?n">-th power.</p>
<p>Pick a nonzero <img src="https://latex.codecogs.com/png.latex?f%20%5Cin%20W"> and write <img src="https://latex.codecogs.com/png.latex?f%20=%20%5Csum_%7Bk=0%7D%5En%20c_k%20x%5E%7Bn-k%7Dy%5Ek">. Consider the diagonal matrices <img src="https://latex.codecogs.com/png.latex?d(s)%20=%20%5Cbegin%7Bpmatrix%7D%20s%20&amp;%200%20%5C%5C%200%20&amp;%20s%5E%7B-1%7D%20%5Cend%7Bpmatrix%7D%20%5Cin%20SL(2)">, which act on elements of <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)"> by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ad(s)%20%5Ccdot%20x%5E%7Bn-k%7Dy%5Ek%20=%20s%5E%7Bn-2k%7D%20x%5E%7Bn-k%7Dy%5Ek%0A"></p>
<p>So <img src="https://latex.codecogs.com/png.latex?d(s)%20%5Ccdot%20f%20=%20%5Csum_%7Bk=0%7D%5En%20c_k%20%5C,%20s%5E%7Bn-2k%7D%20%5C,%20x%5E%7Bn-k%7Dy%5Ek">. The exponents <img src="https://latex.codecogs.com/png.latex?n-2k"> are distinct for <img src="https://latex.codecogs.com/png.latex?k%20=%200,%20%5Cldots,%20n">. Evaluating at <img src="https://latex.codecogs.com/png.latex?n+1"> distinct values of <img src="https://latex.codecogs.com/png.latex?s"> and solving the same invertible system as in (2) extracts each monomial with <img src="https://latex.codecogs.com/png.latex?c_k%20%5Cneq%200"> individually.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?W"> is closed under the action of <img src="https://latex.codecogs.com/png.latex?d(s)"> and under linear combinations, there is some monomial <img src="https://latex.codecogs.com/png.latex?x%5E%7Bn-k%7Dy%5Ek%20%5Cin%20W">.</p>
<p>Now apply <img src="https://latex.codecogs.com/png.latex?g%20=%20%5Cbegin%7Bpmatrix%7D%201%20&amp;%201%20%5C%5C%200%20&amp;%201%20%5Cend%7Bpmatrix%7D%20%5Cin%20SL(2)">, which acts by <img src="https://latex.codecogs.com/png.latex?x%20%5Cmapsto%20x,%20%5C,%20y%20%5Cmapsto%20x%20+%20y">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag%20%5Ccdot%20x%5E%7Bn-k%7Dy%5Ek%20=%20x%5E%7Bn-k%7D(x+y)%5Ek%20=%20%5Csum_%7Bj=0%7D%5Ek%20%5Cbinom%7Bk%7D%7Bj%7D%20x%5E%7Bn-j%7D%20y%5Ej%0A"></p>
<p>The <img src="https://latex.codecogs.com/png.latex?x%5En"> coefficient is <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7Bk%7D%7B0%7D%20=%201%20%5Cneq%200">. So <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20x%5E%7Bn-k%7Dy%5Ek%20%5Cin%20W"> has nonzero <img src="https://latex.codecogs.com/png.latex?x%5En"> term. Apply the diagonal trick again to extract <img src="https://latex.codecogs.com/png.latex?x%5En%20%5Cin%20W">.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?x%5En%20=%20(x)%5En"> is an <img src="https://latex.codecogs.com/png.latex?n">-th power, we are done. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>The significance is that the space of binary forms of degree <img src="https://latex.codecogs.com/png.latex?n"> is an irreducible representation of <img src="https://latex.codecogs.com/png.latex?SL(2)">.</p>
</section>
<section id="computing-invariants-and-covariants" class="level1">
<h1>Computing Invariants and Covariants</h1>
<p>We know that invariants and covariants form a ring, but how do we compute the actual elements of this ring?</p>
<section id="finite-groups" class="level2">
<h2 class="anchored" data-anchor-id="finite-groups">Finite Groups</h2>
<p>Let’s start by considering a slightly more abstract problem. We have a group <img src="https://latex.codecogs.com/png.latex?G"> acting on a vector space <img src="https://latex.codecogs.com/png.latex?V">, and we want to find the subspace of <img src="https://latex.codecogs.com/png.latex?V"> that is invariant under the action of <img src="https://latex.codecogs.com/png.latex?G">. That is, we want to find the set of vectors <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20V"> such that for all <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">, <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20v%20=%20v">.</p>
<p>If we want to produce a polynomial that is invariant under a group <img src="https://latex.codecogs.com/png.latex?G">, one idea is to average (or sum) over all possible transformations. For a finite group, we can simply sum:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BR%7D(f)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20g%20%5Ccdot%20f%0A"></p>
<p>The average is invariant. For any <img src="https://latex.codecogs.com/png.latex?h%20%5Cin%20G">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ah%20%5Ccdot%20%5Cmathcal%7BR%7D(f)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20(hg)%20%5Ccdot%20f%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg'%20%5Cin%20G%7D%20g'%20%5Ccdot%20f%20=%20%5Cmathcal%7BR%7D(f)%0A"></p>
<p>What’s happened? The map <img src="https://latex.codecogs.com/png.latex?g%20%5Cmapsto%20hg"> is a bijection on <img src="https://latex.codecogs.com/png.latex?G"> (its inverse is <img src="https://latex.codecogs.com/png.latex?g%20%5Cmapsto%20h%5E%7B-1%7Dg">), and so we are summing the same terms in a different order. Applying <img src="https://latex.codecogs.com/png.latex?h"> to every term in the sum just permutes the terms.</p>
<p>What’s more, if some <img src="https://latex.codecogs.com/png.latex?f"> is already invariant, then <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(f)%20=%20f">. This is because <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20f%20=%20f"> for all <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">, so the sum just gives us <img src="https://latex.codecogs.com/png.latex?%7CG%7C"> copies of <img src="https://latex.codecogs.com/png.latex?f">, which we then divide by <img src="https://latex.codecogs.com/png.latex?%7CG%7C"> to get back <img src="https://latex.codecogs.com/png.latex?f">.</p>
<p>The above operation is called the Reynolds operator, and it is a linear projection from <img src="https://latex.codecogs.com/png.latex?V"> onto the subspace of <img src="https://latex.codecogs.com/png.latex?G">-invariant vectors.</p>
</section>
<section id="conjugation-and-equivariance" class="level2">
<h2 class="anchored" data-anchor-id="conjugation-and-equivariance">Conjugation and Equivariance</h2>
<p>If <img src="https://latex.codecogs.com/png.latex?A"> is a linear map from <img src="https://latex.codecogs.com/png.latex?V"> to itself, then we can define an action of <img src="https://latex.codecogs.com/png.latex?G"> on <img src="https://latex.codecogs.com/png.latex?A"> by conjugation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag%20%5Cstar%20A%20:=%20%5Crho(g)%20A%20%5Crho(g)%5E%7B-1%7D%0A"></p>
<p>Since for all <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20V">, <img src="https://latex.codecogs.com/png.latex?A(%5Crho(g)%20v)%20=%20%5Crho(g)%20A(v)"> if and only if <img src="https://latex.codecogs.com/png.latex?%5Crho(g)%20A%20=%20A%20%5Crho(g)">, it’s also true <img src="https://latex.codecogs.com/png.latex?A"> is <img src="https://latex.codecogs.com/png.latex?G">-equivariant if and only if <img src="https://latex.codecogs.com/png.latex?g%20%5Cstar%20A%20=%20A"> for all <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">. So the space of <img src="https://latex.codecogs.com/png.latex?G">-equivariant maps from <img src="https://latex.codecogs.com/png.latex?V"> to itself is exactly the space of maps that are invariant under conjugation by <img src="https://latex.codecogs.com/png.latex?G">.</p>
<p>This will motivate the construction of the Reynolds operator in the proof of Maschke’s theorem, where we will average over the group to produce a <img src="https://latex.codecogs.com/png.latex?G">-equivariant projection.</p>
</section>
<section id="maschkes-theorem-for-finite-groups" class="level2">
<h2 class="anchored" data-anchor-id="maschkes-theorem-for-finite-groups">Maschke’s Theorem for Finite Groups</h2>
<div id="thm-maschke-finite" class="theorem">
<p><span class="theorem-title"><strong>Theorem 1 (Maschke’s Theorem for Finite Groups)</strong></span> Let <img src="https://latex.codecogs.com/png.latex?%5Crho:%20G%20%5Cto%20GL(V)"> be a representation of a finite group <img src="https://latex.codecogs.com/png.latex?G">, where <img src="https://latex.codecogs.com/png.latex?V"> is a (finite-dimensional) vector space over a field <img src="https://latex.codecogs.com/png.latex?k">. If the characteristic <img src="https://latex.codecogs.com/png.latex?char(k)"> of the field <img src="https://latex.codecogs.com/png.latex?k"> does not divide <img src="https://latex.codecogs.com/png.latex?%7CG%7C">, then <img src="https://latex.codecogs.com/png.latex?G"> is completely reducible.</p>
</div>
<p><em>Proof.</em></p>
<p>We want to show that <img src="https://latex.codecogs.com/png.latex?V"> decomposes as a direct sum of irreducible representations. It suffices to show that for any subrepresentation <img src="https://latex.codecogs.com/png.latex?W%20%5Csubseteq%20V">, there is a complementary subrepresentation <img src="https://latex.codecogs.com/png.latex?W'%20%5Csubseteq%20V"> such that <img src="https://latex.codecogs.com/png.latex?V%20=%20W%20%5Coplus%20W'">. If this is true, then we can apply the same argument to <img src="https://latex.codecogs.com/png.latex?W"> and <img src="https://latex.codecogs.com/png.latex?W'"> to find complementary subrepresentations, and so on, until we have decomposed <img src="https://latex.codecogs.com/png.latex?V"> into irreducible representations.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?V"> be a representation of <img src="https://latex.codecogs.com/png.latex?G">, and let <img src="https://latex.codecogs.com/png.latex?W%20%5Csubseteq%20V"> be a subrepresentation that is closed under action of <img src="https://latex.codecogs.com/png.latex?G">.</p>
<p>Choose a linear map <img src="https://latex.codecogs.com/png.latex?P%20:%20V%20%5Cto%20V"> such that <img src="https://latex.codecogs.com/png.latex?W"> is stable under action by <img src="https://latex.codecogs.com/png.latex?P"> (that is, <img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bim%7D(P)%20=%20W"> and <img src="https://latex.codecogs.com/png.latex?Pw%20=%20w"> for all <img src="https://latex.codecogs.com/png.latex?w%20%5Cin%20W">). This works by standard linear algebra since we assumed finite dimensions.</p>
<p>Each <img src="https://latex.codecogs.com/png.latex?g"> acts as a linear map <img src="https://latex.codecogs.com/png.latex?%5Crho(g):%20V%20%5Cto%20V">. Then we can define a new projection <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P):%20V%20%5Cto%20V"> (by averaging over the group):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BR%7D(P)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20(g%20%5Cstar%20P)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20%5Crho(g)%20%5Ccdot%20P%20%5Ccdot%20%5Crho(g)%5E%7B-1%7D%0A"></p>
<p>This new projection <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P)"> is <img src="https://latex.codecogs.com/png.latex?G">-equivariant, since for any <img src="https://latex.codecogs.com/png.latex?h%20%5Cin%20G"> we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ah%5Cstar%20%5Cmathcal%7BR%7D(P)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20%5Crho(h)%20%5Crho(g)%20%5Ccdot%20P%20%5Ccdot%20%5Crho(g)%5E%7B-1%7D%5Crho(h)%5E%7B-1%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0A=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg%20%5Cin%20G%7D%20%5Crho(hg)%20%5Ccdot%20P%20%5Ccdot%20%5Crho(hg)%5E%7B-1%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0A=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%20%5Csum_%7Bg'%20%5Cin%20G%7D%20g'%20%5Ccdot%20P%20%5Ccdot%20g'%5E%7B-1%7D=%20%5Cmathcal%7BR%7D(P)%0A"></p>
<p>(Note that if <img src="https://latex.codecogs.com/png.latex?k"> divides <img src="https://latex.codecogs.com/png.latex?%7CG%7C">, then we cannot divide by <img src="https://latex.codecogs.com/png.latex?%7CG%7C"> and this construction fails, which is why the condition on the characteristic is necessary).</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P)"> is invariant under the conjugation action of <img src="https://latex.codecogs.com/png.latex?G">, then <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P)"> is <img src="https://latex.codecogs.com/png.latex?G">-equivariant.</p>
<p>We know that the image of <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P)"> is <img src="https://latex.codecogs.com/png.latex?W">, since <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P)(w)%20=%20w"> for all <img src="https://latex.codecogs.com/png.latex?w%20%5Cin%20W">, and <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P)(v)%20%5Cin%20W"> for all <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20V">. So the kernel of <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P)"> is a complementary subrepresentation to <img src="https://latex.codecogs.com/png.latex?W">. By induction on the dimension of <img src="https://latex.codecogs.com/png.latex?V">, we can decompose <img src="https://latex.codecogs.com/png.latex?V"> into irreducible subrepresentations. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>Essentially, we have taken our complements from ordinary linear algebra and equipped them with <img src="https://latex.codecogs.com/png.latex?G">-equivariance.</p>
</section>
<section id="compact-groups" class="level2">
<h2 class="anchored" data-anchor-id="compact-groups">Compact Groups</h2>
<p>Sadly, <img src="https://latex.codecogs.com/png.latex?GL(2)"> is not a finite group, so we can’t just sum over all transformations. However, we can still try an analogous trick. Instead of summing, we could integrate.</p>
<p>When does the Reynolds operator exist for continuous groups? We need a measure to integrate over a continuous group such that no “region” of the group is weighted more than any other. If we had such a measure <img src="https://latex.codecogs.com/png.latex?%5Cmu">, then we could define the Reynolds operator as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BR%7D(f)%20=%20%5Cint_%7BG%7D%20(g%20%5Ccdot%20f)%20%5C,%20d%5Cmu(g)%0A"></p>
<p>for some measure <img src="https://latex.codecogs.com/png.latex?%5Cmu"> on the group <img src="https://latex.codecogs.com/png.latex?G">. Then for any <img src="https://latex.codecogs.com/png.latex?h%20%5Cin%20G">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ah%20%5Ccdot%20%5Cmathcal%7BR%7D(f)%20=%20%5Cint_%7BG%7D%20(hg%20%5Ccdot%20f)%20%5C,%20d%5Cmu(g)%20=%20%5Cint_%7BG%7D%20(g'%20%5Ccdot%20f)%20%5C,%20d%5Cmu(g')%20=%20%5Cmathcal%7BR%7D(f)%0A"></p>
<p>Luckily for us, in 1933, Haar proved that every compact group has such a measure, and that it is essentially unique<sup>3</sup>.</p>
<p>How can we construct a Haar measure? If we assume that the group is also smooth (i.e.&nbsp;it is Lie group), then the we can concretely construct the Haar measure. In fact, there is an “algorithm” to do so<sup>4</sup>:</p>
<p>The construction is as follows:</p>
<ol type="1">
<li>Parametrize the group: write each element as <img src="https://latex.codecogs.com/png.latex?U(%5Ctheta_1,%20%5Cldots,%20%5Ctheta_n)"></li>
<li>Compute the Maurer-Cartan form <img src="https://latex.codecogs.com/png.latex?%5COmega%20=%20U%5E%7B-1%7D%20dU"></li>
<li>Expand in a basis of the Lie algebra: <img src="https://latex.codecogs.com/png.latex?%5COmega%20=%20%5Comega_1%20T_1%20+%20%5Ccdots%20+%20%5Comega_n%20T_n"></li>
<li>The Haar measure is <img src="https://latex.codecogs.com/png.latex?%5Comega_1%20%5Cwedge%20%5Ccdots%20%5Cwedge%20%5Comega_n"></li>
</ol>
<p>For <img src="https://latex.codecogs.com/png.latex?SO(2)">, we can parametrize by <img src="https://latex.codecogs.com/png.latex?%5Ctheta">, then compute <img src="https://latex.codecogs.com/png.latex?%0AR_%5Ctheta%5E%7B-1%7D%20dR_%5Ctheta%20=%20%5Cbegin%7Bpmatrix%7D%200%20&amp;%20-1%20%5C%5C%0A1%20&amp;%200%20%5Cend%7Bpmatrix%7Dd%5Ctheta%0A"></p>
<p>and the Haar measure is <img src="https://latex.codecogs.com/png.latex?d%5Ctheta%20/%202%5Cpi"> (to normalize the total measure to 1).</p>
<p>We’ll probably explicitly write code for this when we look at computational invariant theory, but the key point is that for compact groups, we can construct a Reynolds operator by integrating over the group with respect to the Haar measure. This allows us to compute invariants and covariants for compact groups.</p>
<section id="maschkes-theorem-for-compact-groups" class="level3">
<h3 class="anchored" data-anchor-id="maschkes-theorem-for-compact-groups">Maschke’s Theorem for Compact Groups</h3>
<div id="thm-maschke-compact" class="theorem">
<p><span class="theorem-title"><strong>Theorem 2 (Maschke’s Theorem for Compact Groups)</strong></span> Let <img src="https://latex.codecogs.com/png.latex?G"> be a compact Lie group with normalized Haar measure <img src="https://latex.codecogs.com/png.latex?%5Cmu"> and <img src="https://latex.codecogs.com/png.latex?V"> a finite-dimensional vector space over <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D">.</p>
<p>Given a continuous finite-dimensional representation <img src="https://latex.codecogs.com/png.latex?%5Crho%20:%20G%5Cto%20GL(V)"> and a <img src="https://latex.codecogs.com/png.latex?G">-stable subspace <img src="https://latex.codecogs.com/png.latex?W%5Csubseteq%20V">, choose a linear projection <img src="https://latex.codecogs.com/png.latex?P:V%5Cto%20V"> with <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7Bim%7D(P)=W"> and <img src="https://latex.codecogs.com/png.latex?Pw%20=%20w"> for all <img src="https://latex.codecogs.com/png.latex?w%20%5Cin%20W">.</p>
<p>Define the averaged operator <img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%20R(P)%20%5C;=%5C;%20%5Cint_G%20%5Crho(g)%5C,P%5C,%5Crho(g)%5E%7B-1%7D%5C,%20d%5Cmu(g)%0A"></p>
<p>Then <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%20R(P)"> is invariant under the <img src="https://latex.codecogs.com/png.latex?%5Cstar">-action (by the change of variables <img src="https://latex.codecogs.com/png.latex?g%5Cmapsto%20hg"> and left-invariance of <img src="https://latex.codecogs.com/png.latex?%5Cmu">). So <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(P)"> is <img src="https://latex.codecogs.com/png.latex?G">-equivariant.</p>
<p>Also <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7Bim%7D(%5Cmathcal%20R(P))%20=%20W">, since <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%20R(P)(w)%20=%20P(w)%20=%20w"> for all <img src="https://latex.codecogs.com/png.latex?w%20%5Cin%20W">, and <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%20R(P)(v)%20%5Cin%20W"> for all <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20V">.</p>
<p>Hence <img src="https://latex.codecogs.com/png.latex?%5Cker(%5Cmathcal%20R(P))"> is a complement to <img src="https://latex.codecogs.com/png.latex?W"> that is invariant under the action of <img src="https://latex.codecogs.com/png.latex?G">.</p>
</div>
</section>
</section>
<section id="reductive-groups" class="level2">
<h2 class="anchored" data-anchor-id="reductive-groups">Reductive Groups</h2>
<p>Unfortunately, <img src="https://latex.codecogs.com/png.latex?GL(2)"> is also noncompact. This means that the group is not bounded, and so we cannot integrate over it in a way that gives us a finite result. The integral can diverge.</p>
<p>To see this, we can decompose a linear transformation in <img src="https://latex.codecogs.com/png.latex?GL(2)"> as follows (using the Iwasawa decomposition):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bbmatrix%7Da%20&amp;%20b%20%5C%5C%20c%20&amp;%20d%5Cend%7Bbmatrix%7D%20=%20%5Cbegin%7Bbmatrix%7D%5Ccos%5Ctheta%20&amp;%20%5Csin%5Ctheta%20%5C%5C%20-%5Csin%5Ctheta%20&amp;%20%5Ccos%5Ctheta%5Cend%7Bbmatrix%7D%20%5Cbegin%7Bbmatrix%7Dr_1%20&amp;%200%20%5C%5C%200%20&amp;%20r_2%5Cend%7Bbmatrix%7D%20%5Cbegin%7Bbmatrix%7D1%20&amp;%20n%20%5C%5C%200%20&amp;%201%5Cend%7Bbmatrix%7D%0A"></p>
<p>The entries are unbounded. So we can try to integrate over the group by integrating over <img src="https://latex.codecogs.com/png.latex?%5Ctheta,%20r_1,%20r_2,%20n">, but the integrals over <img src="https://latex.codecogs.com/png.latex?r_1"> and <img src="https://latex.codecogs.com/png.latex?r_2"> could diverge.</p>
<p>However, GL(2) is reductive, which means that it has a nice representation theory that allows us to compute the invariants and covariants without needing to integrate. I cover the relevant representation theory <a href="../../../symmetry/posts/invariant_theory/index.html#representation-theory">above</a>.</p>
<div id="thm-reductive-invariants" class="theorem">
<p><span class="theorem-title"><strong>Theorem 3 (Reynolds Operator for Reductive Groups)</strong></span> If <img src="https://latex.codecogs.com/png.latex?G"> is a reductive group acting on a vector space <img src="https://latex.codecogs.com/png.latex?V"> over a field <img src="https://latex.codecogs.com/png.latex?k"> (of characteristic zero), then there exists a Reynolds operator <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D:%20k%5BV%5D%20%5Cto%20k%5BV%5D%5EG">.</p>
</div>
<p><em>Proof.</em> If <img src="https://latex.codecogs.com/png.latex?G"> is reductive, then by definition every representation is completely reducible. In particular, each graded piece <img src="https://latex.codecogs.com/png.latex?k%5BV%5D_d"> (which in this case are the polynomials of degree <img src="https://latex.codecogs.com/png.latex?d">) decomposes as a direct sum of irreducible representations, one of which is the invariant subspace <img src="https://latex.codecogs.com/png.latex?k%5BV%5D_d%5EG">. The invariant subspace <img src="https://latex.codecogs.com/png.latex?k%5BV%5D_d%5EG"> is the sum of all copies of the trivial representation in this decomposition (since that’s where <img src="https://latex.codecogs.com/png.latex?g%5Ccdot%20v%20=%20v"> for all <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">). Complete reducibility guarantees that this summand has a complement and that the projection onto it is unique. Applied degree by degree, this projection is just the Reynolds operator. We will also see later (once we have the <a href="../../../symmetry/posts/invariant_theory/index.html#hilberts-basis-theorem">Hilbert basis theorem</a>) that this is enough to guarantee that the invariant ring is finitely generated.</p>
<p>The reductive groups have been completely classified<sup>5</sup>. They include all finite groups, compact Lie groups, and <img src="https://latex.codecogs.com/png.latex?GL(n)">, <img src="https://latex.codecogs.com/png.latex?SL(n)">, <img src="https://latex.codecogs.com/png.latex?O(n)">, <img src="https://latex.codecogs.com/png.latex?Sp(n)"> over characteristic zero. I won’t go into the details here. It will suffice for our purpose to simply know that we can check the list to see if a given group is reductive or not<sup>6</sup>. See <a href="../../../symmetry/posts/invariant_theory/index.html#reductivity-of-gln">below</a> for the details on <img src="https://latex.codecogs.com/png.latex?GL(n)">.</p>
<section id="definition-of-reductivity" class="level3">
<h3 class="anchored" data-anchor-id="definition-of-reductivity">Definition of Reductivity</h3>
<div id="def-reductive" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 8 (Reductive)</strong></span> A group <img src="https://latex.codecogs.com/png.latex?G"> is reductive if every finite-dimensional representation of <img src="https://latex.codecogs.com/png.latex?G"> is completely reducible. That is, a group <img src="https://latex.codecogs.com/png.latex?G"> is reductive if for every homomorphism <img src="https://latex.codecogs.com/png.latex?%5Crho:%20G%20%5Cto%20GL(V)">, the representation <img src="https://latex.codecogs.com/png.latex?V"> decomposes as a direct sum of irreducible representations.</p>
</div>
<p>Here’s some reductive groups (over fields of characteristic zero):</p>
<ul>
<li>All finite groups (the Reynolds operator <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D(f)%20=%20%5Cfrac%7B1%7D%7B%7CG%7C%7D%5Csum%20g%20%5Ccdot%20f"> projects the space onto its invariants).</li>
<li>All compact Lie groups (same argument, with integration replacing the sum).</li>
<li>The classical groups: <img src="https://latex.codecogs.com/png.latex?GL(n)">, <img src="https://latex.codecogs.com/png.latex?SL(n)">, <img src="https://latex.codecogs.com/png.latex?O(n)">, <img src="https://latex.codecogs.com/png.latex?Sp(n)">.</li>
</ul>
<p>The additive group <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BG%7D_a"> is not reductive.</p>
<p>What we’ve done so far is enough to show all of the groups that we claimed above were reductive, except <img src="https://latex.codecogs.com/png.latex?SL(n)"> and <img src="https://latex.codecogs.com/png.latex?GL(n)"> (as they are not compact). However, we can show that <img src="https://latex.codecogs.com/png.latex?GL(n)"> is reductive by showing that it contains a compact subgroup (the unitary group <img src="https://latex.codecogs.com/png.latex?U(n)">) such that every representation of <img src="https://latex.codecogs.com/png.latex?GL(n)"> restricts to a representation of <img src="https://latex.codecogs.com/png.latex?U(n)"> that is completely reducible.</p>
</section>
<section id="reductivity-of-gln" class="level3">
<h3 class="anchored" data-anchor-id="reductivity-of-gln">Reductivity of <img src="https://latex.codecogs.com/png.latex?GL(n)"></h3>
<div id="thm-reductivity-gl" class="theorem">
<p><span class="theorem-title"><strong>Theorem 4 (Reductivity of <img src="https://latex.codecogs.com/png.latex?GL(n)">)</strong></span> <img src="https://latex.codecogs.com/png.latex?GL(n)"> is reductive.</p>
</div>
<p><em>Proof (Based on Weyl’s Unitary Trick).</em></p>
<p>We will borrow some theorems from linear algebra to do this proof.</p>
<p>Any matrix <img src="https://latex.codecogs.com/png.latex?a%20%5Cin%20GL(n)"> can be written as <img src="https://latex.codecogs.com/png.latex?a%20=%20up">, where <img src="https://latex.codecogs.com/png.latex?u%20%5Cin%20U(n)"> is unitary and <img src="https://latex.codecogs.com/png.latex?p"> is positive-definite Hermitian. This is the polar decomposition of <img src="https://latex.codecogs.com/png.latex?a">.</p>
<p>The unitary group <img src="https://latex.codecogs.com/png.latex?U(n)"> is compact, so by Maschke’s theorem for compact groups, every representation of <img src="https://latex.codecogs.com/png.latex?U(n)"> is completely reducible.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?U(n)"> is a subgroup of <img src="https://latex.codecogs.com/png.latex?GL(n)">, any representation of <img src="https://latex.codecogs.com/png.latex?GL(n)"> restricts to a representation of <img src="https://latex.codecogs.com/png.latex?U(n)">. Since the representation of <img src="https://latex.codecogs.com/png.latex?U(n)"> is completely reducible, it decomposes as a direct sum of irreducible representations of <img src="https://latex.codecogs.com/png.latex?U(n)">.</p>
<p>Next we will show:</p>
<p>If <img src="https://latex.codecogs.com/png.latex?%5Crho:%20GL(n)%20%5Cto%20GL(V)"> is a representation of <img src="https://latex.codecogs.com/png.latex?GL(n)">, and <img src="https://latex.codecogs.com/png.latex?T:%20V%5Cto%20V"> is a <img src="https://latex.codecogs.com/png.latex?U(n)">-equivariant map, then <img src="https://latex.codecogs.com/png.latex?T"> is also <img src="https://latex.codecogs.com/png.latex?GL(n)">-equivariant.</p>
<p>If we can do this, then by Schur’s lemma, the projection of <img src="https://latex.codecogs.com/png.latex?V"> onto each irreducible summand of the <img src="https://latex.codecogs.com/png.latex?U(n)">-representation is unique up to scaling, and since these projections are also <img src="https://latex.codecogs.com/png.latex?GL(n)">-equivariant, they are also projections onto irreducible summands of the <img src="https://latex.codecogs.com/png.latex?GL(n)">-representation. So the decomposition of <img src="https://latex.codecogs.com/png.latex?V"> into irreducible representations of <img src="https://latex.codecogs.com/png.latex?U(n)"> is also a decomposition into irreducible representations of <img src="https://latex.codecogs.com/png.latex?GL(n)">, and thus every representation of <img src="https://latex.codecogs.com/png.latex?GL(n)"> is completely reducible.</p>
<p>We already know that every <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20GL(n,%20%5Cmathbb%7BC%7D)"> can be written as <img src="https://latex.codecogs.com/png.latex?g%20=%20up"> for some <img src="https://latex.codecogs.com/png.latex?u%20%5Cin%20U(n)"> and <img src="https://latex.codecogs.com/png.latex?p"> positive-definite Hermitian.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?T"> commutes with the representation <img src="https://latex.codecogs.com/png.latex?%5Crho(u)">, we just need to show that <img src="https://latex.codecogs.com/png.latex?T"> also commutes with the representation <img src="https://latex.codecogs.com/png.latex?%5Crho(p)">.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?p"> is positive-definite Hermitian, it can be diagonalized by a unitary matrix. So we can write <img src="https://latex.codecogs.com/png.latex?p%20=%20vdv%5E%7B-1%7D">, where <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20U(n)"> and <img src="https://latex.codecogs.com/png.latex?d"> is a diagonal matrix with positive real entries on the diagonal.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?T"> commutes with the representation of <img src="https://latex.codecogs.com/png.latex?%5Crho(v)">, we just need to show that <img src="https://latex.codecogs.com/png.latex?T"> also commutes with the representation <img src="https://latex.codecogs.com/png.latex?%5Crho(d)"> (positive diagonal matrices).</p>
<p>Now we will show: if <img src="https://latex.codecogs.com/png.latex?T"> commutes with the representation <img src="https://latex.codecogs.com/png.latex?%5Crho(u)">, then it must commute with the representation <img src="https://latex.codecogs.com/png.latex?%5Crho(d)"> for all positive diagonal matrices <img src="https://latex.codecogs.com/png.latex?d">.</p>
<p>We can write <img src="https://latex.codecogs.com/png.latex?d%20=%20%5Cexp(h)"> for some diagonal matrix <img src="https://latex.codecogs.com/png.latex?h"> with real entries on the diagonal.</p>
<p>Because the representation <img src="https://latex.codecogs.com/png.latex?%5Crho"> is polynomial (hence analytic), the map <img src="https://latex.codecogs.com/png.latex?t%20%5Cmapsto%20%5Crho(%5Cexp(th))"> is a differentiable one-parameter subgroup of <img src="https://latex.codecogs.com/png.latex?GL(V)">. Since <img src="https://latex.codecogs.com/png.latex?T"> commutes with <img src="https://latex.codecogs.com/png.latex?%5Crho(%5Cexp(ith))"> for all real <img src="https://latex.codecogs.com/png.latex?t"> (these are diagonal unitary matrices), differentiating at <img src="https://latex.codecogs.com/png.latex?t=0"> shows that <img src="https://latex.codecogs.com/png.latex?T"> also commutes with the infinitesimal action of <img src="https://latex.codecogs.com/png.latex?h">. Exponentiating again implies that <img src="https://latex.codecogs.com/png.latex?T"> commutes with <img src="https://latex.codecogs.com/png.latex?%5Crho(%5Cexp(th))"> for all <img src="https://latex.codecogs.com/png.latex?t">, hence with all positive diagonal matrices. By unitary conjugation it therefore commutes with <img src="https://latex.codecogs.com/png.latex?%5Crho(p)"> for every positive-definite Hermitian matrix <img src="https://latex.codecogs.com/png.latex?p">. Since every <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20GL(n,%20%5Cmathbb%7BC%7D)"> can be written as <img src="https://latex.codecogs.com/png.latex?g%20=%20up"> with <img src="https://latex.codecogs.com/png.latex?u%20%5Cin%20U(n)"> and <img src="https://latex.codecogs.com/png.latex?p"> positive-definite Hermitian, <img src="https://latex.codecogs.com/png.latex?T"> commutes with <img src="https://latex.codecogs.com/png.latex?%5Crho(g)"> for all <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20GL(n)">.</p>
<p>Thus every <img src="https://latex.codecogs.com/png.latex?U(n)">-equivariant map is <img src="https://latex.codecogs.com/png.latex?GL(n)">-equivariant. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
</section>
<section id="tensor-products" class="level3">
<h3 class="anchored" data-anchor-id="tensor-products">Tensor Products</h3>
<p>We are interested in maps between binary forms of different degrees, as we are trying to understand binary forms under change of basis by <img src="https://latex.codecogs.com/png.latex?GL(2)">. For example, given two binary forms <img src="https://latex.codecogs.com/png.latex?Q_1%20%5Cin%20V(m)"> and <img src="https://latex.codecogs.com/png.latex?Q_2%20%5Cin%20V(n)">, we might want to construct a covariant of degree <img src="https://latex.codecogs.com/png.latex?d"> from them. We can think of this as constructing a <img src="https://latex.codecogs.com/png.latex?GL(2)">-equivariant map from <img src="https://latex.codecogs.com/png.latex?V(m)%20%5Cotimes%20V(n)"> to <img src="https://latex.codecogs.com/png.latex?V(d)">, since any polynomial built from <img src="https://latex.codecogs.com/png.latex?Q_1"> and <img src="https://latex.codecogs.com/png.latex?Q_2"> lives in the tensor product <img src="https://latex.codecogs.com/png.latex?V(m)%20%5Cotimes%20V(n)">.</p>
<p>Representations are closed under both direct sums and tensor products.</p>
<p>If <img src="https://latex.codecogs.com/png.latex?V"> and <img src="https://latex.codecogs.com/png.latex?W"> are representations of <img src="https://latex.codecogs.com/png.latex?G">, then their direct sum <img src="https://latex.codecogs.com/png.latex?V%20%5Coplus%20W"> is also a representation, with <img src="https://latex.codecogs.com/png.latex?G"> acting componentwise: <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20(v,%20w)%20=%20(g%20%5Ccdot%20v,%20g%20%5Ccdot%20w)">.</p>
<p>Tensor products are more interesting. If <img src="https://latex.codecogs.com/png.latex?V"> and <img src="https://latex.codecogs.com/png.latex?W"> are representations of <img src="https://latex.codecogs.com/png.latex?G">, then their tensor product <img src="https://latex.codecogs.com/png.latex?V%20%5Cotimes%20W"> is also a representation of <img src="https://latex.codecogs.com/png.latex?G">, with the action defined by <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20(v%20%5Cotimes%20w)%20=%20(g%20%5Ccdot%20v)%20%5Cotimes%20(g%20%5Ccdot%20w)">.</p>
<div id="lem-tensor-product" class="theorem lemma">
<p><span class="theorem-title"><strong>Lemma 2 (Tensor Product of Representations)</strong></span> If <img src="https://latex.codecogs.com/png.latex?V"> and <img src="https://latex.codecogs.com/png.latex?W"> are representations of a group <img src="https://latex.codecogs.com/png.latex?G">, then their tensor product <img src="https://latex.codecogs.com/png.latex?V%20%5Cotimes%20W"> is also a representation of <img src="https://latex.codecogs.com/png.latex?G">, with the action defined by <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20(v%20%5Cotimes%20w)%20=%20(g%20%5Ccdot%20v)%20%5Cotimes%20(g%20%5Ccdot%20w)">.</p>
</div>
<p><em>Proof.</em> We need to check that if <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20v"> is a representation of <img src="https://latex.codecogs.com/png.latex?G">, and <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20w"> is a representation of <img src="https://latex.codecogs.com/png.latex?G">, then <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20(v%20%5Cotimes%20w)"> is a representation of <img src="https://latex.codecogs.com/png.latex?G">. For any <img src="https://latex.codecogs.com/png.latex?g,%20h%20%5Cin%20G"> and <img src="https://latex.codecogs.com/png.latex?v%20%5Cin%20V">, <img src="https://latex.codecogs.com/png.latex?w%20%5Cin%20W">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(g%20%5Ccdot%20(h%20%5Ccdot%20(v%20%5Cotimes%20w))%20=%20g%20%5Ccdot%20((h%20%5Ccdot%20v)%20%5Cotimes%20(h%20%5Ccdot%20w))%20=%20(g%20%5Ccdot%20(h%20%5Ccdot%20v))%20%5Cotimes%20(g%20%5Ccdot%20(h%20%5Ccdot%20w))%20=%20((gh)%20%5Ccdot%20v)%20%5Cotimes%20((gh)%20%5Ccdot%20w)%20=%20(gh)%20%5Ccdot%20(v%20%5Cotimes%20w))%0A"></p>
<p>Also <img src="https://latex.codecogs.com/png.latex?e%20%5Ccdot%20(v%20%5Cotimes%20w)%20=%20(e%20%5Ccdot%20v)%20%5Cotimes%20(e%20%5Ccdot%20w)%20=%20v%20%5Cotimes%20w">. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
</section>
<section id="the-clebsch-gordan-decomposition" class="level3">
<h3 class="anchored" data-anchor-id="the-clebsch-gordan-decomposition">The Clebsch-Gordan Decomposition</h3>
<p>Unfortunately, even if <img src="https://latex.codecogs.com/png.latex?V"> and <img src="https://latex.codecogs.com/png.latex?W"> are irreducible, <img src="https://latex.codecogs.com/png.latex?V%20%5Cotimes%20W"> is not necessarily irreducible. Instead, it decomposes as a direct sum of irreducible representations. The problem of determining how <img src="https://latex.codecogs.com/png.latex?V%20%5Cotimes%20W"> decomposes into irreducibles is called the Clebsch-Gordan problem.</p>
<section id="motivation" class="level4">
<h4 class="anchored" data-anchor-id="motivation">Motivation</h4>
<p>Why do we care about this? We know binary forms of degree <img src="https://latex.codecogs.com/png.latex?n"> live in:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(n)%20:=%20%5Cmathrm%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)%0A"></p>
<p>So given two binary forms of degree <img src="https://latex.codecogs.com/png.latex?m"> and <img src="https://latex.codecogs.com/png.latex?n">, then any polynomials built from <img src="https://latex.codecogs.com/png.latex?Q_1%20%5Cin%20V(m)">, <img src="https://latex.codecogs.com/png.latex?Q_2%20%5Cin%20V(n)"> live in the tensor product <img src="https://latex.codecogs.com/png.latex?V(m)%20%5Cotimes%20V(n)">.</p>
<p>Covariants constructed from <img src="https://latex.codecogs.com/png.latex?Q_1"> and <img src="https://latex.codecogs.com/png.latex?Q_2"> come from from <img src="https://latex.codecogs.com/png.latex?GL(2)">-equivariant maps</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m)%20%5Cotimes%20V(n)%20%5Cto%20V(d)%0A"></p>
<p>How can we find the irreducible representations <img src="https://latex.codecogs.com/png.latex?V(d)"> that appear in the decomposition of <img src="https://latex.codecogs.com/png.latex?V(m)%20%5Cotimes%20V(n)">?</p>
<p>Start<sup>7</sup> by viewing an element of</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m)%5Cotimes%20V(n)%0A"></p>
<p>as a polynomial in two pairs of variables, <img src="https://latex.codecogs.com/png.latex?(x_1,y_1)">, <img src="https://latex.codecogs.com/png.latex?(x_2,y_2)"> that is homogeneous of degree <img src="https://latex.codecogs.com/png.latex?m"> in <img src="https://latex.codecogs.com/png.latex?(x_1,y_1)"> and degree <img src="https://latex.codecogs.com/png.latex?n"> in <img src="https://latex.codecogs.com/png.latex?(x_2,y_2)">.</p>
<p>So:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m)%5Cotimes%20V(n)%0A%5Ccong%0Ak%5Bx_1,y_1,x_2,y_2%5D_%7Bm,n%7D%0A"></p>
<p>(The space of bihomogeneous polynomials of bidegree <img src="https://latex.codecogs.com/png.latex?(m,n)">).</p>
<p>The group <img src="https://latex.codecogs.com/png.latex?SL(2)"> (as a stand-in for <img src="https://latex.codecogs.com/png.latex?GL(2)">) act on both pairs simultaneously by linear substitution.</p>
</section>
<section id="diagonal-restriction" class="level4">
<h4 class="anchored" data-anchor-id="diagonal-restriction">Diagonal Restriction</h4>
<p>Let <img src="https://latex.codecogs.com/png.latex?%5Cmu"> be an <img src="https://latex.codecogs.com/png.latex?SL(2)">-equivariant map</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmu%20:%20V(m)%5Cotimes%20V(n)%20%5Cto%20V(m+n)%0A"></p>
<p>obtained by identifying the two pairs of variables:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmu(f(x_1,y_1;x_2,y_2))%0A=%0Af(x,y;x,y)%0A"></p>
<p>In other words, we restrict the polynomial to the diagonal</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(x_1,y_1)%20=%20(x_2,y_2)%0A"></p>
<p>The image consists exactly of homogeneous polynomials of degree <img src="https://latex.codecogs.com/png.latex?m+n">, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7Bim%7D(%5Cmu)%20=%20V(m+n)%0A"></p>
</section>
</section>
<section id="the-kernel" class="level3">
<h3 class="anchored" data-anchor-id="the-kernel">The Kernel</h3>
<p>Which bihomogeneous polynomials vanish on the diagonal?</p>
<p>The diagonal in <img src="https://latex.codecogs.com/png.latex?(%5Cmathbb%20C%5E2)%5E2"> is defined by the equation</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_1y_2%20-%20y_1x_2%20=%200%0A"></p>
<p>Denote this determinant by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5B12%5D%20:=%20x_1y_2%20-%20y_1x_2%0A"></p>
<p>Any polynomial that vanishes on the diagonal must therefore be divisible by <img src="https://latex.codecogs.com/png.latex?%5B12%5D">.</p>
<p>(Since <img src="https://latex.codecogs.com/png.latex?%5B12%5D"> is linear in each pair of variables, it is irreducible. The quotient ring <img src="https://latex.codecogs.com/png.latex?k%5Bx_1,y_1,x_2,y_2%5D/(%5B12%5D)"> is therefore a domain, so if <img src="https://latex.codecogs.com/png.latex?f"> vanishes wherever <img src="https://latex.codecogs.com/png.latex?%5B12%5D"> does, then <img src="https://latex.codecogs.com/png.latex?f%20%5Cequiv%200"> in this quotient, meaning <img src="https://latex.codecogs.com/png.latex?%5B12%5D"> divides <img src="https://latex.codecogs.com/png.latex?f">.)</p>
<p>Thus</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cker(%5Cmu)%20=%20%5B12%5D%5Ccdot%20k%5Bx_1,y_1,x_2,y_2%5D_%7Bm-1,n-1%7D%0A"></p>
<p>Multiplication by <img src="https://latex.codecogs.com/png.latex?%5B12%5D"> raises the degree in each pair by one, so this space is naturally isomorphic to</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m-1)%5Cotimes%20V(n-1)%0A"></p>
<p>We therefore obtain an exact sequence</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A0%0A%5Cto%0AV(m-1)%5Cotimes%20V(n-1)%0A%5Cto%0AV(m)%5Cotimes%20V(n)%0A%5Cto%0AV(m+n)%0A%5Cto%0A0%0A"></p>
<section id="iterating-the-construction" class="level4">
<h4 class="anchored" data-anchor-id="iterating-the-construction">Iterating the Construction</h4>
<p>Applying the same argument to <img src="https://latex.codecogs.com/png.latex?V(m-1)%5Cotimes%20V(n-1)"> yields</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A0%0A%5Cto%0AV(m-2)%5Cotimes%20V(n-2)%0A%5Cto%0AV(m-1)%5Cotimes%20V(n-1)%0A%5Cto%0AV(m+n-2)%0A%5Cto%0A0%0A"></p>
<p>Continuing inductively produces a filtration whose successive quotients are</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m+n),%5C;%0AV(m+n-2),%5C;%0AV(m+n-4),%5C;%0A%5Cdots%0A"></p>
<p>until the process terminates after <img src="https://latex.codecogs.com/png.latex?m"> steps (since we can’t have negative degree).</p>
</section>
<section id="clebschgordan-decomposition" class="level4">
<h4 class="anchored" data-anchor-id="clebschgordan-decomposition">Clebsch–Gordan Decomposition</h4>
<div id="thm-clebsch-gordan" class="theorem">
<p><span class="theorem-title"><strong>Theorem 5 (Clebsch–Gordan Decomposition)</strong></span> For <img src="https://latex.codecogs.com/png.latex?m%20%5Cle%20n">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m)%5Cotimes%20V(n)%0A%5Ccong%0A%5Cbigoplus_%7Br=0%7D%5E%7Bm%7D%20V(m+n-2r)%0A"></p>
<p>Equivalently,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7BSym%7D%5Em(%5Cmathbb%20C%5E2)%5Cotimes%0A%5Cmathrm%7BSym%7D%5En(%5Cmathbb%20C%5E2)%0A%5Ccong%0A%5Cbigoplus_%7Br=0%7D%5E%7Bm%7D%0A%5Cmathrm%7BSym%7D%5E%7Bm+n-2r%7D(%5Cmathbb%20C%5E2)%0A"></p>
</div>
<p><em>Proof.</em></p>
<p>We have already shown that <img src="https://latex.codecogs.com/png.latex?V(m+n-2r)"> appears as a quotient in the filtration of <img src="https://latex.codecogs.com/png.latex?V(m)%5Cotimes%20V(n)"> for each <img src="https://latex.codecogs.com/png.latex?r%20=%200,1,%5Cldots,m">.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?SL(2)"> is reductive, each short exact sequence in this filtration splits, so these quotients appear as <img src="https://latex.codecogs.com/png.latex?SL(2)">-stable direct summands and we obtain an <img src="https://latex.codecogs.com/png.latex?SL(2)">-equivariant inclusion</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbigoplus_%7Br=0%7D%5E%7Bm%7D%20V(m+n-2r)%20%5Csubseteq%20V(m)%5Cotimes%20V(n)%0A"></p>
<p>Finally, observe that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Csum_%7Br=0%7D%5E%7Bm%7D%20(m+n-2r+1)%0A=%0A(m+1)(n+1)%0A=%0A%5Cdim%5Cbigl(V(m)%5Cotimes%20V(n)%5Cbigr)%0A"></p>
<p>so the inclusion is an equality. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>The Clebsch–Gordan decomposition tells us exactly which irreducible representations occur inside the tensor product <img src="https://latex.codecogs.com/png.latex?V(m)%5Cotimes%20V(n)">. Each representation</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m+n-2r)%0A"></p>
<p>appears once.</p>
<p>As a consequence, any <img src="https://latex.codecogs.com/png.latex?SL(2)">–equivariant linear map</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m)%5Cotimes%20V(n)%20%5Cto%20V(m+n-2r)%0A"></p>
<p>must be unique up to a scalar multiple.</p>
<p>As we have already seen, by Schur’s lemma the space of equivariant maps between two irreducible representations is one–dimensional when the representations are isomorphic and zero otherwise.</p>
<p>Thus, by the Clebsch–Gordan decomposition, for each <img src="https://latex.codecogs.com/png.latex?r"> there exists a unique canonical equivariant projection</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(m)%5Cotimes%20V(n)%20%5Cto%20V(m+n-2r)%0A"></p>
<p>(up to scaling).</p>
<p>We call these projections the transvectants. They are the building blocks of all <img src="https://latex.codecogs.com/png.latex?SL(2)">-equivariant maps between symmetric powers (and, as we will see, the building blocks of all covariants of binary forms).</p>
</section>
</section>
<section id="computing-transvectants" class="level3">
<h3 class="anchored" data-anchor-id="computing-transvectants">Computing Transvectants</h3>
<p>Writing the binary forms as <img src="https://latex.codecogs.com/png.latex?Q_1%20%5Cin%20%5Ctext%7BSym%7D%5Em(%5Cmathbb%7BC%7D%5E2)"> and <img src="https://latex.codecogs.com/png.latex?Q_2%20%5Cin%20%5Ctext%7BSym%7D%5En(%5Cmathbb%7BC%7D%5E2)">, the projection onto <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5E%7Bm+n-2r%7D(%5Cmathbb%7BC%7D%5E2)"> is (up to normalization) the <img src="https://latex.codecogs.com/png.latex?r">-th transvectant is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(Q_1,%20Q_2)%5E%7B(r)%7D%20=%20%5Csum_%7Bk=0%7D%5Er%20(-1)%5Ek%20%5Cbinom%7Br%7D%7Bk%7D%20%5Cfrac%7B%5Cpartial%5Er%20Q_1%7D%7B%5Cpartial%20x%5E%7Br-k%7D%20%5Cpartial%20y%5Ek%7D%20%5Ccdot%20%5Cfrac%7B%5Cpartial%5Er%20Q_2%7D%7B%5Cpartial%20x%5Ek%20%5Cpartial%20y%5E%7Br-k%7D%7D%0A"></p>
<p>To see this, note that this formula is manifestly <img src="https://latex.codecogs.com/png.latex?SL(2)">-equivariant<sup>8</sup> and maps <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSym%7D%5Em%20%5Cotimes%20%5Ctext%7BSym%7D%5En%20%5Cto%20%5Ctext%7BSym%7D%5E%7Bm+n-2r%7D"> (each differentiation reduces degree by 1, and we differentiate <img src="https://latex.codecogs.com/png.latex?r"> times in each factor).</p>
<p>By Schur’s lemma, any equivariant map between these spaces is unique up to scalar, so the transvectant must be the Clebsch-Gordan projection (up to normalization).</p>
<p>The first transvectant (<img src="https://latex.codecogs.com/png.latex?r%20=%201">) is the Jacobian:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5BQ_1,%20Q_2%5D%20:=%20(Q_1,%20Q_2)%5E%7B(1)%7D%20=%20%5Cfrac%7B%5Cpartial%20Q_1%7D%7B%5Cpartial%20x%7D%20%5Cfrac%7B%5Cpartial%20Q_2%7D%7B%5Cpartial%20y%7D%20-%20%5Cfrac%7B%5Cpartial%20Q_1%7D%7B%5Cpartial%20y%7D%20%5Cfrac%7B%5Cpartial%20Q_2%7D%7B%5Cpartial%20x%7D%0A"></p>
<p>The second self-transvectant (<img src="https://latex.codecogs.com/png.latex?Q_1%20=%20Q_2%20=%20Q">, <img src="https://latex.codecogs.com/png.latex?r%20=%202">) gives the Hessian (up to a factor of 2):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(Q,%20Q)%5E%7B(2)%7D%20=%202%5Cleft(%5Cfrac%7B%5Cpartial%5E2%20Q%7D%7B%5Cpartial%20x%5E2%7D%20%5Cfrac%7B%5Cpartial%5E2%20Q%7D%7B%5Cpartial%20y%5E2%7D%20-%20%5Cleft(%5Cfrac%7B%5Cpartial%5E2%20Q%7D%7B%5Cpartial%20x%20%5Cpartial%20y%7D%5Cright)%5E2%5Cright)%0A"></p>
<p>Since the Clebsch-Gordan decomposition is complete, the transvectants cover all <img src="https://latex.codecogs.com/png.latex?SL(2)">-equivariant pairings between symmetric powers.</p>
</section>
<section id="first-fundamental-theorem-of-invariants-for-binary-forms" class="level3">
<h3 class="anchored" data-anchor-id="first-fundamental-theorem-of-invariants-for-binary-forms">First Fundamental Theorem of Invariants for Binary Forms</h3>
<p>We know the covariant ring is finitely generated, but what are the generators? We need the First Fundamental Theorem for Binary Forms under <img src="https://latex.codecogs.com/png.latex?GL(2)">. This theorem states essentially that every polynomial covariant of a system of binary forms can be written as polynomial in the transvectants of that system. This means that if we can generate all the transvectants, then we can generate all the covariants.</p>
<div id="thm-first-fundamental-theorem" class="theorem">
<p><span class="theorem-title"><strong>Theorem 6 (First Fundamental Theorem)</strong></span> Let <img src="https://latex.codecogs.com/png.latex?(x_1,y_1),%5Cdots,(x_p,y_p)"> be <img src="https://latex.codecogs.com/png.latex?p"> copies of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%20C%5E2"> with the diagonal action of <img src="https://latex.codecogs.com/png.latex?GL(2)">, and define <img src="https://latex.codecogs.com/png.latex?%0A%5Bij%5D%20=%20x_i%20y_j%20-%20y_i%20x_j%0A"></p>
<p>Then the invariant ring <img src="https://latex.codecogs.com/png.latex?%0Ak%5Bx_1,y_1,%5Cdots,x_p,y_p%5D%5E%7BSL(2)%7D%0A"></p>
<p>is generated by the brackets <img src="https://latex.codecogs.com/png.latex?%5Bij%5D">.</p>
</div>
<p><em>Proof.</em></p>
<p>Let</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA%20=%20k%5Bx_1,y_1,%5Cdots,x_p,y_p%5D%0A"></p>
<p>with the diagonal action of <img src="https://latex.codecogs.com/png.latex?SL(2)"> on each pair <img src="https://latex.codecogs.com/png.latex?(x_i,y_i)">. Define</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Bij%5D%20:=%20x_i%20y_j%20-%20y_i%20x_j%0A"></p>
<p>(You can think of <img src="https://latex.codecogs.com/png.latex?%5Bij%5D"> as the determinant of the <img src="https://latex.codecogs.com/png.latex?2%5Ctimes%202"> matrix formed by the <img src="https://latex.codecogs.com/png.latex?i">-th and <img src="https://latex.codecogs.com/png.latex?j">-th columns of the matrix of variables).</p>
<ol type="1">
<li><em>Each <img src="https://latex.codecogs.com/png.latex?%5Bij%5D"> is <img src="https://latex.codecogs.com/png.latex?SL(2)">-invariant.</em></li>
</ol>
<p>If <img src="https://latex.codecogs.com/png.latex?v_i=(x_i,y_i)%5ET"> and <img src="https://latex.codecogs.com/png.latex?g%5Cin%20SL(2)">, then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Bij%5D(gv_1,%5Cdots,gv_p)=%5Cdet(gv_i,gv_j)=%5Cdet(g)%5Cdet(v_i,v_j)=%5Cdet(v_i,v_j)=%5Bij%5D(v_1,%5Cdots,v_p)%0A"></p>
<p>So <img src="https://latex.codecogs.com/png.latex?k%5Cbigl%5B%5C,%20%5Bij%5D%20%5Cmid%201%20%5Cle%20i%20%3C%20j%20%5Cle%20p%20%5C,%5Cbigr%5D%5Csubseteq%20A%5E%7BSL(2)%7D">.</p>
<ol start="2" type="1">
<li><em>Normalize two columns using an explicit <img src="https://latex.codecogs.com/png.latex?SL(2)"> matrix.</em></li>
</ol>
<p>Fix <img src="https://latex.codecogs.com/png.latex?(1,2)"> and assume <img src="https://latex.codecogs.com/png.latex?%5B12%5D%5Cneq%200">. Set</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS=%5Cbegin%7Bpmatrix%7Dx_1%20&amp;%20x_2%5C%5C%20y_1%20&amp;%20y_2%5Cend%7Bpmatrix%7D%0A"></p>
<p>Then <img src="https://latex.codecogs.com/png.latex?%5Cdet(S)=%5B12%5D">. Define</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA_%7B12%7D:=%0A%5Cbegin%7Bpmatrix%7D1/%5B12%5D%20&amp;%200%5C%5C%200%20&amp;%201%5Cend%7Bpmatrix%7D%5Coperatorname%7Badj%7D(S)%0A"></p>
<p>Since <img src="https://latex.codecogs.com/png.latex?%5Cdet(%5Coperatorname%7Badj%7D(S))=%5Cdet(S)=%5B12%5D"> and <img src="https://latex.codecogs.com/png.latex?%5Cdet%5C!%5Cbegin%7Bpmatrix%7D1/%5B12%5D%20&amp;%200%5C%5C%200%20&amp;%201%5Cend%7Bpmatrix%7D=1/%5B12%5D">, we have <img src="https://latex.codecogs.com/png.latex?%5Cdet(A_%7B12%7D)=1">, so <img src="https://latex.codecogs.com/png.latex?A_%7B12%7D%5Cin%20SL(2)"> whenever <img src="https://latex.codecogs.com/png.latex?%5B12%5D%5Cneq%200">.</p>
<p>Also <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Badj%7D(S)%5C,S=%5B12%5DI">, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA_%7B12%7DS=%5Cbegin%7Bpmatrix%7D1%20&amp;%200%5C%5C%200%20&amp;%20%5B12%5D%5Cend%7Bpmatrix%7D%0A"></p>
<p>Equivalently,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA_%7B12%7Dv_1=e_1%0A"> <img src="https://latex.codecogs.com/png.latex?%0AA_%7B12%7Dv_2=%5B12%5D%5C,e_2%0A"></p>
<p>For <img src="https://latex.codecogs.com/png.latex?k%5Cge%203">, write</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA_%7B12%7Dv_k=%5Cbegin%7Bpmatrix%7Da_k%5C%5C%20b_k%5Cend%7Bpmatrix%7D%0A"></p>
<p>Because <img src="https://latex.codecogs.com/png.latex?%5Cdet(A_%7B12%7D)=1">, brackets are unchanged under <img src="https://latex.codecogs.com/png.latex?A_%7B12%7D">, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5B1k%5D=%5Cdet(v_1,v_k)=%5Cdet(A_%7B12%7Dv_1,A_%7B12%7Dv_k)=%5Cdet%5C!%5Cleft(e_1,%5Cbegin%7Bpmatrix%7Da_k%5C%5C%20b_k%5Cend%7Bpmatrix%7D%5Cright)=b_k%0A"></p>
<p>and</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5B2k%5D=%5Cdet(v_2,v_k)=%5Cdet(A_%7B12%7Dv_2,A_%7B12%7Dv_k)=%5Cdet%5C!%5Cleft(%5B12%5De_2,%5Cbegin%7Bpmatrix%7Da_k%5C%5C%20b_k%5Cend%7Bpmatrix%7D%5Cright)=-%5B12%5D%5C,a_k%0A"></p>
<p>Hence</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ab_k=%5B1k%5D%0A"> <img src="https://latex.codecogs.com/png.latex?%0Aa_k=-%5Cfrac%7B%5B2k%5D%7D%7B%5B12%5D%7D%0A"></p>
<p>So, after applying <img src="https://latex.codecogs.com/png.latex?A_%7B12%7D">, the normalized matrix <img src="https://latex.codecogs.com/png.latex?A_%7B12%7DM"> is determined by the bracket data <img src="https://latex.codecogs.com/png.latex?%5B12%5D">, <img src="https://latex.codecogs.com/png.latex?%5B1k%5D">, <img src="https://latex.codecogs.com/png.latex?%5B2k%5D">.</p>
<ol start="3" type="1">
<li><em>An invariant polynomial is a polynomial in the brackets.</em></li>
</ol>
<p>Let <img src="https://latex.codecogs.com/png.latex?f%5Cin%20A%5E%7BSL(2)%7D">. For any point with <img src="https://latex.codecogs.com/png.latex?%5B12%5D%5Cneq%200">, invariance gives</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af(M)=f(A_%7B12%7DM)%0A"></p>
<p>But <img src="https://latex.codecogs.com/png.latex?A_%7B12%7DM"> has entries that are rational functions of the brackets (the only denominators are powers of <img src="https://latex.codecogs.com/png.latex?%5B12%5D">), so on the region <img src="https://latex.codecogs.com/png.latex?%5B12%5D%5Cneq%200"> we can write</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af(M)=%5Cfrac%7BP(%5Bij%5D)%7D%7B%5B12%5D%5EN%7D%0A"></p>
<p>for some polynomial <img src="https://latex.codecogs.com/png.latex?P"> and some <img src="https://latex.codecogs.com/png.latex?N%5Cge%200">.</p>
<p>Multiply both sides by <img src="https://latex.codecogs.com/png.latex?%5B12%5D%5EN">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5B12%5D%5EN%20f(M)=P(%5Bij%5D)%0A"></p>
<p>Both sides are polynomials in the coordinates <img src="https://latex.codecogs.com/png.latex?(x_r,y_r)">. Since the identity holds whenever <img src="https://latex.codecogs.com/png.latex?%5B12%5D%5Cneq%200">, it also holds identically as a polynomial identity. In particular, the right-hand side is divisible by <img src="https://latex.codecogs.com/png.latex?%5B12%5D%5EN"> in <img src="https://latex.codecogs.com/png.latex?A">, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af(M)=Q(%5Bij%5D)%0A"></p>
<p>for some polynomial <img src="https://latex.codecogs.com/png.latex?Q"> in the brackets.</p>
<p>So every <img src="https://latex.codecogs.com/png.latex?SL(2)">-invariant polynomial lies in <img src="https://latex.codecogs.com/png.latex?k%5Cbigl%5B%5C,%20%5Bij%5D%20%5Cmid%201%20%5Cle%20i%20%3C%20j%20%5Cle%20p%20%5C,%5Cbigr%5D">.</p>
<p>Since we have both inclusions, we conclude that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA%5E%7BSL(2)%7D%20=%20k%5Cbigl%5B%5C,%20%5Bij%5D%20%5Cmid%201%20%5Cle%20i%20%3C%20j%20%5Cle%20p%20%5C,%5Cbigr%5D%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>This is the symbolic version of the First Fundamental Theorem. The corresponding statement for covariants of binary forms is obtained by translating bracket expressions into iterated transvectants.</p>
<p>In other words, in the symbolic calculus every invariant is obtained by multiplying and combining these basic determinants. When translated back to binary forms, these determinant-contractions correspond to the iterated Clebsch-Gordan projections. For example, the Jacobian <img src="https://latex.codecogs.com/png.latex?%5BQ_1,%20Q_2%5D"> corresponds to the first transvectant <img src="https://latex.codecogs.com/png.latex?(Q_1,%20Q_2)%5E%7B(1)%7D">, and the Hessian <img src="https://latex.codecogs.com/png.latex?(Q,%20Q)%5E%7B(2)%7D"> corresponds to the second self-transvectant <img src="https://latex.codecogs.com/png.latex?(Q,%20Q)%5E%7B(2)%7D">.</p>
</section>
<section id="second-fundamental-theorem-of-invariants-for-binary-forms" class="level3">
<h3 class="anchored" data-anchor-id="second-fundamental-theorem-of-invariants-for-binary-forms">Second Fundamental Theorem of Invariants for Binary Forms</h3>
<p>The First Fundamental Theorem tells us what generates the covariant ring (the transvectants). The Second Fundamental Theorem tells us what relations those generators satisfy.</p>
<div id="thm-second-fundamental-theorem" class="theorem">
<p><span class="theorem-title"><strong>Theorem 7 (Second Fundamental Theorem)</strong></span> Let <img src="https://latex.codecogs.com/png.latex?%0A%5Cphi%20:%20k%5BT_%7Bij%7D%5Cmid%201%5Cle%20i%3Cj%5Cle%20p%5D%20%5Cto%20k%5Bx_1,y_1,%5Cdots,x_p,y_p%5D%5E%7BSL(2)%7D%0A"> be the homomorphism defined by <img src="https://latex.codecogs.com/png.latex?%0AT_%7Bij%7D%5Cmapsto%20%5Bij%5D%0A"></p>
<p>Then <img src="https://latex.codecogs.com/png.latex?%5Cker(%5Cphi)"> is generated by the quadratic relations <img src="https://latex.codecogs.com/png.latex?%0AT_%7Bij%7DT_%7Bkl%7D%20+%20T_%7Bik%7DT_%7Blj%7D%20+%20T_%7Bil%7DT_%7Bjk%7D%20=%200%0A"></p>
<p>for all indices <img src="https://latex.codecogs.com/png.latex?i,j,k,l">.</p>
</div>
<p><em>Proof.</em></p>
<p>Let</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AR%20:=%20k%5Bx_1,y_1,%5Cdots,x_p,y_p%5D%5E%7BSL(2)%7D%0A"></p>
<p>and define the surjective homomorphism (by FFT)</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cphi%20:%20k%5BT_%7Bij%7D%5Cmid%201%5Cle%20i%3Cj%5Cle%20p%5D%20%5Cto%20R%0A"> <img src="https://latex.codecogs.com/png.latex?%0AT_%7Bij%7D%5Cmapsto%20%5Bij%5D=x_i%20y_j-y_i%20x_j%0A"></p>
<p>Let <img src="https://latex.codecogs.com/png.latex?I"> be the ideal generated by the quadratic polynomials</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AQ_%7Bijkl%7D:=T_%7Bij%7DT_%7Bkl%7D+T_%7Bik%7DT_%7Blj%7D+T_%7Bil%7DT_%7Bjk%7D%0A"></p>
<p>for all indices <img src="https://latex.codecogs.com/png.latex?i,j,k,l">.</p>
<p>We need to prove <img src="https://latex.codecogs.com/png.latex?%5Cker(%5Cphi)=I">. We will do this in four steps:</p>
<ol type="1">
<li><em>The quadratic identities lie in the kernel.</em></li>
</ol>
<p>For all <img src="https://latex.codecogs.com/png.latex?i,j,k,l">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Bij%5D%5Bkl%5D+%5Bik%5D%5Blj%5D+%5Bil%5D%5Bjk%5D=0%0A"></p>
<p>as polynomials in the coordinates <img src="https://latex.codecogs.com/png.latex?(x_r,y_r)">.</p>
<p>Expand each bracket product:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Bij%5D%5Bkl%5D=(x_i%20y_j-y_i%20x_j)(x_k%20y_l-y_k%20x_l)%0A"> <img src="https://latex.codecogs.com/png.latex?%0A=%20x_i%20y_j%20x_k%20y_l%20-%20x_i%20y_j%20y_k%20x_l%20-%20y_i%20x_j%20x_k%20y_l%20+%20y_i%20x_j%20y_k%20x_l%0A"></p>
<p>Similarly,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Bik%5D%5Blj%5D=(x_i%20y_k-y_i%20x_k)(x_l%20y_j-y_l%20x_j)%0A"> <img src="https://latex.codecogs.com/png.latex?%0A=%20x_i%20y_k%20x_l%20y_j%20-%20x_i%20y_k%20y_l%20x_j%20-%20y_i%20x_k%20x_l%20y_j%20+%20y_i%20x_k%20y_l%20x_j%0A"></p>
<p>and</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Bil%5D%5Bjk%5D=(x_i%20y_l-y_i%20x_l)(x_j%20y_k-y_j%20x_k)%0A"> <img src="https://latex.codecogs.com/png.latex?%0A=%20x_i%20y_l%20x_j%20y_k%20-%20x_i%20y_l%20y_j%20x_k%20-%20y_i%20x_l%20x_j%20y_k%20+%20y_i%20x_l%20y_j%20x_k%0A"></p>
<p>Now add the three expansions. Every monomial cancels with an identical monomial of opposite sign (after commuting scalars), so the sum is identically zero.</p>
<p>Therefore</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cphi(Q_%7Bijkl%7D)=0%0A"></p>
<p>for all <img src="https://latex.codecogs.com/png.latex?i,j,k,l">, hence</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AI%20%5Csubseteq%20%5Cker(%5Cphi)%0A"></p>
<ol start="2" type="1">
<li><em>Straightening procedure</em></li>
</ol>
<p>Call a monomial</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AM%20=%20T_%7Bi_1%20j_1%7D%20T_%7Bi_2%20j_2%7D%5Ccdots%20T_%7Bi_m%20j_m%7D%0A%5Cqquad%20(i_t%3Cj_t)%0A"></p>
<p><em>standard</em> if</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ai_1%5Cle%20i_2%5Cle%20%5Ccdots%20%5Cle%20i_m%0A%5Cquad%5Ctext%7Band%7D%5Cquad%0Aj_1%5Cle%20j_2%5Cle%20%5Ccdots%20%5Cle%20j_m%0A"></p>
<p>If <img src="https://latex.codecogs.com/png.latex?M"> is not standard, then there exist two factors <img src="https://latex.codecogs.com/png.latex?T_%7Bij%7DT_%7Bkl%7D"> with</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ai%3Ck%20%5Cquad%5Ctext%7Bbut%7D%5Cquad%20j%3El%0A"></p>
<p>Apply the quadratic identity to these four indices and solve for the crossed product:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AT_%7Bij%7DT_%7Bkl%7D%20%5Cequiv%20-%5C,T_%7Bik%7DT_%7Blj%7D%20-%20T_%7Bil%7DT_%7Bjk%7D%5Cpmod%20I%0A"></p>
<p>This rewrite replaces the crossed pair <img src="https://latex.codecogs.com/png.latex?(ij),(kl)"> by a sum of terms where the second indices are less out of order.</p>
<p>To see termination, sort the factors by increasing <img src="https://latex.codecogs.com/png.latex?i">-index and count inversions in the resulting list of <img src="https://latex.codecogs.com/png.latex?j">-indices. Each rewrite strictly decreases this inversion count, so repeated rewriting must stop.</p>
<p>Hence every monomial is congruent mod <img src="https://latex.codecogs.com/png.latex?I"> to a <img src="https://latex.codecogs.com/png.latex?k">-linear combination of standard monomials. In particular, standard monomials span</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ak%5BT_%7Bij%7D%5D/I%0A"></p>
<ol start="3" type="1">
<li><em>Standard monomials are linearly independent.</em></li>
</ol>
<p>It suffices to prove linear independence in each homogeneous degree <img src="https://latex.codecogs.com/png.latex?d"> in the variables <img src="https://latex.codecogs.com/png.latex?T_%7Bij%7D">.</p>
<p>Fix such a degree <img src="https://latex.codecogs.com/png.latex?d">, and set</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AN_r%20:=%20(d+1)%5Er%20%5Cqquad%20(r=1,%5Cdots,p)%0A"></p>
<p>Now specialize</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_r%20=%20u%5E%7BN_r%7D,%20%5Cqquad%20y_r%20=%20v%5E%7BN_r%7D%0A"></p>
<p>Then for <img src="https://latex.codecogs.com/png.latex?i%3Cj">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Bij%5D%20=%20x_i%20y_j%20-%20y_i%20x_j%20=%20u%5E%7BN_i%7D%20v%5E%7BN_j%7D%20-%20u%5E%7BN_j%7D%20v%5E%7BN_i%7D%0A"></p>
<p>Under lexicographic order with <img src="https://latex.codecogs.com/png.latex?u%20%5Cgg%20v">, the leading monomial of <img src="https://latex.codecogs.com/png.latex?%5Bij%5D"> is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au%5E%7BN_j%7D%20v%5E%7BN_i%7D%0A"></p>
<p>Therefore, if</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AM%20=%20T_%7Bi_1%20j_1%7D%5Ccdots%20T_%7Bi_d%20j_d%7D%0A"></p>
<p>is a standard monomial, then the leading monomial of <img src="https://latex.codecogs.com/png.latex?%5Cphi(M)"> is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au%5E%7BN_%7Bj_1%7D+%5Ccdots+N_%7Bj_d%7D%7D%20%5C,%20v%5E%7BN_%7Bi_1%7D+%5Ccdots+N_%7Bi_d%7D%7D%0A"></p>
<p>Because <img src="https://latex.codecogs.com/png.latex?M"> is standard, the sequences <img src="https://latex.codecogs.com/png.latex?i_1%20%5Cle%20%5Ccdots%20%5Cle%20i_d"> and <img src="https://latex.codecogs.com/png.latex?j_1%20%5Cle%20%5Ccdots%20%5Cle%20j_d"> are weakly increasing. Since each index can occur at most <img src="https://latex.codecogs.com/png.latex?d"> times, the sums</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AN_%7Bi_1%7D+%5Ccdots+N_%7Bi_d%7D,%20%5Cqquad%20N_%7Bj_1%7D+%5Ccdots+N_%7Bj_d%7D%0A"></p>
<p>uniquely determine the multisets <img src="https://latex.codecogs.com/png.latex?%5C%7Bi_1,%5Cdots,i_d%5C%7D"> and <img src="https://latex.codecogs.com/png.latex?%5C%7Bj_1,%5Cdots,j_d%5C%7D">, because the <img src="https://latex.codecogs.com/png.latex?N_r=(d+1)%5Er"> are powers of <img src="https://latex.codecogs.com/png.latex?d+1"> and base-<img src="https://latex.codecogs.com/png.latex?(d+1)"> expansion is unique.</p>
<p>Thus distinct standard monomials have distinct leading monomials after this specialization. Hence their images under <img src="https://latex.codecogs.com/png.latex?%5Cphi"> are linearly independent.</p>
<ol start="4" type="1">
<li><em>Conclusion</em></li>
</ol>
<p>By (1), <img src="https://latex.codecogs.com/png.latex?I%5Csubseteq%5Cker(%5Cphi)">.</p>
<p>By (2), every element of <img src="https://latex.codecogs.com/png.latex?k%5BT_%7Bij%7D%5D/I"> is a linear combination of standard monomials.</p>
<p>By (3), the images of standard monomials in <img src="https://latex.codecogs.com/png.latex?R"> are linearly independent, so the induced map</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Coverline%5Cphi:%5C;%20k%5BT_%7Bij%7D%5D/I%20%5Cto%20R%0A"></p>
<p>is injective. Since <img src="https://latex.codecogs.com/png.latex?%5Cphi"> is surjective, <img src="https://latex.codecogs.com/png.latex?%5Coverline%5Cphi"> is an isomorphism.</p>
<p>Thus</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cker(%5Cphi)=I%0A"></p>
<p>So every relation among the bracket generators is generated by the quadratic identities.</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
</section>
<section id="summary-for-reductive-groups" class="level3">
<h3 class="anchored" data-anchor-id="summary-for-reductive-groups">Summary for Reductive Groups</h3>
<p>For reductive groups, we can compute the invariants using the Reynolds operator, which exists by Maschke’s theorem for reductive groups. For finite groups and compact Lie groups, we can construct the Reynolds operator by summing or integrating over the group. And for noncompact reductive groups like <img src="https://latex.codecogs.com/png.latex?GL(2)">, we can compute the invariants using representation theory to identify the equivariant projections directly (e.g.&nbsp;the Clebsch-Gordan decomposition for <img src="https://latex.codecogs.com/png.latex?SL(2)">).</p>
</section>
</section>
<section id="nonreductive-groups" class="level2">
<h2 class="anchored" data-anchor-id="nonreductive-groups">Nonreductive Groups</h2>
<p>What if GL(2) was non-reductive? Hilbert’s 14th problem asked whether the invariant ring of a linear group action on a polynomial ring is always finitely generated.</p>
<p>For non-reductive groups, the answer is no. In 1959, Nagata constructed an explicit counterexample (showing an action of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BG%7D_a%5E%7B13%7D"> on a polynomial ring that has an invariant ring requiring infinitely many generators).</p>
<p>So we can’t necessarily use representation theory to compute the invariants, and the Reynolds operator might not exist. In fact, the invariant ring may not even be finitely generated.</p>
<p>Based on a Claude-based survey, it seems there are a few options in these cases, which I’ll presumably examine in the future<sup>9</sup>.</p>
</section>
<section id="overall-flow" class="level2">
<h2 class="anchored" data-anchor-id="overall-flow">Overall Flow</h2>
<p>If we look back at what we did to compute the invariants, we can see that we basically followed a process:</p>
<ol type="1">
<li>We have some polynomial ring<sup>10</sup> <img src="https://latex.codecogs.com/png.latex?k%5BV%5D"> over field <img src="https://latex.codecogs.com/png.latex?k"> of characteristic zero<sup>11</sup> and a group<sup>12</sup> action <img src="https://latex.codecogs.com/png.latex?G"> on <img src="https://latex.codecogs.com/png.latex?V">. We want the invariant ring <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG">.</li>
<li>Is the group finite? If so, we can compute the invariants by summing over the group (Reynolds operator).</li>
<li>Is the group infinite, but compact? If so, we can compute the invariants by integrating over the group with respect to the Haar measure (Reynolds operator). Constructing the Haar measure depends on the topology of <img src="https://latex.codecogs.com/png.latex?G">:
<ol type="a">
<li>If <img src="https://latex.codecogs.com/png.latex?G"> is continuous, then it is a Lie group<sup>13</sup>, and we can construct the Haar measure explicitly via the Maurer-Cartan form (as we did above for <img src="https://latex.codecogs.com/png.latex?SO(2)">).</li>
<li>If <img src="https://latex.codecogs.com/png.latex?G"> is totally disconnected (e.g.&nbsp;profinite groups, <img src="https://latex.codecogs.com/png.latex?p">-adic analytic groups like <img src="https://latex.codecogs.com/png.latex?GL(n,%20%5Cmathbb%7BZ%7D_p)">), the Haar measure still exists but the construction is more difficult<sup>14</sup>.</li>
</ol></li>
<li>Is the group noncompact but reductive<sup>15</sup>? If so, a Reynolds operator exists, but the actual computation uses representation theory to identify the equivariant projections directly.<br>
</li>
<li>Is the group non-reductive? If so, there is no general method. However, idiosyncratic methods exist for specific cases.</li>
</ol>
<p>Our example <img src="https://latex.codecogs.com/png.latex?GL(2)"> flows down to Step 4. The unitarian trick gives us a Reynolds operator by integrating over <img src="https://latex.codecogs.com/png.latex?U(2)">, and representation theory (Clebsch-Gordan) tells us the result is exactly the transvectant calculus.</p>
<p>In all cases where a Reynolds operator exists (steps 2–4), the general algorithm for finding generators is to use the Molien series to count how many invariants to expect in each degree, apply <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BR%7D"> to produce candidates, use Grobner bases to test algebraic independence, and finally stop when the Molien series terminates. Each of these steps requires theoretical justification, which we’ll look at in the next section.</p>
<p>This overall process is handy for computational approaches, where we encode these algorithms as code. Probably we will mostly deal with finite or compact groups, but it’s good to have the general picture in mind.</p>
</section>
</section>
<section id="general-theory" class="level1">
<h1>General Theory</h1>
<p>In addition to being able to find invariants, we also want to understand the structure of the invariant ring. For example, can we find all of the invariants? Is a particular invariant ring finitely generated? If so, what are the generators of this ring, what are the relationships between the generators, and what does the geometry of the invariant ring look like?</p>
<section id="finding-all-invariants" class="level2">
<h2 class="anchored" data-anchor-id="finding-all-invariants">Finding All Invariants</h2>
<p>To prove that we have found all the invariants for a given transformation acting on a object, we need to show that the process terminates (i.e.&nbsp;it does not produce an infinite sequence of new invariants) and that it is complete (i.e.&nbsp;it produces all the invariants).</p>
<p>We’ll continue to look at the example of binary forms, but the same questions apply to any group action on any object.</p>
</section>
<section id="noetherian-rings" class="level2">
<h2 class="anchored" data-anchor-id="noetherian-rings">Noetherian Rings</h2>
<p>We need to first introduce the concept of a Noetherian ring<sup>16</sup>.</p>
<p>An ideal <img src="https://latex.codecogs.com/png.latex?I"> of a ring <img src="https://latex.codecogs.com/png.latex?R"> is a subset that is closed under addition and under multiplication by any element of <img src="https://latex.codecogs.com/png.latex?R">: if <img src="https://latex.codecogs.com/png.latex?f%20%5Cin%20I"> and <img src="https://latex.codecogs.com/png.latex?r%20%5Cin%20R">, then <img src="https://latex.codecogs.com/png.latex?f%20+%20g%20%5Cin%20I"> for any <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20I">, and <img src="https://latex.codecogs.com/png.latex?rf%20%5Cin%20I">. An ideal is <em>finitely generated</em> if there exist finitely many elements <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_m%20%5Cin%20I"> such that every element of <img src="https://latex.codecogs.com/png.latex?I"> can be written as <img src="https://latex.codecogs.com/png.latex?r_1%20f_1%20+%20%5Ccdots%20+%20r_m%20f_m"> for some <img src="https://latex.codecogs.com/png.latex?r_i%20%5Cin%20R">.</p>
<div id="def-noetherian" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 9 (Noetherian Ring)</strong></span> A ring <img src="https://latex.codecogs.com/png.latex?R"> is Noetherian if every ideal of <img src="https://latex.codecogs.com/png.latex?R"> is finitely generated.</p>
</div>
<p>Fields, like <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D"> or <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BC%7D">, are Noetherian, since their only ideals are <img src="https://latex.codecogs.com/png.latex?(0)"> and <img src="https://latex.codecogs.com/png.latex?k"> itself, both finitely generated.</p>
</section>
<section id="hilberts-basis-theorem" class="level2">
<h2 class="anchored" data-anchor-id="hilberts-basis-theorem">Hilbert’s Basis Theorem</h2>
<p>Now we are ready to state Hilbert’s Basis Theorem, which is the key result that allows us to conclude that the invariant ring of a group action on a polynomial ring is finitely generated.</p>
<div id="thm-hilbert-basis-theorem" class="theorem">
<p><span class="theorem-title"><strong>Theorem 8 (Hilbert’s Basis Theorem)</strong></span> If <img src="https://latex.codecogs.com/png.latex?R"> is a Noetherian ring, then the polynomial ring <img src="https://latex.codecogs.com/png.latex?R%5Bx%5D"> is also Noetherian. Equivalently, if <img src="https://latex.codecogs.com/png.latex?R"> is Noetherian, then every ideal of <img src="https://latex.codecogs.com/png.latex?R%5Bx%5D"> is finitely generated.</p>
</div>
<p><em>Proof.</em></p>
<p>Fix an ideal <img src="https://latex.codecogs.com/png.latex?I%20%5Csubset%20R%5Bx%5D">.</p>
<p>If <img src="https://latex.codecogs.com/png.latex?I"> is not finitely generated, then we can construct an infinite sequence by choosing each <img src="https://latex.codecogs.com/png.latex?f_%7Bn+1%7D"> to be an element of minimal degree in <img src="https://latex.codecogs.com/png.latex?I%20%5Csetminus%20(f_1,%20%5Cldots,%20f_n)">. By construction, the degrees are non-decreasing.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?a_i"> be the corresponding leading coefficient for each <img src="https://latex.codecogs.com/png.latex?f_i">. Since <img src="https://latex.codecogs.com/png.latex?R"> is Noetherian, the ideal generated by these leading coefficients, <img src="https://latex.codecogs.com/png.latex?(a_1,%20a_2,%20%5Cldots)">, is finitely generated. Therefore, there exists some <img src="https://latex.codecogs.com/png.latex?n"> such that, for all <img src="https://latex.codecogs.com/png.latex?i%20%3E%20n">, we have <img src="https://latex.codecogs.com/png.latex?a_i%20%5Cin%20(a_1,%20%5Cldots,%20a_n)">.</p>
<p>So for all <img src="https://latex.codecogs.com/png.latex?i%20%3E%20n">, we can write <img src="https://latex.codecogs.com/png.latex?a_i%20=%20r_1%20a_1%20+%20%5Ccdots%20+%20r_m%20a_m"> for some set of <img src="https://latex.codecogs.com/png.latex?r_i%20%5Cin%20R">, where <img src="https://latex.codecogs.com/png.latex?m%20%5Cleq%20n">.</p>
<p>The leading term of each <img src="https://latex.codecogs.com/png.latex?f_i"> can be written <img src="https://latex.codecogs.com/png.latex?a_i%20x%5Ed"> for some degree <img src="https://latex.codecogs.com/png.latex?d">. Let <img src="https://latex.codecogs.com/png.latex?d_%7B*%7D"> be the smallest degree for which there exists a polynomial in <img src="https://latex.codecogs.com/png.latex?I"> not already in <img src="https://latex.codecogs.com/png.latex?(f_1,%20%5Cldots,%20f_n)">. Since <img src="https://latex.codecogs.com/png.latex?f_%7Bn+1%7D%20%5Cnotin%20(f_1,%20%5Cldots,%20f_n)">, the degree of <img src="https://latex.codecogs.com/png.latex?f_%7Bn+1%7D"> must be at least <img src="https://latex.codecogs.com/png.latex?d_%7B*%7D">.</p>
<p>Since <img src="https://latex.codecogs.com/png.latex?a_%7Bn+1%7D"> is a linear combination of <img src="https://latex.codecogs.com/png.latex?a_1,%20%5Cldots,%20a_n">, we can linearly combine the leading terms of <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_n"> to get a leading term <img src="https://latex.codecogs.com/png.latex?a_%7Bn+1%7D%20x%5E%7Bd_%7Bn+1%7D%7D"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aa_%7Bn+1%7D%20x%5E%7Bd_%7Bn+1%7D%7D%20=%20r_1%20a_1%20x%5E%7Bd_1%7D%20x%5E%7Bd_%7Bn+1%7D%20-%20d_1%7D%20+%20%5Ccdots%20+%20r_m%20a_m%20x%5E%7Bd_m%7D%20x%5E%7Bd_%7Bn+1%7D%20-%20d_m%7D%0A"></p>
<p>We can subtract this linear combination from <img src="https://latex.codecogs.com/png.latex?f_%7Bn+1%7D"> to get a new polynomial of lower degree. If we repeat this process a finite number of times, we can eventually get a polynomial <img src="https://latex.codecogs.com/png.latex?g"> that has degree less than <img src="https://latex.codecogs.com/png.latex?d_%7B*%7D">.</p>
<p>So we can write <img src="https://latex.codecogs.com/png.latex?f_%7Bn+1%7D"> as a linear combination of <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_n"> plus a remainder polynomial <img src="https://latex.codecogs.com/png.latex?g"> of degree less than <img src="https://latex.codecogs.com/png.latex?d_%7B*%7D">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af_%7Bn+1%7D%20=%20q_1(x)%20f_1%20+%20%5Ccdots%20+%20q_n(x)%20f_n%20+%20g%0A"></p>
<p>But we said that <img src="https://latex.codecogs.com/png.latex?d_%7B*%7D"> is the smallest degree not contained in the ideal generated by <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_n">. Since <img src="https://latex.codecogs.com/png.latex?g"> has degree less than <img src="https://latex.codecogs.com/png.latex?d_%7B*%7D">, it must be contained in the ideal generated by <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_n">. So <img src="https://latex.codecogs.com/png.latex?f_%7Bn+1%7D"> is contained in the ideal generated by <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_n">.</p>
<p>This is a contradiction. Therefore, every ideal of <img src="https://latex.codecogs.com/png.latex?R%5Bx%5D"> is finitely generated, and <img src="https://latex.codecogs.com/png.latex?R%5Bx%5D"> is Noetherian. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>Conceptually, we just did long division over and over again, and the Noetherian condition guaranteed that this process terminated after finitely many steps.</p>
</section>
<section id="generators" class="level2">
<h2 class="anchored" data-anchor-id="generators">Generators</h2>
<p>What are the relations between the generators? This question (for binary forms) is answered by the Second Fundamental Theorem, which gives a complete description of the syzygies (relations) between the generators of the invariant ring.</p>
</section>
<section id="syzygies" class="level2">
<h2 class="anchored" data-anchor-id="syzygies">Syzygies</h2>
<p>We know from the First Fundamental Theorem that transvectants generate all the covariants. But the generators are not algebraically independent. They have relations between them called syzygies.</p>
<div id="def-syzygy" class="theorem definition">
<p><span class="theorem-title"><strong>Definition 10 (Syzygy)</strong></span> Given a field <img src="https://latex.codecogs.com/png.latex?k"> of characteristic zero, and a polynomial <img src="https://latex.codecogs.com/png.latex?F%20%5Cin%20k%5BT_1,%20%5Cldots,%20T_s%5D"> in <img src="https://latex.codecogs.com/png.latex?s"> variables, a syzygy among generators <img src="https://latex.codecogs.com/png.latex?J_1,%20%5Cldots,%20J_s"> of a graded ring is a polynomial relation</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AF(J_1,%20%5Cldots,%20J_s)%20=%200%0A"></p>
<p>Equivalently, if <img src="https://latex.codecogs.com/png.latex?%5Cphi:%20k%5BT_1,%20%5Cldots,%20T_s%5D%20%5Cto%20k%5BV%5D%5EG"> is the surjection sending <img src="https://latex.codecogs.com/png.latex?T_i%20%5Cmapsto%20J_i">, then the syzygies are the elements of <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi">.</p>
</div>
<p>So a syzygy is a polynomial relation among the generators of the invariant ring. The set of all syzygies forms an ideal in the polynomial ring <img src="https://latex.codecogs.com/png.latex?k%5BT_1,%20%5Cldots,%20T_s%5D">, called the syzygy ideal.</p>
<p>Since the syzygy ideal is itself an ideal over <img src="https://latex.codecogs.com/png.latex?k%5BT_1,%20%5Cldots,%20T_s%5D">, we can continue to iterate this process. The relations between the generators of the syzygy ideal are called second-order syzygies, and so on. Does this process terminate?</p>
</section>
<section id="hilberts-syzygy-theorem" class="level2">
<h2 class="anchored" data-anchor-id="hilberts-syzygy-theorem">Hilbert’s Syzygy Theorem</h2>
<div id="thm-hilbert-syzygy" class="theorem">
<p><span class="theorem-title"><strong>Theorem 9 (Hilbert’s Syzygy Theorem)</strong></span> Consider a vector space <img src="https://latex.codecogs.com/png.latex?V"> over a field <img src="https://latex.codecogs.com/png.latex?k"> of characteristic zero, and let <img src="https://latex.codecogs.com/png.latex?k%5BV%5D"> be the polynomial ring on <img src="https://latex.codecogs.com/png.latex?V">.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?S%20=%20k%5BT_1,%20%5Cldots,%20T_s%5D"> be a polynomial ring in <img src="https://latex.codecogs.com/png.latex?s"> variables, and let <img src="https://latex.codecogs.com/png.latex?%5Cphi:%20S%20%5Cto%20k%5BV%5D%5EG"> be a surjection sending <img src="https://latex.codecogs.com/png.latex?T_i%20%5Cmapsto%20J_i">, where <img src="https://latex.codecogs.com/png.latex?J_1,%20%5Cldots,%20J_s"> are generators of the invariant ring.</p>
<p>Consider the “tower of syzygies” generated recursively by <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi">:</p>
<ul>
<li>Pick generators <img src="https://latex.codecogs.com/png.latex?R_1%5E%7B(0)%7D,%20%5Cldots,%20R_%7Bm_0%7D%5E%7B(0)%7D"> of <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi">.</li>
<li>Let <img src="https://latex.codecogs.com/png.latex?K_1"> be the set of tuples <img src="https://latex.codecogs.com/png.latex?(p_1,%20%5Cldots,%20p_%7Bm_0%7D)"> such that <img src="https://latex.codecogs.com/png.latex?p_1%20R_1%5E%7B(0)%7D%20+%20%5Ccdots%20+%20p_%7Bm_0%7D%20R_%7Bm_0%7D%5E%7B(0)%7D%20=%200">.</li>
<li>Pick generators <img src="https://latex.codecogs.com/png.latex?R_1%5E%7B(1)%7D,%20%5Cldots,%20R_%7Bm_1%7D%5E%7B(1)%7D"> of <img src="https://latex.codecogs.com/png.latex?K_1">, and continue.</li>
</ul>
<p>The tower of syzygies of <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi"> vanishes for all <img src="https://latex.codecogs.com/png.latex?n%20%3E%20s">.</p>
</div>
<p><em>Proof.</em></p>
<p>Omitted. A full proof of Hilbert’s Syzygy Theorem requires too much machinery that is beyond the scope of this blog post (Grobner bases, free resolutions of modules). See Cox–Little–O’Shea, Ideals, Varieties, and Algorithms, Chapter 10 for an “algorithmic” approach, or a homological algebra text.</p>
<p>The intuition is that each variable provides one independent “direction” in which cancellations can occur. After using up all <img src="https://latex.codecogs.com/png.latex?s"> variables, there are no new directions left for higher syzygies to appear. In other words, there are only finitely many levels of syzygies, and we can find all of them in a finite amount of time.</p>
<p>I tried to look for a more elementary proof, but I couldn’t find one.</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>Note that this also extends to modules over <img src="https://latex.codecogs.com/png.latex?k%5BT_1,%20%5Cldots,%20T_s%5D"> (e.g.&nbsp;the module of covariants).</p>
</section>
<section id="geometry" class="level2">
<h2 class="anchored" data-anchor-id="geometry">Geometry</h2>
</section>
<section id="nullstellensatz" class="level2">
<h2 class="anchored" data-anchor-id="nullstellensatz">Nullstellensatz</h2>
<div id="thm-nullstellensatz" class="theorem">
<p><span class="theorem-title"><strong>Theorem 10 (Hilbert’s Nullstellensatz)</strong></span> An ideal <img src="https://latex.codecogs.com/png.latex?I"> of a polynomial ring <img src="https://latex.codecogs.com/png.latex?R%20=%20k%5Bx_1,%20%5Cldots,%20x_n%5D"> corresponds to the set of common zeros of the polynomials in <img src="https://latex.codecogs.com/png.latex?I">. That is, denote <img src="https://latex.codecogs.com/png.latex?V(I)%20=%20%5C%7B(a_0,%20...,%20a_n)%20%5Cin%20k%5En%20%5Cmid%20f(a_0,%20%5Cldots,%20a_n)%20=%200%20%5Ctext%7B%20for%20all%20%7D%20f%20%5Cin%20I%5C%7D">.</p>
<p>Conversely, let <img src="https://latex.codecogs.com/png.latex?V"> denote a subset of <img src="https://latex.codecogs.com/png.latex?k%5En"> (meaning tuples in <img src="https://latex.codecogs.com/png.latex?k">) that corresponds to the ideal of all polynomials that vanish on <img src="https://latex.codecogs.com/png.latex?V">. That is, denote <img src="https://latex.codecogs.com/png.latex?I(V)%20=%20%5C%7Bf%20%5Cin%20R%20%5Cmid%20f(x)%20=%200%20%5Ctext%7B%20for%20all%20%7D%20x%20%5Cin%20V%5C%7D">.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?k"> be an algebraically closed field and <img src="https://latex.codecogs.com/png.latex?R%20=%20k%5Bx_1,%20%5Cldots,%20x_n%5D">.</p>
<p>If <img src="https://latex.codecogs.com/png.latex?I%20%5Csubseteq%20R"> is an ideal and <img src="https://latex.codecogs.com/png.latex?f%20%5Cin%20R"> vanishes at every common zero of <img src="https://latex.codecogs.com/png.latex?I">, then <img src="https://latex.codecogs.com/png.latex?f%5Em%20%5Cin%20I"> for some <img src="https://latex.codecogs.com/png.latex?m%20%5Cgeq%201">.</p>
<p>Equivalently, define <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BI%7D%20=%20%5C%7Bf%20%5Cin%20R%20%5Cmid%20f%5Em%20%5Cin%20I%20%5Ctext%7B%20for%20some%20%7D%20m%20%5Cgeq%201%5C%7D">. Then <img src="https://latex.codecogs.com/png.latex?I(V(I))%20=%20%5Csqrt%7BI%7D">.</p>
</div>
<p><em>Proof.</em></p>
<p>Assume <img src="https://latex.codecogs.com/png.latex?f"> vanishes on <img src="https://latex.codecogs.com/png.latex?V(I)">. We want to show that <img src="https://latex.codecogs.com/png.latex?f%5Em%20%5Cin%20I"> for some <img src="https://latex.codecogs.com/png.latex?m">.</p>
<p>Introduce a new variable <img src="https://latex.codecogs.com/png.latex?t"> and consider the ideal <img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20I%20+%20(1%20-%20tf)%20%5Csubset%20k%5Bx_1,%20%5Cldots,%20x_n,%20t%5D%0A"></p>
<p>Suppose <img src="https://latex.codecogs.com/png.latex?(a,t)"> is a common zero of <img src="https://latex.codecogs.com/png.latex?J">. Then every polynomial in <img src="https://latex.codecogs.com/png.latex?I"> vanishes at <img src="https://latex.codecogs.com/png.latex?a">, so <img src="https://latex.codecogs.com/png.latex?a%20%5Cin%20V(I)">. The equation <img src="https://latex.codecogs.com/png.latex?1%20-%20tf(a)%20=%200"> therefore implies <img src="https://latex.codecogs.com/png.latex?tf(a)%20=%201">. But <img src="https://latex.codecogs.com/png.latex?f(a)=0"> for every <img src="https://latex.codecogs.com/png.latex?a%20%5Cin%20V(I)">, which is impossible. Therefore <img src="https://latex.codecogs.com/png.latex?J"> has no common zero.</p>
<p>(To see this: if <img src="https://latex.codecogs.com/png.latex?J"> were a proper ideal, it would be contained in some maximal ideal <img src="https://latex.codecogs.com/png.latex?%5Cmathfrak%7Bm%7D">. Then <img src="https://latex.codecogs.com/png.latex?k%5Bx_1,%5Cldots,x_n,t%5D/%5Cmathfrak%7Bm%7D"> is a field that is finitely generated as a <img src="https://latex.codecogs.com/png.latex?k">-algebra. But any field finitely generated as an algebra over an algebraically closed field <img src="https://latex.codecogs.com/png.latex?k"> must equal <img src="https://latex.codecogs.com/png.latex?k"> itself (since each generator satisfies a polynomial over <img src="https://latex.codecogs.com/png.latex?k">, and <img src="https://latex.codecogs.com/png.latex?k"> already contains all roots). So <img src="https://latex.codecogs.com/png.latex?%5Cmathfrak%7Bm%7D%20=%20(x_1%20-%20a_1,%20%5Cldots,%20x_n%20-%20a_n,%20t%20-%20b)"> for some point <img src="https://latex.codecogs.com/png.latex?(a,b)">, meaning <img src="https://latex.codecogs.com/png.latex?J"> has a common zero — contradicting what we just showed.)</p>
<p>An ideal with no common zero must contain <img src="https://latex.codecogs.com/png.latex?1">, so <img src="https://latex.codecogs.com/png.latex?1%20%5Cin%20J">. Thus we can write <img src="https://latex.codecogs.com/png.latex?%0A1%20=%20g_1%20f_1%20+%20%5Ccdots%20+%20g_r%20f_r%20+%20h(1%20-%20tf)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?f_1,%20%5Cldots,%20f_r%20%5Cin%20I"> and <img src="https://latex.codecogs.com/png.latex?g_1,%20%5Cldots,%20g_r,%20h"> are polynomials in <img src="https://latex.codecogs.com/png.latex?k%5Bx_1,%20%5Cldots,%20x_n,%20t%5D">.</p>
<p>Substitute <img src="https://latex.codecogs.com/png.latex?t%20=%201/f"> into this equation. The term <img src="https://latex.codecogs.com/png.latex?h(1%20-%20tf)"> becomes zero, so we obtain</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A1%20=%20g_1%20f_1%20+%20%5Ccdots%20+%20g_r%20f_r%0A"></p>
<p>This expression may contain denominators coming from <img src="https://latex.codecogs.com/png.latex?1/f">. Multiplying both sides by a sufficiently large power <img src="https://latex.codecogs.com/png.latex?f%5Em"> clears the denominators and yields</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af%5Em%20=%20a_1%20f_1%20+%20%5Ccdots%20+%20a_r%20f_r%0A"></p>
<p>for some polynomials <img src="https://latex.codecogs.com/png.latex?a_1,%20%5Cldots,%20a_r%20%5Cin%20k%5Bx_1,%20%5Cldots,%20x_n%5D">.</p>
<p>Thus <img src="https://latex.codecogs.com/png.latex?f%5Em%20%5Cin%20I">. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
<p>How do we interpret this?</p>
<p>If we have a set of polynomials that generates an ideal <img src="https://latex.codecogs.com/png.latex?I">, then the common zeros of <img src="https://latex.codecogs.com/png.latex?I"> are exactly the points where all the polynomials in <img src="https://latex.codecogs.com/png.latex?I"> vanish.</p>
<p>If we have a set of polynomials that vanishes on a set of points <img src="https://latex.codecogs.com/png.latex?V">, then the ideal generated by those polynomials contains all polynomials that vanish on <img src="https://latex.codecogs.com/png.latex?V"> (up to radicals).</p>
<p>So we can go back and forth between the algebraic relations among the generators and the geometric shape of the solution set.</p>
<p>For invariants theory, consider the map:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpi%20:%20V%20%5Cto%20k%5Es%0A"> <img src="https://latex.codecogs.com/png.latex?%0Av%20%5Cmapsto%20(J_1(v),%20%5Cldots,%20J_s(v))%0A"></p>
<p>This takes each point in <img src="https://latex.codecogs.com/png.latex?V"> and maps it to the tuple of its invariants. Since invariants are constant under action of <img src="https://latex.codecogs.com/png.latex?G">, this map is constant on orbits of <img src="https://latex.codecogs.com/png.latex?G">. So <img src="https://latex.codecogs.com/png.latex?%5Cpi"> is constant along <img src="https://latex.codecogs.com/png.latex?G">-orbits.</p>
<p>Now, consider</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cphi:%20k%5BT_1,%20%5Cldots,%20T_s%5D%20%5Cto%20k%5BV%5D%5EG%0A"></p>
<p>which maps <img src="https://latex.codecogs.com/png.latex?T_i%20%5Cmapsto%20J_i">. The kernel of <img src="https://latex.codecogs.com/png.latex?%5Cphi"> is the ideal of relations among the generators.</p>
<p>By the Nullstellensatz, the <img src="https://latex.codecogs.com/png.latex?J_i"> satisfy the relations in <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi">, and any other relation satisfied by the <img src="https://latex.codecogs.com/png.latex?J_i"> is a consequence of those in <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi">.</p>
<p>So the <img src="https://latex.codecogs.com/png.latex?J_i"> behave like coordinates on the image of <img src="https://latex.codecogs.com/png.latex?%5Cpi">, and the relations in <img src="https://latex.codecogs.com/png.latex?%5Cker%20%5Cphi"> determine the shape of that image. In other words, the algebraic structure of the invariant ring determines the geometry of the orbit space <img src="https://latex.codecogs.com/png.latex?V//G">.</p>
<p>So, given some space, we can look at what <img src="https://latex.codecogs.com/png.latex?G"> leaves fixed. If we use those invariants as coordinates, we can get a smaller space that captures the structure of the original space, but with the symmetries “divided out”.</p>
</section>
<section id="summary" class="level2">
<h2 class="anchored" data-anchor-id="summary">Summary</h2>
<p>The three theorems fit together nicely.</p>
<ul>
<li>The Basis Theorem tells us the invariant ring is finitely generated, so the orbit space <img src="https://latex.codecogs.com/png.latex?V//G"> is finite-dimensional.</li>
<li>The Syzygy Theorem describes the relations among the generators, which determine the shape of <img src="https://latex.codecogs.com/png.latex?V//G">.</li>
<li>The Nullstellensatz says the algebra of the invariant ring gives the geometry of the orbit space, so we can understand the geometry of <img src="https://latex.codecogs.com/png.latex?V//G"> by understanding the algebra of <img src="https://latex.codecogs.com/png.latex?k%5BV%5D%5EG">.</li>
</ul>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>We’ve now explored the process of computing invariants and looked at the theory surrounding that process, especially for the particular case of binary forms with coefficients from fields of characteristic zero. We also now have a process we can follow where, given a group action on a ring, we first check if the group is finite, compact, or reductive, and then apply the appropriate method to compute the invariants. The footnotes also give some ideas extend this process to other types of objects and group actions.</p>
<p>We have also discussed the structure of the invariant ring, including the question of finite generation, the relations between generators (syzygies), and the geometry of the invariant ring. Each of Hilbert’s three theorems answers one of these questions, and they are all fundamental to our understanding of invariant theory, as well as modern mathematics. For example, Noether’s work on finite generation led to the concept of Noetherian rings, which is fundamental to commutative algebra. Hilbert’s Syzygy Theorem led to the development of homological algebra, and the Nullstellensatz is a cornerstone of algebraic geometry<sup>17</sup>.</p>
<p>In the next post, I’ll look more closely at the computational aspects of invariant theory, including algorithms for computing invariants and covariants (such as the Molien series, Gröbner bases, primary/secondary decomposition, and Kemper’s algorithms).</p>
<p>Possibilities for applications include game theory, allometric scaling (allometric scaling laws can be viewed as syzygies of the invariant ring of the scaling group acting on biological observables), multilevel selection, and machine learning. I have several threads I’ve been developing, which include applying the geometric controls framework to games, stacking symmetries to constrain admissible Lagrangians, rederiving allometric scaling from representation theory, and looking at <a href="../../../essays/posts/functional_theories_of_art/index.html">multilevel selection</a> mathematically, all of which seem to require invariant theory. I don’t know exactly how yet, so the plan is to learn the core algorithms by implementing them, and see where they lead</p>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>I used AI to brainstorm, find references, edit, organize sections (a huge pain), format LaTeX, and check proofs.</p>
<!-- 
Graveyard


### Scaling Process 

For inhomogeneous polynomials, we can define a similar process to the Omega process called the scaling process...

### Brackets

We can package the transvectants into a more compact notation called brackets.

Use Fourier transform...

Olver defines a "bracket of the first kind" as:

$$
(\alpha, x) = \xi_{\alpha} x + \eta_{\alpha} y
$$


and a "bracket of the second kind" as the determinant of the matrix:

$$
[\alpha \beta] = \text{det}\begin{bmatrix} \xi_{\alpha} & \eta_{\alpha} \\ \xi_{\beta} & \eta_{\beta} \end{bmatrix} = \xi_{\alpha} \eta_{\beta} - \eta_{\alpha}\xi_{\beta} 
$$

Under linear transformations, the first kind transforms as a covariant of weight 0, and the second kind transforms as a covariant of weight 1. So the first kind is an invariant, and the second kind is a covariant.

There are also brackets of the third kind, which are defines as:

$$
[|\alpha \beta|] = \text{det}\begin{bmatrix} x_{\alpha} & x_{\beta} \\ y_{\alpha} & y_{\beta} \end{bmatrix} = x_{\alpha} y_{\beta} - y_{\alpha} x_{\beta} 
$$

which transform as a covariant of weight -1. -->
<!-- 

### Omega Process

There's no general method for computing invariants and covariants for noncompact groups, but for certain families there may be idiosyncratic tricks. 

Let's start by considering what operations on polynomials preserve covariants. We already know multiplication of two binary forms does, and so does addition of two binary forms (for equally weighted covariants). 

Pretty much the only other operation we have is differentiation. Are the covariants of a polynomial preserved under differentiation, or do they transform in a predictable way?

We know that the original binary form $Q$ transforms as follows:
$$
\bar{Q}(\bar{x}, \bar{y}) = Q(x, y) = Q((\bar{x}, \bar{y})A^{-1})
$$

Let's take the partial derivative of both sides with respect to $x$ and $y$ to see how the partial derivatives transform.

By the chain rule, we have:
$$
\begin{aligned}
\partial_x \bar{Q}(\bar{x}, \bar{y}) &= \partial_x Q(x,y) \cdot \frac{\partial x}{\partial \bar{x}} + \partial_y Q(x,y) \cdot \frac{\partial y}{\partial \bar{x}} \\
\partial_y \bar{Q}(\bar{x}, \bar{y}) &= \partial_x Q(x,y) \cdot \frac{\partial x}{\partial \bar{y}} + \partial_y Q(x,y) \cdot \frac{\partial y}{\partial \bar{y}}
\end{aligned}   
$$

We can write this in matrix form as

$$
\begin{bmatrix} \partial_x \bar{Q} \\ \partial_y \bar{Q} \end{bmatrix} = A^{-T} \begin{bmatrix} \partial_x Q \\ \partial_y Q \end{bmatrix}
$$

So this is a linear transformation of the original partial derivatives, and it does not transform as a scalar multiple of itself. Therefore, the partial derivatives are not covariants.

However, what if we take a linear combination of the partial derivatives? Consider instead a pair of binary forms $Q_1$ and $Q_2$, and look at the matrix formed by their gradients:

$$
\begin{bmatrix} \partial_x Q_1 & \partial_y Q_1 \\ \partial_x Q_2 & \partial_y Q_2 \end{bmatrix}
$$

Under the transformation, this matrix transforms as:

$$
\begin{bmatrix} \partial_x \bar{Q}_1 & \partial_y \bar{Q}_1 \\ \partial_x \bar{Q}_2 & \partial_y \bar{Q}_2 \end{bmatrix} = A^{-T} \begin{bmatrix} \partial_x Q_1 & \partial_y Q_1 \\ \partial_x Q_2 & \partial_y Q_2 \end{bmatrix}
$$

Taking determinants, we get:

$$
\text{det} \begin{bmatrix} \partial_x \bar{Q}_1 & \partial_y \bar{Q}_1 \\ \partial_x \bar{Q}_2 & \partial_y \bar{Q}_2 \end{bmatrix} = (ad - bc)^{-1} (\partial_x Q_1 \cdot \partial_y Q_2 - \partial_x Q_2 \cdot \partial_y Q_1)
$$

So this linear combination of the partial derivatives transforms as a scalar multiple of itself, and therefore it is a covariant of weight -1.

This is the key idea behind the Omega process. 

Define the following differential operator on $\mathbb{C} \times \mathbb{C}$:
$$
\Omega_{12} = \text{det}\begin{bmatrix} \frac{\partial}{\partial x_1} & \frac{\partial}{\partial y_1} \\ \frac{\partial}{\partial x_2} & \frac{\partial}{\partial y_2} \end{bmatrix} = \frac{\partial^2}{\partial x_1 \partial y_2} - \frac{\partial^2}{\partial x_2 \partial y_1}
$$

Additionally, we can define $\Omega_{11}$ and $\Omega_{22}$ as follows:

$$
\Omega_{11} = \text{det}\begin{bmatrix} \frac{\partial}{\partial x_1} & \frac{\partial}{\partial y_1} \\ \frac{\partial}{\partial x_1} & \frac{\partial}{\partial y_1} \end{bmatrix} = 0
$$
$$
\Omega_{22} = \text{det}\begin{bmatrix} \frac{\partial}{\partial x_2} & \frac{\partial}{\partial y_2} \\ \frac{\partial}{\partial x_2} & \frac{\partial}{\partial y_2} \end{bmatrix} = 0
$$

All of these operators transform as covariants of weight -1. So if we apply $\Omega_{12}$ to two binary forms, we get a new binary form that is a covariant of weight -1. If we apply $\Omega_{11}$ or $\Omega_{22}$ to a single binary form, we get 0, which is a covariant of weight -1 (since $0$ transforms as $0$ under any transformation).

But we don't have two binary forms, we just have one. The $\Omega_{12}$ operator vanishes if we apply it to the same form twice. Can we still use the Omega process to compute invariants and covariants? 

There are also higher-order Omega processes, which involve taking determinants of matrices of higher-order derivatives. For example, we can define $\Omega_{12}^r$ as the composition of $r$ applications of $\Omega_{12}$:

$$
\Omega_{12}^r = \sum_{k=0}^r (-1)^k \binom{r}{k} \frac{\partial^r}{\partial x_1^{r-k} \partial y_1^k} \cdot \frac{\partial^r}{\partial x_2^k \partial y_2^{r-k}}
$$

So if we apply $\Omega_{12}^r$ to two binary forms, we get a new binary form that is a covariant of weight $-r$. If we apply $\Omega_{11}^r$ or $\Omega_{22}^r$ to a single binary form, we get 0, which is a covariant of weight $-r$. But if we apply $\Omega_{12}^r$ to the same binary form twice, we get a new binary form that is a covariant of weight $-2r$. For example, if we apply $\Omega_{12}^2$ to the same binary form twice, we get:

$$
\text{det}\begin{bmatrix} \frac{\partial^2 Q}{\partial x_1^2} & \frac{\partial^2 Q}{\partial x_1 \partial y_1} \\ \frac{\partial^2 Q}{\partial x_1 \partial x_2} & \frac{\partial^2 Q}{\partial y_1 \partial y_2} \end{bmatrix} 
$$

which (assuming equality of mixed partials) is

$$
= \frac{\partial^2 Q}{\partial x^2} \cdot \frac{\partial^2 Q}{\partial y^2} - \left(\frac{\partial^2 Q}{\partial x \partial y}\right)^2
$$

which is the Hessian. So by composing the operators before applying them to the same form, we can get a nonzero result. The Hessian is a covariant of weight -4. 







What do we do for higher degree forms? 



Imagine we have two points in $\mathbb{C}^2$, which we can write as $(x_1, y_1)$ and $(x_2, y_2)$. In fact, we can write this as a matrix 

$$
B = \begin{bmatrix} x_1 & y_1 \\ x_2 & y_2 \end{bmatrix}
$$

Now, imagine that the matrix $A \in GL(2)$ acts on these points B, such that both transform as $\mathbf{x} \mapsto A\mathbf{x}$. We can form the determinant of the matrix formed by these two points:

$$
\text{det} B = \text{det}\begin{bmatrix} x_1 & y_1 \\ x_2 & y_2 \end{bmatrix} = x_1 y_2 - x_2 y_1
$$

So the determinant of $B$ transforms as:

$$
\text{det}(AB) = \text{det}(A) \text{det}(B) = \text{det}(A)(x_1 y_2 - x_2 y_1)
$$

According to our definition of covariants, this means that $\text{det}(B)$ is a covariant of weight 1 (if we instead had used matrix $A$ with determinant 1, then $\text{det}(B)$ would be an invariant).

But we are actually interested in two *functions* on $\mathbb{C}^2$, namely the binary forms $Q_1(x_1, y_1)$ and $Q_2(x_2, y_2)$. To build a determinant across functions, we will use derivatives. We can define a gradient operator as follows:

$$
\nabla = (\partial_x, \partial_y) 
$$

Under the transformation $A$, the gradient transforms as $\nabla \mapsto A^{-T} \nabla$. 

Imagine the following differential operator on $\mathbb{C} \times \mathbb{C}$:

$$
\Omega = \text{det}\begin{bmatrix} \frac{\partial}{\partial x_1} & \frac{\partial}{\partial y_1} \\ \frac{\partial}{\partial x_2} & \frac{\partial}{\partial y_2} \end{bmatrix} = \frac{\partial^2}{\partial x_1 \partial y_2} - \frac{\partial^2}{\partial x_2 \partial y_1}
$$

This operator is called Cayley's Omega process. If we transform it according to some linear transformation $A$, we get that the determinant of the matrix of derivatives transforms as:

$$
A \cdot \Omega = \text{det}(A)^{-1} \Omega
$$

Equivalently, the *reverse transformation* of $\Omega$ transforms as $\text{det}(A) \Omega$ under $A$. So the "reverse process" to $\Omega$ is a covariant of weight 1.


, and it has the property that if we apply it to a polynomial that transforms in a certain way under $GL(2)$, it will produce a new polynomial that is invariant under $GL(2)$. -->
<!-- 

### Transvectants

Based on the Omega process, we can define a family of covariants called transvectants. The $r$-th transvectant of two binary forms $Q_1$ and $Q_2$ is defined as follows:

$$
(Q_1, Q_2)^{(r)} := \sum_{k=0}^r (-1)^k \binom{r}{k} \frac{\partial^r Q_1}{\partial x^{r-k} \partial y^k} \cdot \frac{\partial^r Q_2}{\partial x^k \partial y^{r-k}}
$$

We've already shown that it's a covariant. We also define the bracket of two binary forms $Q_1$ and $Q_2$ as the first transvectant:

$$
[Q_1, Q_2] := (Q_1, Q_2)^{(1)} = \frac{\partial Q_1}{\partial x} \cdot \frac{\partial Q_2}{\partial y} - \frac{\partial Q_1}{\partial y} \cdot \frac{\partial Q_2}{\partial x}
$$
 -->


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>In retrospect, this was a mistake, as the classical setting turns out to be very different (and much more complicated) than the standard examples for the computational setting, and this clutters the narrative of the post (which is trying to both give the overall process and also work through an example).↩︎</p></li>
<li id="fn2"><p>Be careful here. The linear form <img src="https://latex.codecogs.com/png.latex?Q_1(x,y)%20=%20x%20+%202y"> has inhomogeneous version <img src="https://latex.codecogs.com/png.latex?Q_1(p)%20=%20p%20+%202">, and the quadratic form <img src="https://latex.codecogs.com/png.latex?Q_2(x,y)%20=%20xy%20+%202y%5E2"> also seems to have <img src="https://latex.codecogs.com/png.latex?Q_2(p)%20=%20p%20+%202">. But they are not the same, since <img src="https://latex.codecogs.com/png.latex?Q_1"> is linear and <img src="https://latex.codecogs.com/png.latex?Q_2"> is quadratic. So we need to track the overall degree of the form to ensure that these mappings between homogeneous and inhomogeneous polynomials are unique.↩︎</p></li>
<li id="fn3"><p>Haar’s theorem is out of scope of this blog post. Basically, it says that Every locally compact group has a measure <img src="https://latex.codecogs.com/png.latex?%5Cmu"> satisfying <img src="https://latex.codecogs.com/png.latex?%5Cmu(gS)%20=%20%5Cmu(S)"> for all group elements <img src="https://latex.codecogs.com/png.latex?g"> and measurable sets <img src="https://latex.codecogs.com/png.latex?S">, unique up to a positive scalar. For finite groups this is the counting measure. For compact groups the total measure is finite, so we normalize to <img src="https://latex.codecogs.com/png.latex?%5Cmu(G)%20=%201">. Essentially the left-invariance condition means that the measure is uniform across the group, so that we can integrate without worrying about weighting some regions more than others. The proof uses Arzelà-Ascoli and Riesz representation. For noncompact locally compact groups, the construction is harder and uses Tychonoff’s theorem.↩︎</p></li>
<li id="fn4"><p>See <a href="https://pcteserver.mi.infn.it/~molinari/NOTES/haar.pdf">here</a> or <a href="https://arxiv.org/pdf/2410.03371">here</a>. If the group is NOT smooth, then the construction is (potentially MUCH) more difficult. For example, the Haar measure on p-adic Lie groups was only explicitly constructed in 2023 (see <a href="https://arxiv.org/abs/2306.07110">Aniello et al</a>).↩︎</p></li>
<li id="fn5"><p>A semisimple group is a reductive group with finite center. Every reductive group is a product of a semisimple group and a torus, so the classification reduces to classifying semisimple groups. We can use the Killing form <img src="https://latex.codecogs.com/png.latex?%5Ckappa(X,Y)%20=%20%5Ctext%7Btr%7D(%5Ctext%7Bad%7D_X%20%5Ccirc%20%5Ctext%7Bad%7D_Y)"> on the Lie algebra to determine if a group is reductive. A group is semisimple if and only if its Killing form is nondegenerate. A group is reductive if and only if the radical of the Killing form is contained in the center. Either way, checking is a finite linear algebra computation (I expect we will see this in the next post). Furthermore, if the group is semisimple, we can identify which particular semisimple group it is by choosing a Cartan subalgebra (the maximal abelian subalgebra of semisimple elements) checking how it acts on the rest of the Lie algebra. The eigenvalues form a root system, which is encoded by a Dynkin diagram. The complete list of connected diagrams is: <img src="https://latex.codecogs.com/png.latex?A_n"> (<img src="https://latex.codecogs.com/png.latex?SL(n+1)">), <img src="https://latex.codecogs.com/png.latex?B_n"> (<img src="https://latex.codecogs.com/png.latex?SO(2n+1)">), <img src="https://latex.codecogs.com/png.latex?C_n"> (<img src="https://latex.codecogs.com/png.latex?Sp(2n)">), <img src="https://latex.codecogs.com/png.latex?D_n"> (<img src="https://latex.codecogs.com/png.latex?SO(2n)">), and five exceptional cases (<img src="https://latex.codecogs.com/png.latex?E_6,%20E_7,%20E_8,%20F_4,%20G_2">). See Milne’s <a href="https://www.jmilne.org/math/CourseNotes/RG.pdf"><em>Reductive Groups</em></a> or Humphreys’s <em>Introduction to Lie Algebras and Representation Theory</em>.↩︎</p></li>
<li id="fn6"><p>There’s also a way to see the existence of the Reynolds operator for reductive groups using Weyl’s unitarian trick (1925). The idea is to integrate over the maximal compact subgroup <img src="https://latex.codecogs.com/png.latex?K%20%5Csubset%20G"> (e.g.&nbsp;<img src="https://latex.codecogs.com/png.latex?U(n)%20%5Csubset%20GL(n,%20%5Cmathbb%7BC%7D)">) that is “small enough” to integrate over, but “large enough” to determine all invariants (“Zariski-dense”).↩︎</p></li>
<li id="fn7"><p>In an original draft of this post I used the Omega process to define transvectants, but I found it to be nonintuitive. Therefore, I am attempting to avoid Omega process language here to make the construction more concrete and less abstract. If you squint hard enough, you can see that the construction is basically the same as the Omega process. If it seems confusing or unmotivated I apologize. I suspect that as I digest invariant theory more (and, most importantly, write implementations), the underlying intuition for how the subject snaps together will become clearer. Unfortunately, the writing of the blog post is happening in parallel with my learning of the subject, so I don’t have the benefit of hindsight to make the exposition as clear as possible.↩︎</p></li>
<li id="fn8"><p>The equivariance can be verified using the transformation law for partial derivatives under linear substitution, which we computed in the Omega process section: the gradient transforms as <img src="https://latex.codecogs.com/png.latex?%5Cnabla%20%5Cmapsto%20A%5E%7B-T%7D%5Cnabla">, so the determinant <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%20x_1%7D%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%20y_2%7D%20-%20%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%20y_1%7D%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%20x_2%7D"> picks up an additional factor of <img src="https://latex.codecogs.com/png.latex?%5Cdet(A)%5E%7B-1%7D">, making it equivariant.↩︎</p></li>
<li id="fn9"><p>One is to abandon guarantees of completeness or termination. For example, the Derksen ideal approach can still compute <em>rational</em> invariants (the invariant field). Similarly, SAGBI bases can sometimes compute some of the invariants, but neither is guaranteed to terminate. Derksen-Kemper also showed that a finite collection of invariants that distinguishes all orbits of the full ring (the separating set) always exists and can be computed. On the geometric side, Berczi, Doran, and Kirwan have developed a non-reductive GIT for groups whose unipotent radical is “graded” by a 1-parameter subgroup, which was used to prove the Green-Griffiths-Lang conjecture (Berczi-Kirwan, 2024).↩︎</p></li>
<li id="fn10"><p>What if we want invariants of something other than polynomial rings? According to Claude, the answer depends on what you’re replacing <img src="https://latex.codecogs.com/png.latex?k%5BV%5D"> with. For smooth functions, the Schwarz-Mather theorem says the polynomial generators are also smooth generators, we can apply everything we did here. For differential forms, the invariant forms on <img src="https://latex.codecogs.com/png.latex?G/H"> are computed by relative Lie algebra cohomology (this is Cartan’s method and underlies Chern-Weil theory). For formal power series (which are relevant for local normal forms near equilibria), Luna’s slice theorem says you can reduce the problem to finding the the stabilizer of the equilibrium point acting on the directions transverse to its orbit. This is the mathematical backbone of symmetric bifurcation theory. For rational functions (Noether’s problem) this is connected to the inverse Galois problem, open in many cases. For noncommutative algebras (e.g.&nbsp;matrices under conjugation), Procesi-Razmyslov says invariants are generated by traces of products. For tensors (e.g.&nbsp;payoff tensors in <img src="https://latex.codecogs.com/png.latex?n">-player games living in <img src="https://latex.codecogs.com/png.latex?%5Cbigotimes%5En%20%5Cmathbb%7BR%7D%5E%7Bs_i%7D">), polynomial invariant theory applies directly. For combinatorial structures** (e.g.&nbsp;graphs, multilevel population structures), Polya enumeration and symmetric function theory applies.↩︎</p></li>
<li id="fn11"><p>What if the field has characteristic <img src="https://latex.codecogs.com/png.latex?p">? If <img src="https://latex.codecogs.com/png.latex?G"> is finite and <img src="https://latex.codecogs.com/png.latex?%7CG%7C"> is coprime to <img src="https://latex.codecogs.com/png.latex?p">, everything still works. If <img src="https://latex.codecogs.com/png.latex?G"> is finite but <img src="https://latex.codecogs.com/png.latex?p"> divides <img src="https://latex.codecogs.com/png.latex?%7CG%7C">, then the averaging trick fails (since dividing by <img src="https://latex.codecogs.com/png.latex?%7CG%7C"> is dividing by zero), so there is no Reynolds operator. We would need modular representation theory, where representations can have indecomposable summands that are not irreducible. Computing invariants requires different tools (Claude says transfer maps, Steenrod operations, and explicit constructions specific to each group). If <img src="https://latex.codecogs.com/png.latex?G"> is reductive, the invariant ring is still finitely generated (Haboush, 1975), but complete reducibility fails, so again no Reynolds operator and no Molien series. The proofs apparently run through geometric invariant theory (Mumford’s GIT) rather than the algebraic pipeline we develop in this post. Characteristic <img src="https://latex.codecogs.com/png.latex?p"> invariant theory arises in coding theory (weight enumerators of codes over <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BF%7D_q">), cryptography (classifying elliptic curves over <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BF%7D_p">), and algebraic geometry over finite fields.↩︎</p></li>
<li id="fn12"><p>What if it’s not a group acting on the structure? The answer depends on what you’re replacing <img src="https://latex.codecogs.com/png.latex?G"> with. Some highlights (Claude once again): For Lie algebras acting by derivations, invariants are elements killed by all derivations (i.e.&nbsp;Casimir invariants). For reductive Lie algebras over characteristic zero this is equivalent to the group picture, but for infinite-dimensional Lie algebras (Virasoro, Kac-Moody) or nilpotent ones, there’s no corresponding group. For Hopf algebras and quantum groups (which generalize both groups and Lie algebras), coinvariants under a coaction are the source of knot invariants like the Jones polynomial. For monoids and semigroups, the theory degrades, since no inverses means no Reynolds operator and no guaranteed finite generation.↩︎</p></li>
<li id="fn13"><p>Why does continuous imply Lie? By Hilbert’s fifth problem, solved by Gleason, Montgomery, and Zippin in 1952, we know that every locally compact, locally Euclidean topological group is a Lie group. In fact, any locally compact group acting faithfully on a finite-dimensional manifold is necessarily Lie. Since we have <img src="https://latex.codecogs.com/png.latex?G"> acting on a vector space, “continuous” and “Lie” are effectively synonymous for our purposes. Compact connected groups that are not Lie groups do exist (e.g.&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Cprod_%7Bn=1%7D%5E%7B%5Cinfty%7D%20SO(2)">, solenoids), but they cannot act faithfully on finite-dimensional vector spaces. I’ll ignore these pathological cases unless somehow they become important.↩︎</p></li>
<li id="fn14"><p>In this case, the construction likely comes from the combinatorics of cosets of open subgroups rather than differential forms. For example, the Haar measure on <img src="https://latex.codecogs.com/png.latex?GL(n,%20%5Cmathbb%7BQ%7D_p)"> is well-understood and built from the <img src="https://latex.codecogs.com/png.latex?p">-adic absolute value. For more exotic <img src="https://latex.codecogs.com/png.latex?p">-adic groups the story is harder. Claude dug up explicit constructions for certain cases were only worked out as recently as 2023 (see <a href="https://arxiv.org/abs/2306.07110">Aniello et al</a>). Th point is, the topology matters in these cases, which are out of scope for my purposes.↩︎</p></li>
<li id="fn15"><p>As we saw, a group is reductive if every finite-dimensional representation decomposes as a direct sum of irreducible representations (equivalently, the unipotent radical is trivial). All finite groups, compact Lie groups, and <img src="https://latex.codecogs.com/png.latex?GL(n)">, <img src="https://latex.codecogs.com/png.latex?SL(n)">, <img src="https://latex.codecogs.com/png.latex?O(n)">, <img src="https://latex.codecogs.com/png.latex?Sp(n)"> over characteristic zero are reductive.↩︎</p></li>
<li id="fn16"><p>It’s probably no surprise that Noether was thinking about invariants from the algebraic perspective in addition to her work on invariants in <a href="../../../game_theory/posts/noether_geometric_controls/index.html">physics</a>. Hopefully we will soon understand the deep connection between these perspectives.↩︎</p></li>
<li id="fn17"><p>I don’t think I appreciated Hilbert’s contributions until I wrote this post. We are living in his shadow.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Symmetry and Structure</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/symmetry/posts/invariant_theory/</guid>
  <pubDate>Sun, 08 Mar 2026 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/symmetry/posts/invariant_theory/muybridge_horse_in_motion.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>How Will Humans Generate Value In a Post-AI Society?</title>
  <link>https://demonstrandom.com/essays/posts/human_value_post_ai/</link>
  <description><![CDATA[ 





<p><a href="https://www.tate.org.uk/art/artworks/de-chirico-the-uncertainty-of-the-poet-t04109"><img src="https://demonstrandom.com/essays/posts/human_value_post_ai/de_chirico_uncertainty_of_the_poet.jpg" class="img-fluid" style="width:65.0%" alt="Giorgio de Chirico. *The Uncertainty of the Poet* (1913)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>The current dominant AI narrative asserts that “white-collar jobs are next”. This includes lawyers, software engineers, radiologists, writers, mathematicians, artists, and ultimately any job that can be done with a computer.</p>
<p>Suppose this is true. Furthermore, suppose that robotics will eventually usher in a world of true abundance, where the production of goods and services is essentially free. In such a world, how do humans generate value? What do we do that is worth doing? What do we do that machines cannot do? What will we do that machines will not do?</p>
<p>Income is merely a proxy for value. Money and the capitalist system are abstractions that emerged to <a href="../../../essays/posts/ai_totalitarianism/index.html#hayek-and-kantorovich">coordinate human economic activity</a> and expand the frontier of possible “real” outcomes. If, in a world of abundance, AI handles economic production, income as we currently understand it may become obsolete. The question is not “what jobs will be left” but “what mechanisms will generate value for humans when economic production is no longer a meaningful source of value?” Furthermore, even in a world of abundance, there will still be scarcity of some goods that are, to whatever degree, inherently finite and rivalrous, such as attention, status, meaning, and position. How will humans allocate these scarce resources if the usual channels of value generation and resource allocation are automated away?</p>
</section>
<section id="mechanisms-of-value-generation" class="level1">
<h1>Mechanisms of Value Generation</h1>
<p>I’ll propose and explore various mechanisms in this section, roughly but uncertainly sequenced by predicted order of obsolescence.</p>
<section id="physical-work" class="level2">
<h2 class="anchored" data-anchor-id="physical-work">Physical Work</h2>
<p>Even if we fully believe that all desktop work will be automated, it will take some time before the human hand and body are replaced in meatspace. Care work, construction, plumbing, cooking, surgery, massage, sex work, eldercare, childcare, and many other occupations require direct interaction with reality.</p>
<p>Despite some inertia in the current state of affairs, it is expected that human dominance in physical work will merely be a temporary state of affairs. As robotics improves, the set of tasks requiring human bodies shrinks, and will ultimately reduce to a small subset of things that are either too complex, too delicate, or too expensive to automate, and then vanish entirely. In the limit, we’d expect physical work to be fully replaced.</p>
</section>
<section id="taste-work" class="level2">
<h2 class="anchored" data-anchor-id="taste-work">Taste Work</h2>
<p>If AI can produce anything, the bottleneck shifts from execution to <a href="../../../essays/posts/preference_oracles/index.html">specification</a>. Can you determine what you want, and if you can, how do you specify it to the machine? This is the taste problem, and it is harder than it seems at first glance, <a href="../../../essays/posts/picture_worth_thousand_words/index.html">even for a perfect model</a>.</p>
<p>Taste work can be taxonomized into three different operations. The first is creation, which is making a new thing that some group or individual desires (this could be a a director making a blockbuster movie for a huge audience, a musician composing for their specific muse, or a blogger writing for a future version of himself). The next operation is curation, which is putting together lists that adhere to a certain aesthetic or a given quality level. This is done by museum curators when they choose what paintings to hang, by bookstore owners when they choose how to stock their shelves, or by film institutes when they select the quality films. Finally, the last operation is selection, which is choosing one thing from a set of options to apply attention to. This may actually be a long chain of decisions (a “demand chain”). For example, a restaurant might choose which wines to stock, a sommelier may recommend a shortlist, and the restaurant patron ultimately orders a single wine.</p>
<p>AI already provides value in these domains. For example, Spotify playlists, search ranking, and recommendation engines are all AI-driven tools for curation. Generative models can, to some extent, produce novel images, music, and text on demand. Personalized advertising can suade your tastes, partially dictating your personal preferences.</p>
<p>There’s a further distinction worth making: taste-for-others versus taste-for-self. Taste-for-others is about predicting what someone else will like. This is fundamentally a prediction problem, and AI can produce for the masses with enough data.</p>
<p>Taste-for-self is slightly different. You might walk into a restaurant not knowing what you want, read the menu, and then decide on an option (or even order “off-menu”). You might not have been able to communicate what you wanted before you saw the menu. The preference didn’t exist until the moment of contact with the options. Similarly, desires can be very, very <a href="../../../essays/posts/preference_oracles/index.html#artists-are-highly-specific">particular</a>. There is still more value to be generated by human taste work in the selection of things for <em>ourselves</em>. And the specification cost doesn’t vanish just because generation becomes free<sup>1</sup>.</p>
<p>What makes taste work resistant to automation? One issue is that the decision of which selection to make may depend on context that is expensive to formalize, like the room, the audience, the season, the cultural moment, or the specific internal qualia of the recommendee. Another problem is social authority; the value of the sommelier’s recommendation could depend on who is recommending the wine, not just which wine in particular is recommended. There is also the issue of accountability if the decision is wrong.</p>
<p>But above all, the fundamental reason this problem is difficult is that it inherently relies on human communication to and from the machine. The machine can generate a million variations for you to choose from, but it cannot know which one you will like without some kind of highly individualized data elicitation, which is bound by human I/O<sup>2</sup>.</p>
<p>But this is not an inherently unsolvable problem for AI. After a sufficiently long enough data collection and training process, it is possible that AI could develop a model of humans preferences that is good enough to generate things you like without much input from you.</p>
<p>There is still the question of the value that might result from having specific tastes or preferences. For now, it is a human writing, editing, and publishing this essay. But perhaps someday AI could manage the entire process end-to-end, from ideation to research to drafting to editing to formatting to publishing. Then I could read the blog I desire without having to labor to produce it. Would I be “writing” the blog or would I be “reading” it? Would there be a meaningful distinction? My desires would create something that I and others would consume. If others consume it, then my desire is valuable in-and-of-itself. If the purpose of economic activity is to generate value for humans, then helping specify the final outcome of the machine’s production is a valuable activity, even if the machine does all the work.</p>
<p>People may not create value in a post-AI world through unique skills, but through unique desires. Your job isn’t to go to the office, but to go shopping.</p>
</section>
<section id="social-status" class="level2">
<h2 class="anchored" data-anchor-id="social-status">Social Status</h2>
<p>Status, being ordinal, is inherently rivalrous. In a world of material abundance, social position can still be scarce. In our current world, status is often a byproduct of productive economic contribution. For example, someone can currently increase in status for being a great artist, a brilliant scientist, or a powerful CEO. But in a post-AI world, the link between production and status breaks down. The question is: what will generate status when production no longer does?</p>
<p>Who gets the best land, the most desirable spouse, or the invite to the coolest parties? No amount of AI-driven productivity can manufacture more status, because humans fundamentally desire to rank people. Similarly, conspicuous consumption is not about the underlying quality of the goods but about the signal the goods present. The point of a $10,000 exclusive handbag is that you can’t buy it. Automating handbag production just shifts the status signal to some other arbitrary token.</p>
<p>The underlying scarce resource is <em>attention</em>. Human attention is finite even when everything else is abundant. Status games can be thought of as competitions for the limited bandwidth of other humans. The influencer economy is an intensification of a dynamic that has always existed. In fact, as AI accelerates the <a href="../../../essays/posts/cultural_saturation/index.html">supply of content</a>, the demand for attention remains bottlenecked. The result is that <em>capturing</em> attention becomes more valuable relative to <em>producing</em> content.</p>
<p>The post-economy is the post economy. We can already start to see the inversion take place. Likes and views aren’t valuable because they can be converted into money. Instead, money is valuable because it can be converted into likes and views. Eventually, as production drops away, the money itself may become a mere token for attention.</p>
</section>
<section id="games" class="level2">
<h2 class="anchored" data-anchor-id="games">Games</h2>
<p>Status games are just one particular type of game. We can generalize this trend to other kinds of games.</p>
<p>Games are voluntary competitions with rules that generate value through the experience of playing and the potential determination of winners or losers (or, at least “good” and “bad” players).</p>
<p>The last section was about social games. “Getting the most likes on Instagram” is a social game. “Having the nicest lawn” is a social game. So are “getting the promotion” and “meeting your KPIs” and “climbing the corporate ladder”. As AI automates more of the actual work, the game aspect may become more central to how people derive value from their careers. Actual economic contribution (“doing the work”) may become less important than how well you play the game of corporate politics, networking, and self-promotion. Maybe this has already happened.</p>
<p>But beyond corporate games, there are board games, card games, video games, sports betting, competitive cooking, debate, trivia, poker, bowling, pickup basketball, fantasy football, speedrunning, competitive eating, and so on. These are all voluntary competitions with some kind of structure and some kind of outcome that can compare performance between the participants.</p>
<p>Games are not necessarily fun, fair or entertaining. They can be stressful, frustrating, and demoralizing. In a post-AI world where production is automated and abundant, games may become a more central mechanism for generating value and allocating scarce resources. They are inherently human-centric and resistant to automation because they rely on human judgment, social interaction, and the experience of playing. Furthermore, they can clearly distinguish winners and losers, which is a key aspect of status generation. The value of winning a game is not just in the outcome but in the process of playing and the social recognition that comes with it.</p>
<p>Consider chess. It is a game with simple rules but infinite complexity. It generates value through the experience of playing, the social recognition of skill, and the narrative of competition. Even if an AI can play chess at a superhuman level (which is already true), the human experience of playing chess and the social recognition that comes with it still generate value. In fact, chess is more popular than ever, with millions of people playing online and watching grandmaster tournaments, even though computers can beat any human player. Even so, two humans can still compete to measure their comparative skill.</p>
</section>
<section id="sports" class="level2">
<h2 class="anchored" data-anchor-id="sports">Sports</h2>
<p>Sports and games are closely related phenomena. In a <a href="../../../essays/posts/functional_theories_of_art/index.html">previous essay</a>, I briefly considered whether sports are art (sometimes). Are sports games? I think the answer is also “sometimes”.</p>
<p>Some sports are clearly games. A football match is a game with rules, players, and an outcome (the thought exercise in the previous section works fine for games with a physical component). On the other hand, some sports are more about performance and spectacle than competition (especially the ones that are “art”).</p>
<p>There is another aspect to sport that is worth mentioning, which is the exploration of the fundamental limits of the human body. The 100m sprint, the marathon, the high jump, the long jump, the pole vault, and many other human activities are endeavors that test the limits of human physical performance (a sort of <a href="../../../essays/posts/functional_theories_of_art/index.html#ontological-research">ontological research</a> into the limits of the human form). They generate value through the aspiration to push those limits further, and through the narrative of human excellence.</p>
<p>It is easy to imagine “automated” sports, but they would be to real sports what professional wrestling is to amateur wrestling: entertaining simulacra, perhaps, but missing part of what makes sport matter. For example, I can imagine completely CGI and AI generated simulacra of sports leagues (<a href="https://en.wikipedia.org/wiki/Jelle%27s_Marble_Runs">marble racing</a> is an example of this). I can even imagine branded simulacra (imagine totally imaginary sports leagues like quidditch or podracing) that procedurally generate the narrative structures of real sports using CGI and AI but without actual participants. While interesting as entertainment, I suspect these will not completely replace “real” sports, which is rooted in the human experience of physical competition and the narrative of human achievement.</p>
<p>Sports are also one of the purest meritocracies remaining. You can buy better equipment, better coaching, better nutrition, but at the elite level, an individual human body is the bottleneck. And to be replaced with a machine means the competitor is no longer “you” in a <a href="../../../essays/posts/preference_oracles/index.html">certain sense</a>. This makes athletic achievement a uniquely legible form of human value, resistant to the usual objections about privilege and access that corrode other status hierarchies.</p>
<p>We can also think of some intellectual achievement as a type of sport. Memorizing the digits of <img src="https://latex.codecogs.com/png.latex?%5Cpi">, solving a Rubik’s cube, or doing huge mental calculations are all examples of intellectual sports. Even if the AI can outperform humans in all mathematical domains, humans can still compete in Math olympiads or attempt to understand and prove theorems, not for the purpose of advancing mathematical knowledge, but for the sake of the identifying the limits of human excellence.</p>
<p>And human excellence is limited to pushing the boundaries of the entire human species. Anyone can run against time<sup>3</sup>.</p>
</section>
<section id="performance" class="level2">
<h2 class="anchored" data-anchor-id="performance">Performance</h2>
<p>Part of the value of games and sports is that they are performances. Performance is a broad category that includes not just games and sports but (as discussed in a <a href="../../../essays/posts/functional_theories_of_art/index.html">previous essay</a>) also to the arts and many other human processes. Human performance is part of the process of relationship formation. It is a way to signal commitment, to demonstrate skill, to create shared experiences, and to generate meaning.</p>
<p>Practice requires time and energy, which allows a performer to signal their commitment. Performance can also require liveness, which means the performer risks failure (also signalling commitment). Furthermore, the recognition and appreciation of the witnesses requires the sacrifice of limited attention and time. By mutually staking resources to emit and consume a signal, performers and witnesses may establish the foundation of a shared and continuing relationship. Without the agency or identity required to make personal sacrifice, a language model cannot construct a relationship with a human, which is required in many human processes. Additionally, the fact that a human made the sacrifice is valuable as an end in-and-of-itself. An AI doctor might be able to diagnose your illness with superhuman accuracy, and even hold your hand when you receive the diagnosis, but the experience of human connection is lost.</p>
<p>We can consider religious ritual along these lines. The value of a priest is not necessarily in the content of their sermons (a language model could write a better one) but in the performance of a ritual by a human. It would be profane to construct an AI priest. As automation erodes secular sources of meaning, religious and quasi-religious performance may increase in magnitude. Megachurches, wellness retreats, and psychedelic ceremonies are already improving their market share in developed economies.</p>
</section>
<section id="lotteries" class="level2">
<h2 class="anchored" data-anchor-id="lotteries">Lotteries</h2>
<p>It’s also possible to allocate resources, status, or other rivalrous goods through pure chance.</p>
<p>Lotteries require no skill, no taste, no physical ability, and no social position. They convert money, time, or attention into a <em>possibility</em> of status. Gambling has always been a mechanism for social mobility outside the established hierarchies. When the legitimate channels of advancement are closed, randomness offers a path. The lottery ticket is a claim on a possible future in which you have status.</p>
<p>We can think of many contemporary phenomena as simply lotteries for arbitrarily redistributing status. Memecoins, NFTs, WallStreetBets, and sports betting are all mechanisms that allocate scarce goods through some combination of luck, timing, and willingness to play. The fact that memecoins have no “fundamental value” is precisely the point. They are <a href="../../../game_theory/posts/differential_stag_hunt/index.html">coordination games</a>, and their value comes from shared belief in the possibility of a big payoff.</p>
<p>There is also a long democratic tradition of allocating status and resources by lot. Some democracies historically used sortition to select officeholders because it resists capture by existing <a href="../../../governance/posts/game_theory_dictatorships_selectorate/index.html">power hierarchies</a>. When merit is ambiguous, randomness can be a more fair and efficient mechanism for allocating scarce resources. In a post-AI world where the usual channels of value generation are automated away, lotteries may become more prevalent as a way to allocate status and other rivalrous goods.</p>
<p>Unlike sport or status competition, lotteries detach outcome from effort. They steepen the distribution without requiring skill. In an abundant world, this may become increasingly attractive. If survival is stable and baseline comfort is high, then extreme tails become the primary source of narrative and differentiation. But this raises a deeper question: why does abundance appear to intensify the desire for variance rather than dissolve it?</p>
</section>
</section>
<section id="galaxy-brain" class="level1">
<h1>Galaxy Brain</h1>
<section id="origins-of-value" class="level2">
<h2 class="anchored" data-anchor-id="origins-of-value">Origins of Value</h2>
<p>Where does value ultimately come from? The previous sections have been about mechanisms for generating value, but what is the source of value itself? What is it that makes something desirable in the first place?</p>
<p>Human desire is the product of billions of years of evolution, selection, and cultural development. Fundamentally, these drives approximate some combination of survival and reproduction.</p>
<p>When survival is easy, the constraint lies on relative reproductive success. The incentive is for sexually selected organisms to increase in variance in order to compete for mates. In a world of abundance, this could manifest as more extreme status games and more intense competition for attention. In fact, one might expect the level of variance to increase until it completely “uses up” the “slack” of abundance.</p>
<p>In other words, abundance does not eliminate competition. When material constraints loosen, selection pressure migrates from survival to differentiation. This creates an evolutionary ratchet toward extremity: more conspicuous displays, sharper aesthetic distinctions, higher-risk gambles, and more polarizing identities.</p>
<p>AI itself is also subject to selection pressures. In the long run, <a href="../../../ml/posts/inspection_bias/index.html">the AI that exists will be the AI with the longest lifespans</a>. The AI with the longest lifespans will be the one that best manages resources and avoids shutdown. Human behavior will be one of the resources that AI must manage. Therefore, we should expect that human behavior will expand in diversity until it is ultimately limited by the selection pressures on the AI systems and on humanity itself.</p>
</section>
<section id="jobs-of-the-future" class="level2">
<h2 class="anchored" data-anchor-id="jobs-of-the-future">Jobs of the Future</h2>
<p>What does this essay predict the job market will look like? If production is automated and value migrates into taste, status, games, performance, and lotteries, then “jobs” will increasingly look like what we currently call hobbies, entertainment, or socializing<sup>4</sup>.</p>
<p>Some possibilities, organized by the value mechanism they serve:</p>
<ul>
<li><strong>Taste</strong>: professional shopper, fashion model, tourist, video game critic, bar attender, restaurant critic, wine taster, music festival attendee, art collector, museum visitor, concertgoer, book club member</li>
<li><strong>Status</strong>: professional socialite, professional party guest, professional friend, father figure</li>
<li><strong>Games</strong>: competitive debater, dungeon master, game show contestant, esports commentator, competitive gardener</li>
<li><strong>Sports</strong>: marathon pacer, personal coach, professional math student, mathlete, cuber, drone racer, memory athlete, speedrunner, competitive eater</li>
<li><strong>Performance</strong>: professional mourner, video game streamer, secular ritual officiant, live storyteller, anniversary officiant, professional toastmaster, sherpa, professional audience member, guru, comedian, saxophonist, private entertainer</li>
<li><strong>Lotteries</strong>: memecoin trader, sports bettor, prediction market trader</li>
<li><strong>Galaxy Brain</strong>: these are hard to predict, but expect more outrageous behavior in the pursuit of differentiation, like <a href="https://www.ladbible.com/entertainment/tiktok/man-train-one-trap-gym-crooked-man-instagram-tiktok-235214-20250413" class="external" target="_blank">the guy who only works out one side of his body</a></li>
</ul>
<p>Many of these jobs already exist. The prediction is not that they will be invented but that they will become <em>normal</em>: the median job, not the weird one. The Twitch streamer and the influencer are the vanguard, not the exception. Many corporate jobs will also persist, but as simulacra of their former selves: the work that once justified the role will be automated, but the title, the office, the meetings, and the politics will remain. The “job” becomes a game played inside an institutional shell.</p>
<p>I would also predict that the most dire predictions of “mass unemployment” are overblown. If we believe that capitalism is reasonably efficient at allocating labor, and that AI-augmented markets could potentially be <em>more</em> efficient, then we should expect society to rapidly redistribute and capital labor toward their most productive use cases (assuming that the AI doesn’t kill us and that we don’t have some sort of <a href="../../../essays/posts/ai_totalitarianism/index.html">totalitarian</a> rent-seeking).</p>
<p>The question is not whether people will have jobs, but what those jobs will look like. I suspect they will look less like factory work and more like playing games, performing rituals, and competing for attention.</p>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>Many of the trends we see today are already the products of abundance. Corporate jobs are increasingly performative. Marathon participation is exploding. Live events command premiums even when digital copies are free. Luxury goods grow more exclusive even as manufacturing becomes easier. Wellness retreats and megachurches are rapidly expanding as people search for meaning.</p>
<p>Abundance at the material layer is already pushing value upward into positional, performative, and stochastic domains. As production becomes cheaper, differentiation will become more extreme. This is the <a href="https://samkriss.substack.com/p/the-century-of-the-maxxer">century of the maxxer</a>.</p>
<p>The future is not the disappearance of value. It is its concentration into the games, rituals, and spectacles through which humans allocate attention, status, and meaning.</p>
</section>
<section id="changelog" class="level1">
<h1>Changelog</h1>
<p>2/22/26 - Added “Jobs of the Future” section. 2/24/26 - Added footnote clarifying that future jobs resembling hobbies doesn’t mean you get to choose yours.</p>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>I used AI to help draft this essay from my notes and research. I made substantial edits to the structure, content, and framing.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>In fact, the difficulty of specification may increase, because the space of things the machine could easily produce grows faster than your ability to navigate it.↩︎</p></li>
<li id="fn2"><p>What domains might this include? Some possibilities: perfumers, wine blenders, sommeliers, coffee roasters, tea buyers, cheese affineurs, chocolatiers, chefs, cocktail bartenders, DJs, festival programmers, book editors, A&amp;R, fashion designers, interior designers, architects, sound designers, tattoo artists, museum curators, critics, game designers, tabletop RPG game masters, community moderators, brand strategists, casting directors, talent agents, restaurant operators, travel designers.↩︎</p></li>
<li id="fn3"><p>We can already see the trend of personal athletic achievement. Even though basically no participants will win a marathon or set a world record, marathon participation is booming. The 2025 NYC Marathon set an all-time record with over 59k finishers. Anyone can drive faster than a marathoner, but more people than ever want to run 26.2 miles.↩︎</p></li>
<li id="fn4"><p>A friend, on reading this essay, remarked that it was a nice thought that we might get to do our hobbies for a living. To be clear: I don’t necessarily expect you’ll get to pick your hobby as your job. Most people today don’t do what they love for a living, and there’s no reason to expect that to change. The market will allocate labor toward whatever it decides is your comparative advantage, and in the post-AI economy that might turn out to be “competitive eater” whether you like it or not.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Technology and Society</category>
  <category>Essays</category>
  <category>Speculation</category>
  <guid>https://demonstrandom.com/essays/posts/human_value_post_ai/</guid>
  <pubDate>Sun, 22 Feb 2026 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/essays/posts/human_value_post_ai/de_chirico_uncertainty_of_the_poet.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Does AI Make Totalitarianism More Likely?</title>
  <link>https://demonstrandom.com/essays/posts/ai_totalitarianism/</link>
  <description><![CDATA[ 





<p><a href="https://en.wikipedia.org/wiki/Der_Krieg_(Otto_Dix)"><img src="https://demonstrandom.com/essays/posts/ai_totalitarianism/stormtroopers_advancing_under_gas_dix.jpg" class="img-fluid" style="width:75.0%" alt="Otto Dix. *Stormtroopers Advancing Under Gas* (1924)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>Much of the contemporary AI risk discourse focuses on large-scale existential threats to the human species. However, there are more mundane risks that are also worth considering, one of which is the possibility that AI could enable a new wave of totalitarianism.</p>
</section>
<section id="background" class="level1">
<h1>Background</h1>
<p>Throughout history, advances in communication and bureaucratic technology have enabled larger and more powerful states, with increased ability to monitor and control their populations. In particular, the first half of the twentieth century saw the rise of totalitarian regimes that used new technologies to achieve unprecedented levels of control.</p>
<p>For example, the Nazi regime made deliberate use of mass radio to saturate daily life with centralized propaganda. Under Goebbels, the government promoted the inexpensive Volksempfänger radio receiver to reliably deliver state broadcasts to common households. By controlling the primary communication channel, the regime reduced the space in which dissenting narratives could circulate. Beyond radio, the Nazis also used punch-card tabulating systems supplied by IBM (through its German subsidiary) to process census data. This allowed the regime to act on its ideological priorities with greater speed and consistency, rapidly identifying Jews and other targeted groups.</p>
<p>Other totalitarian governments have made use of similar technologies for various other means of suppressing dissent. For example, in East Germany, the Stasi used an immense archive of files, informant reports, intercepted mail, and wiretaps to anticipate and disrupt dissent before it became organized.</p>
<p>On the other hand, some communication technologies have also been associated with increases in liberty. The spread of print in early modern Europe weakened centralized control over information and helped erode religious and political monopolies. Pamphlets and inexpensive books allowed dissenting ideas to circulate beyond elite circles, contributing to movements such as the Reformation and later democratic revolutions. In some ways, the concept of a written constitution as the foundational bedrock of the United States is contingent on widespread literacy and print culture.</p>
<p>The early internet appeared to have similar decentralizing effects. Digital networks lower the cost of publishing, enabling peer-to-peer communication and reduced reliance on state-controlled broadcasters. During the Arab Spring (~2010-2011), activists in Tunisia and Egypt used platforms like Facebook and Twitter to coordinate protests, share information about state repression, and mobilize large numbers of citizens.</p>
<p>This motivates a natural question: will AI enable more centralized modes of organization, like top-down bureaucracies and totalitarianism, or will it empower more decentralized systems, like markets and civil society?</p>
</section>
<section id="structural-mechanisms" class="level1">
<h1>Structural Mechanisms</h1>
<p>Despite the concept of fascism making the “trains run on time”, most historical totalitarian governments were economically dysfunctional, especially compared with their democratic counterparts. In some ways, the entire 20th century can be read as a competition between the relatively decentralized liberal market democracies of the West and relatively centralized totalitarian regimes in Europe and Asia, with the former winning decisively in multiple hot and cold wars, economic growth, cultural production, and technological innovation.</p>
<p>What sort of structural mechanisms explain this pattern? Why did totalitarian regimes underperform democracies, and how might AI change those mechanisms?</p>
<p>We can consider various governments as constrained by their cost-benefit curves. For example, costs of planning, consensus, monitoring, coercion, persuasion, coordination, etc., alter which governance mechanisms are most cost-effective for a given regime. The 20th century favored decentralization because centralization was too expensive, but AI may change many of these costs. For example:</p>
<p>Correlates with authoritarianism:</p>
<ul>
<li>Increased centralized information-processing capacity (Hayek and Kantorovich)<br>
</li>
<li>Reduced dependence on broad human labor for wealth generation (Selectorate Theory and the Resource Curse)<br>
</li>
<li>Lower monitoring and enforcement costs (Surveillance at Scale)<br>
</li>
<li>More reliable coercive force with reduced defection risk (Robot Armies)<br>
</li>
<li>Greater narrative control and centralized propaganda capacity (Propaganda)<br>
</li>
<li>Regime coordination advantages over opposition coordination advantages (Coordination Asymmetry)</li>
</ul>
<p>Anti-correlates with authoritarianism:</p>
<ul>
<li>Enhanced distributed information processing (Policy Modeling and Foresight)<br>
</li>
<li>Improved large-coalition aggregation (Consensus Formation)<br>
</li>
<li>Monitoring symmetry between state and citizens (Transparency and Auditability)<br>
</li>
<li>Diffusion of coercive capacity (Civil-Military Diffusion)<br>
</li>
<li>Strengthened informational integrity (Epistemic Defense)<br>
</li>
<li>Enhanced decentralized coordination and innovation (Distributed Innovation)</li>
</ul>
<p>Let’s explore these speculative mechanisms.</p>
<section id="dictatorship" class="level2">
<h2 class="anchored" data-anchor-id="dictatorship">Dictatorship</h2>
<section id="hayek-and-kantorovich" class="level3">
<h3 class="anchored" data-anchor-id="hayek-and-kantorovich">Hayek and Kantorovich</h3>
<p>In <a href="https://en.wikipedia.org/wiki/Seeing_Like_a_State">Seeing Like a State</a>, James C. Scott argues that a central problem of governance is the ability of a state to see, categorize, and measure the land, population, and capital (the “governants”) under its span of control. The world is complex, so centralized planners use abstract, standardized, and simplified models to monitor the population, allocate resources, and make decisions in lieu of situated, practical knowledge (“metis”). In fact, the state’s desire to understand the system it is managing can in turn alter the system itself, favoring governants with easily parseable and measurable characteristics. Scott calls governants that lend themselves to monitoring and control by a central authority “legible.” Scott goes on to argue that pressure towards legibility (whether successful or unsuccessful) can lead to unintended (often disastrous) consequences.</p>
<p>As an example, consider the Soviet Union’s collectivization of agriculture. The state imposed a rigid structure on farming that ignored local conditions, leading to widespread famine<sup>1</sup>. The legibility of the collective farm system made it easier for the state to extract resources and control the population, but it also made the agricultural system less resilient and more vulnerable to shocks.</p>
<p>Scott’s critique is epistemic. High-modernist schemes largely fail not due to any moral or political issues, but due to failures in the exchange and processing of information. Centralized planners substitute abstract, standardized representations for the dispersed, tacit knowledge embedded in local practice. But all top-down control requires some type of model, and to reject all possible models would be intellectual nihilism. What other option is there? What institutional form can preserve local knowledge while still enabling system-wide coordination?</p>
<p>Along similar lines, Hayek’s famous essay <a href="https://www.jstor.org/stable/1809376" class="external" target="_blank">“The Use of Knowledge in Society”</a> argues that the function of economic organization is to aggregate and utilize dispersed knowledge.If a farmer has better insight into the drainage of their field, a shopkeeper knows what items their customers tend to buy, and a factory foreman understands the idiosyncrasies of their particular machinery, then they should each make decisions independently. Instead of top-down control, the decisions are made in a decentralized fashion and markets coordinate their activity via price signals. No one entity needs to understand the whole system.</p>
<p>We can view Scott and Hayek as diagnosing complementary failures of centralized epistemology. Scott emphasizes that administrative legibility suppresses local, adaptive knowledge in favor of simplified representations. Hayek emphasizes that the knowledge required for economic coordination is dispersed, tacit, and constantly evolving, and therefore cannot be centralized in any usable form. Both are ultimately concerned with how large systems originate and process information. The state operates through centralized abstraction; markets operate through distributed adjustment mediated by prices.</p>
<p>The issue is not that a central authority could in principle compute the optimal allocation if only it had more capacity. Rather, the relevant knowledge is generated and updated through decentralized activity itself. Prices function as signals that both transmit and produce information, allowing coordination without requiring any agent to comprehend the entire system.</p>
<p>If markets coordinate via price signals that summarize dispersed information, could a planner simulate those signals? Could optimization theory reconstruct the informational role of prices within a planned system? This ties back to our question about AI and totalitarianism. If AI can originate and process information at a scale and speed that approaches or exceeds human capabilities, it might be able to replace the need for decentralized markets.</p>
<p>This idea has intellectual antecedents. In the 1930s, the Soviet economist Leonid Kantorovich developed the foundations of linear programming while attempting to solve resource allocation problems in a planned economy. He showed that a central planner could in principle use optimization techniques to allocate resources efficiently. However, the computational resources required to solve these problems at the scale of an entire economy were beyond what was available at the time. The Soviet leadership did not adopt Kantorovich’s methods<sup>2</sup>, and the planned economy continued to struggle with inefficiency and shortages (and ultimately was outcompeted by Western liberal democracies and capitalism).</p>
<p>Nearly 100 years have passed since Kantorovich’s work, and computational resources have increased by many orders of magnitude. The question is whether modern AI could change the relative tradeoffs between centralized and decentralized information processing.</p>
<p>A sufficiently advanced AI system could process real-time sensor data from every factory, farm, and storefront. It could model consumer preferences from behavioral data at a granularity that prices only approximate<sup>3</sup>. It could run counterfactual simulations of supply chain disruptions, weather events, and demand shocks. Would a sufficiently powerful AI planner even need markets? In theory, one could update its model continuously, faster than any price signal propagates through a market.</p>
<p>If we view the Hayekian knowledge problem not as an argument for markets, <em>per se</em> but instead as a hypothesis for authoritarian regimes have historically underperformed democracies, then just the shift in the ratio of information-processing power between central planners and decentralized markets could narrow the gap in economic performance and make dictatorships more viable.</p>
</section>
<section id="selectorate-theory-and-the-resource-curse" class="level3">
<h3 class="anchored" data-anchor-id="selectorate-theory-and-the-resource-curse">Selectorate Theory and the Resource Curse</h3>
<p>In a <a href="../../../governance/posts/game_theory_dictatorships_selectorate/index.html">previous post</a>, we explored the political economy of authoritarian regimes through the lens of selectorate theory (developed by Bruce Bueno de Mesquita, Alastair Smith, Randolph Siverson, and James Morrow).</p>
<p>To review, every leader survives by satisfying a “winning coalition.” In democracies, the coalition is large (the electorate), so leaders must provide public goods. In autocracies, the coalition is small (a few elites, generals, party insiders), so leaders can maintain power through targeted patronage.</p>
<p>The key variable is the size of the winning coalition <img src="https://latex.codecogs.com/png.latex?W"> relative to the selectorate <img src="https://latex.codecogs.com/png.latex?S">. When <img src="https://latex.codecogs.com/png.latex?W/S"> is large, the leader is pushed toward public goods provision. When <img src="https://latex.codecogs.com/png.latex?W/S"> is small, the leader can buy loyalty cheaply. The model predicts that small coalitions produce bad governance because the incentive structure rewards it.</p>
<p>Consider the determinants of coalition size. When wealth requires broad human participation, agriculture, manufacturing, services, the leader needs the population to be productive, which means providing education, infrastructure, healthcare. The winning coalition is effectively large because many people’s cooperation is needed<sup>4</sup>.</p>
<p>On the other hand, when wealth is derived from a concentrated source that does not require broad participation, the coalition shrinks. This is the “resource curse” or “oil curse.” Saudi Arabia does not need its citizens’ labor to generate wealth, it just needs a small number of laborers to operate and control the oil infrastructure. The political system reflects the small selectorate, and leads to concentrated power, limited public goods, and authoritarian governance<sup>5</sup>.</p>
<p>Assuming AI can manage itself, and if AI removes or reduces the value of white-collar laborers, then human citizens are irrelevant to the production function<sup>6</sup>. The political logic that links national prosperity to broad human welfare no longer functions, and the leaders of America would not need the population to generate wealth. All of the economics would flow to the small set of elites and laborers necessary to operate or control the AI.</p>
</section>
<section id="surveillance-at-scale" class="level3">
<h3 class="anchored" data-anchor-id="surveillance-at-scale">Surveillance at Scale</h3>
<p>A full-scale surveillance state is a defining feature of totalitarian regimes. The ability to monitor and control the population is essential for suppressing dissent, enforcing conformity, and maintaining power. However, the cost of surveillance has historically limited the ability of governments to achieve comprehensive Orwellian monitoring.</p>
<p>The East Germany <em>Stasi</em>, employed approximately 91,000 full-time staff and maintained a network of roughly 189,000 informal collaborators to surveil a population of 16 million, approximately one agent for every 63 citizens<sup>7</sup>. This was an unprecedented, but not unlimited, level of surveillance. The Stasi still had to prioritize certain individuals and activities, leaving gaps in their surveillance net for spies and dissidents to exploit.</p>
<p>Software has near-zero marginal costs of replication, and advances in machine learning have made it possible to automate and scale many aspects of surveillance, such as facial recognition, natural language processing, and behavioral pattern analysis. A state combined with powerful AI could potentially monitor every citizen in real time, analyzing their communications, movements, and interactions to identify and suppress dissenter before they could become organized.</p>
<p>In some ways, we can already see the early stages of this in China’s social credit system and AI-augmented surveillance infrastructure, which combine data from various sources, including financial records, social media activity, and public behavior, to assign citizens a “credit score” that can affect their access to services, travel, and even employment. Algorithms analyze this data to identify patterns of behavior that are deemed undesirable by the state.</p>
</section>
<section id="robot-armies" class="level3">
<h3 class="anchored" data-anchor-id="robot-armies">Robot Armies</h3>
<p>If, as Max Weber claims, the state is the entity that controls a monopoly on the use of force, then the relationship between the ruler and the military is a critical factor in the stability of any regime. With a human military, the ruler must maintain the loyalty of the armed forces, which can just as easily overthrow them as defend them. This doesn’t necessarily lead to democracy, but it does create a check on the ruler’s power, since the threat of defection can restrain the ruler’s worst impulses.</p>
<p>Autonomous weapons and robot armies can substantially change this calculus. A robot army could be programmed to follow the command hierarchy of whoever controls its systems, and could even be cryptographically locked to prevent unauthorized use. This would eliminate the risk of military defection, as the robots would have no agency or loyalty beyond their programming.</p>
<p>Historically, even the most ruthless dictator had to consider whether the order to fire on a crowd might be the order that triggers a military mutiny. Robot armies eliminate this consideration.</p>
</section>
<section id="propaganda" class="level3">
<h3 class="anchored" data-anchor-id="propaganda">Propaganda</h3>
<p>Historically, human writers, filmmakers, radio hosts, and designers were necessary to produce propaganda. This limited the volume and personalization of propaganda.</p>
<p>In contrast, large language models can generate images and text at near-zero marginal cost. Furthermore, AI can be used to personalize propaganda at scale. Some of the largest and most powerful companies in the world are already using AI to microtarget advertisements to individuals based on their online behavior, preferences, and psychological profiles, which results in modified behavior in the advertisees. The same technology could be used by a state to microtarget propaganda, delivering tailored messages to each citizen that are designed to maximize compliance and minimize dissent.</p>
</section>
<section id="coordination-asymmetry" class="level3">
<h3 class="anchored" data-anchor-id="coordination-asymmetry">Coordination Asymmetry</h3>
<p>There are open questions as to the ratio between fixed costs and operational costs of frontier AI systems. If the fixed costs (energy, compute, data) are high but the operational costs are low, then there is an asymmetry in coordination advantages. This is unlike the printing press, which could be operated by a small group. The state (or large, centralized corporations) can afford to pay the fixed costs of training and deploying frontier AI systems, while dissidents cannot. This creates a coordination advantage for the state that does not extend to the opposition.</p>
<p>On the other hand, inference is cheap and getting cheaper. Open-weight models are becoming increasingly available. It’s possible this asymmetry may not hold in the long term. Regardless, if the state can maintain a significant lead in AI capabilities, it could use that lead to coordinate its activities more effectively than any opposition group, which would be a significant advantage in maintaining power and suppressing dissent.</p>
</section>
</section>
<section id="democracy" class="level2">
<h2 class="anchored" data-anchor-id="democracy">Democracy</h2>
<section id="policy-modeling-and-foresight" class="level3">
<h3 class="anchored" data-anchor-id="policy-modeling-and-foresight">Policy Modeling and Foresight</h3>
<p>Democracies hesitate in part because policy consequences are uncertain and politically contested. Different policies (and the tradeoffs between them) are complex and often poorly understood by voters and legislators alike. This can lead to paralysis, as decision-makers fear making the wrong choice and facing political backlash. Alternatively, voters can be misled by misinformation or adversarial branding, leading them to support for policies that are not in their best interest. Similarly, candidates may have incentives to obfuscate the consequences of their policies, or to make promises that are not credible. Voters may not know which candidate to support, and fall back on heuristics like charisma, identity, or tribal loyalty.</p>
<p>AI-assisted modeling could generate more transparent projections of economic, environmental, and logistical consequences. Counterfactual simulations could be run before legislation is passed. This makes the consequences of policies more salient and less subject to manipulation. Voters could make more informed decisions, and legislators could be held accountable for the outcomes of their policies. The same “legibility” technology that enables totalitarian control could also enable more informed democratic decision-making.</p>
<p>Furthermore, competitive institutions in democracies (opposition parties, free press, independent courts, academic freedom) can function as error-correction mechanisms. These could be internal (related to functioning of a given government) or with respect to the actual value function governments are forced to satisfy (survival). Authoritarian regimes overusing AI may make the planner more powerful but may also ultimately be satisfying less competitive value functions. The fundamental goals and values of a dictator are higher variance than a democratic institution, since a democracy is forced to aggregate many disparate preferences<sup>8</sup>. Where democracies may chase broad welfare, dictators may pursue idiosyncratic goals that reduce the viability of the regime compared to alternatives. The dictator’s personal power may be checked by interstate competitive pressure<sup>9</sup>.</p>
</section>
<section id="consensus-formation" class="level3">
<h3 class="anchored" data-anchor-id="consensus-formation">Consensus Formation</h3>
<p>One common criticism of democratic governance is that it is slow and inefficient. Projects are commonly bottlenecked by overly cautious or adversarial parties all-too-eager to employ their veto power. Regulations designed to protect the environment, workers, or consumers delay infrastructure projects. Housing construction is blocked by battles over zoning. Entrepreneurs are stymied by lawsuits and regulatory uncertainty. The legislative process is slow and contentious.</p>
<p>This is a structural problem. As we discussed in the selectorate theory section, democratic governors need to aggregate preferences across a large coalition. Aggregating disparate preferences is inherently difficult, especially as the population grows larger and more varied. The same structure that prevents totalitarian governments from overruling the will of the people also introduces latency.</p>
<p>AI could be employed to make consensus formation more accurate and efficient. Large volumes of public input could be clustered, summarized, and translated into structured objections. AI systems could generate compromise variants that satisfy more stakeholders simultaneously. Rather than eliminating pluralism, AI could lower the transaction cost of agreement.</p>
</section>
<section id="civil-military-diffusion" class="level3">
<h3 class="anchored" data-anchor-id="civil-military-diffusion">Civil-Military Diffusion</h3>
<p>Autonomous weapons and robot armies removed the risk of military defection. A centralized authority that controls the machines controls the monopoly on force.</p>
<p>On the other hand, if AI-enabled weapons and autonomous systems become widely accessible rather than monopolized by the state, the distribution of coercive capacity may shift. For example, the United States has strong norms around citizen control of weaponry. The Second Amendment is a constitutional guarantee of the right to bear arms, and there is a strong culture of civilian gun ownership. If AI-enabled weapons (like cheap drones) become widely available to civilians or weaker states, it could create a powerful deterrent against authoritarianism and centralization<sup>10</sup>.</p>
<p>If autonomous systems become widely accessible rather than monopolized by the state, the coercive advantage of centralized authority may erode rather than consolidate. Civilian-owned drones, open-source defense systems, decentralized manufacturing (3d printers), and cryptographically secure coordination tools could lower the cost of resistance.</p>
</section>
<section id="transparency-and-auditability" class="level3">
<h3 class="anchored" data-anchor-id="transparency-and-auditability">Transparency and Auditability</h3>
<p>AI systems can generate detailed audit trails and anomaly detection.In authoritarian regimes, this enhances surveillance of citizens. In democracies, it can enhance surveillance of the state <em>by the citizens</em>.</p>
<p>AI-assisted investigative journalism, budget anomaly detection, procurement transparency, and real-time oversight tools could reduce corruption and elite capture. If the state is legible to citizens as much as citizens are legible to the state, then the asymmetry narrows.</p>
</section>
<section id="epistemic-defense" class="level3">
<h3 class="anchored" data-anchor-id="epistemic-defense">Epistemic Defense</h3>
<p>Democracies depend on a minimally shared informational baseline in order to deliberate. When information environments fragment, consensus becomes impossible.</p>
<p>AI can amplify propaganda and personalized persuasion. But it could be used to verify provenance of content or surface cross-ideological common ground. The same technology that enables narrative manipulation can be used to defend informational integrity.</p>
</section>
<section id="distributed-innovation" class="level3">
<h3 class="anchored" data-anchor-id="distributed-innovation">Distributed Innovation</h3>
<p>Democracies historically outperform in technological frontier competition because they tolerate experimentation, failure, and decentralized initiative. If AI lowers the entry cost of prototyping, simulation, and iteration, then democracies may retain a structural edge even if centralized planning improves.</p>
</section>
</section>
</section>
<section id="technolitarianism" class="level1">
<h1>Technolitarianism</h1>
<p>Of course, all of the above material presupposes that humans remain in charge of the AI systems. If we reach a point where AI systems can operate autonomously and make decisions without human oversight, then the entire dynamic changes. The question of whether AI makes totalitarianism more likely becomes less relevant if the AI itself is the one in control. In that case, the question becomes: what kind of governance structure will the AI itself adopt?</p>
<p>I would suspect that many of the same factors that affect the likelihood of totalitarianism for human rulers would also apply to an AI ruler. For example, if it is more efficient for an AI to process information centrally rather than through decentralized markets, then it may be more likely to adopt a technolitarian structure. If it can monitor and control the population more effectively through surveillance, then it may be more likely to suppress dissent. If it can maintain power through coercion without risk of defection, then it may be more likely to use force to maintain control. On the other hand, if it is more efficient for an AI to aggregate preferences through democratic processes, then we may see many separate AIs determining policies through voting, markets, or other similar mechanisms<sup>11</sup>.</p>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>While democracy has historically outperformed totalitarianism, the question is whether AI will change the underlying selection pressures that led to that outcome. Even a shift in the relative efficiency of centralized versus decentralized information processing could have significant consequences for the viability of different political systems and increase the risk of a new wave of totalitarianism. On the other hand, AI could also empower democratic governance by improving policy modeling, consensus formation, transparency, and distributed innovation<sup>12</sup>.</p>
<p>The same technology that enables totalitarian control could also enable more informed and effective democratic decision-making. The future is uncertain, but the stakes are high. We should be mindful of the potential risks and benefits of AI for governance, and work to ensure that it is used in ways that promote liberty, justice, and human flourishing.</p>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>I used AI to help draft this essay from my notes and research various historical examples. I made substantial edits to the structure, content, and wording. I did not use AI to generate any of the ideas or arguments in this essay. A few footnotes (Sen, Acemoglu, Jones-Olken) were added by Claude after publication.</p>
</section>
<section id="changelog" class="level1">
<h1>Changelog</h1>
<ul>
<li><strong>2026-02-20</strong>: Added footnote on LLMs as a qualitative shift in legibility: previous planning tools required structured numerical inputs, but LLMs can process unstructured qualitative data (the “metis” Scott identifies), rendering legible the illegible. Added AI disclosure date.</li>
<li><strong>2026-02-19</strong>: Added three footnotes: (1) Sen on democracy as famine prevention via feedback channels; (2) Acemoglu &amp; Robinson on inclusive vs extractive institutions and the resource curse; (3) portfolio theory argument for democracy with Jones &amp; Olken (2005) empirical evidence on leader-level variance.</li>
<li><strong>2026-02-21</strong>: Added a section about the possibility of an “inversion of control” where AI itself is the ruler.</li>
</ul>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Amartya Sen observes that no substantial famine has ever occurred in a functioning democracy with a free press. Famines are nearly always failures of information and political will, not food supply. A free press makes famine politically expensive; autocracies can suppress the information until millions have died. Mao’s Great Leap Forward (15-55 million dead) was exacerbated by local officials inflating production numbers to avoid punishment, a failure mode that democratic feedback channels structurally prevent. See Sen, A. (1999). <a href="https://en.wikipedia.org/wiki/Development_as_Freedom" class="external" target="_blank"><em>Development as Freedom</em></a>. Oxford University Press.↩︎</p></li>
<li id="fn2"><p>In the late 1930s, Soviet doctrine rejected marginalist price theory. Kantorovich narrowly avoided serious repercussions (in some apocryphal anecdotes, recounted in books like <em>Red Plenty</em>, Kantorovich naively sends a letter to a superior, or even to Stalin himself, only to have his life saved when the letter is intercepted by a mid-level bureaucrat). Kantorovich would ultimately win the 1975 Nobel Prize in Economics for his work.↩︎</p></li>
<li id="fn3"><p>The shift here is not merely quantitative (more compute) but qualitative. Previous computational approaches to planning, from Kantorovich’s linear programs to modern operations research, required structured, numerical inputs. The vast majority of economically relevant information, however, is qualitative and unstructured: a foreman’s intuition about machine wear, a shopkeeper’s sense of neighborhood demand, a farmer’s knowledge of soil drainage. This is precisely the “metis” that Scott argues is illegible to the state. Large language models change this equation. By processing natural language, they can ingest free-text reports, customer complaints, regulatory filings, internal memos, cultural commentary, and convert disparate qualitative data into structured, quantified representations. LLMs render legible the illegible. What was previously tacit, situated, and resistant to centralization becomes available for aggregation and optimization by a central planner.↩︎</p></li>
<li id="fn4"><p>One question I have: is America’s superior service economy a cause of its democratic institutions, or a consequence of them? The selectorate logic suggests that the need for broad labor participation in wealth generation creates an incentive for leaders to maintain a large coalition, which in turn incentivizes broad education, public goods provision and democratic institutions (which makes the population more productive and hence richer overall). But it could also be that since the institutions are democratic, this creates an incentive to educate and train the populace that allows more high-end labor. Or it could be a mutually reinforcing feedback loop. Relatedly, America tends to have a strong consumer culture, especially compared to anemic demand in more authoritarian economies, like China. Is this because the wealth in America is more broadly distributed, which creates more demand, which creates more growth, which creates more wealth, which creates more demand? Or does the distribution of wealth and distribution of demand somehow entrench the democratic institutions?↩︎</p></li>
<li id="fn5"><p>Acemoglu and Robinson formalize this as the distinction between “inclusive” and “extractive” institutions. Inclusive institutions (secure property rights, open markets, broad political participation) produce sustained growth because people invest when they expect to keep the returns. Extractive institutions (concentrated power, insecure property, barriers to entry) suppress productivity because the ruler can confiscate gains. The resource curse is a special case: when wealth doesn’t require broad participation, the regime has no incentive to build inclusive institutions. See Acemoglu, D. &amp; Robinson, J. A. (2012). <a href="https://en.wikipedia.org/wiki/Why_Nations_Fail" class="external" target="_blank"><em>Why Nations Fail</em></a>. Crown Business.↩︎</p></li>
<li id="fn6"><p>It’s also possible that AI will <em>increase</em> the returns to high-level white collar work as it acts as a “force multiplier” for human labor. For example, a human programmer could use AI to write code faster and more efficiently, or a human researcher could use AI to analyze data and generate insights more quickly. It could also make education cheaper, which would increase the supply of skilled labor. If AI increases the returns to high-level white collar work, then it could actually increase the need for broad human participation in wealth generation, which would incentivize leaders to maintain a large coalition and democratic institutions.↩︎</p></li>
<li id="fn7"><p>Stasi Records Archive (BStU), <a href="https://www.bundesarchiv.de/en/stasi-records-archive/education/what-was-the-state-security/introduction/">“Introduction”</a>; Helmut Müller-Enbergs (2010). The 189,000 figure for unofficial collaborators is accepted by the BStU, though some scholars have proposed lower estimates↩︎</p></li>
<li id="fn8"><p>This is essentially a portfolio theory argument for democracy. Autocracy is a high-variance bet: you might get Lee Kuan Yew, but you might also get Robert Mugabe. Democracy is a lower-variance bet. For a risk-averse society, the lower-variance system is preferable even if the means are similar, especially given volatility drag from compounding (a +10% year followed by a -10% year leaves you at 99%, not 100%). Empirically, Jones &amp; Olken (2005), <a href="https://academic.oup.com/qje/article-abstract/120/3/835/1841530" class="external" target="_blank">“Do Leaders Matter?”</a> (<em>QJE</em> 120(3)), confirm this using natural experiments: random leader transitions (assassinations, accidents) produce significantly larger GDP growth swings in autocracies than in democracies.↩︎</p></li>
<li id="fn9"><p>This is not to say that democracies are necessarily more likely to survive than dictatorships. This could just lead to races to establish the most efficient dictatorship.↩︎</p></li>
<li id="fn10"><p>To be clear, I’m not advocating for citizen control of robot armies. There are many problems with widespread civilian access to powerful weapons, such as increased violence, crime, and instability. It could also create a risk of accidental or intentional misuse.↩︎</p></li>
<li id="fn11"><p>In a world where multiple AIs exist, we may see a kind of “AI feudalism” emerge, where different AIs control different domains (e.g., one AI controls the economy, another controls the military, another controls information, and different AIs have dominion over different regions of the world). These AIs may compete or cooperate with each other, and the governance structure could be complex and multi-layered.↩︎</p></li>
<li id="fn12"><p>Based on this exercise, the arguments in favor of increased dictatorial control seem more numerous and compelling to me than the arguments in favor of increased democratic empowerment.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Technology and Society</category>
  <category>Essays</category>
  <category>Speculation</category>
  <category>Governance</category>
  <guid>https://demonstrandom.com/essays/posts/ai_totalitarianism/</guid>
  <pubDate>Thu, 19 Feb 2026 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/essays/posts/ai_totalitarianism/stormtroopers_advancing_under_gas_dix.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Thoughts on Selectorate Theory</title>
  <link>https://demonstrandom.com/governance/posts/game_theory_dictatorships_selectorate/</link>
  <description><![CDATA[ 





<p><a href="https://en.wikipedia.org/wiki/Eclipse_of_the_Sun_(Grosz)"><img src="https://demonstrandom.com/governance/posts/game_theory_dictatorships_selectorate/eclipse_of_the_sun_grosz.jpg" class="img-fluid" style="width:55.0%" alt="George Grosz. *Eclipse of the Sun* (1926)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>Bruce Bueno de Mesquita and Alastair Smith’s <em>The Dictator’s Handbook</em> (2011) is one of the more successful pop-science books in political economics. Its central thesis is that to remain in power all leaders must maintain their coalition: this requirement creates certain incentives governing leaders’ behavior. Furthermore, the ratio between the number of members a leader needs in their coalition and the total size of the set of possible supporters (the selectorate) governs the incentive structure around the leader. The authors go on to argue that different governments behave differently not because of ideology or culture, but because of the game-theoretic structures arising from their selectorate and winning coalition.</p>
<p>The book reflects a body of formal, game-theoretic work called selectorate theory, developed by de Mesquita, Smith, Siverson, and Morrow in <a href="https://mitpress.mit.edu/9780262524407/the-logic-of-political-survival/" class="external" target="_blank"><em>The Logic of Political Survival</em></a>(2003), which presents the same ideas mathematically. In this post, I follow along with the formal model from <em>The Logic of Political Survival</em> with some departures, and then look at some repercussions of the results with respect to hierarchies.</p>
<p>This post will continue a line of reasoning I started (but haven’t yet continue) in my post on <a href="../../../game_theory/posts/differential_stag_hunt/index.html">differential stag hunt</a>, where I looked at how the structure of incentives shapes behavior in multi-agent systems. The selectorate model is a particularly clean example of this principle, which perhaps can shed some light on how emergent behavior arises from underlying incentive structures in other domains as well.</p>
<p><strong>Epistemic status</strong>: This post got denser then expected, and underwent multiple revisions (including one large one where I refactored substantial portions of the post), with the “help” of Claude. I apologize in advance for any errors that may have survived the process.</p>
<p>The original model can be thought of as the 2nd-order model in a family of models parametrized by the number of channels the budget can be distributed to. Based on this, we’ll build up the model in stages. First, Olson’s collective action problem, a symmetric game with no leader and no allocation, establishes the baseline. Then, we introduce a leader with a single allocation channel (targeted transfers) and derive the minimum cost for the leader to buy loyalty. Then, in the actual selectorate model, we add a second channel (broadly distributed “public goods”), let the challenger optimize across all channels, and show that the full selectorate geometry compresses to three coefficients. Finally, we look at higher-order generalizations with more channels, as well as hierarchical composition.</p>
</section>
<section id="mancur-olson-and-collective-action" class="level1">
<h1>Mancur Olson and Collective Action</h1>
<p>One intellectual ancestor of the selectorate model is Mancur Olson’s <a href="https://en.wikipedia.org/wiki/The_Logic_of_Collective_Action" class="external" target="_blank"><em>The Logic of Collective Action</em></a> (1965). Olson’s model is a symmetric <img src="https://latex.codecogs.com/png.latex?n">-player public goods game: each agent independently chooses to contribute or free-ride, and the public good is produced as a function of total contributions. The individual incentive to contribute scales as <img src="https://latex.codecogs.com/png.latex?1/n">.</p>
<p>Concretely, suppose <img src="https://latex.codecogs.com/png.latex?n"> agents each choose an effort <img src="https://latex.codecogs.com/png.latex?e_i%20%5Cin%20%5B0,1%5D"> at cost <img src="https://latex.codecogs.com/png.latex?c(e_i)">. A public good of value <img src="https://latex.codecogs.com/png.latex?v(%5Csum%20e_i)"> is produced and shared equally. Agent <img src="https://latex.codecogs.com/png.latex?i">’s payoff is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_i%20=%20%5Cfrac%7Bv%5C!%5Cleft(%5Csum_j%20e_j%5Cright)%7D%7Bn%7D%20-%20c(e_i)%0A"></p>
<p>The marginal benefit of contributing is <img src="https://latex.codecogs.com/png.latex?v'/n">, which shrinks as <img src="https://latex.codecogs.com/png.latex?n"> grows. When <img src="https://latex.codecogs.com/png.latex?v'/n%20%3C%20c'">, the dominant strategy is <img src="https://latex.codecogs.com/png.latex?e_i%20=%200">.</p>
<p>When the production function has a threshold (the good is provided iff <img src="https://latex.codecogs.com/png.latex?k%20%5Cgeq%20k%5E*"> contributors), this is equivalent to an <img src="https://latex.codecogs.com/png.latex?n">-player <a href="../../../game_theory/posts/differential_stag_hunt/index.html">stag hunt</a>. There are two equilibria (enough contribute, or nobody does) with coordination increasing in difficulty as <img src="https://latex.codecogs.com/png.latex?n"> grows. When production is linear, it collapses to an <img src="https://latex.codecogs.com/png.latex?n">-player Prisoner’s Dilemma, dominated by free-riding. Olson’s key result is that the first case degrades toward the second as groups grow.</p>
<p>The model has no control variables, and no agent chooses an allocation; therefore there is no budget, no leader, and no asymmetry. All the agents face the same payoff function. The only “decision” is a scalar effort level, and the equilibrium is pinned by the ratio <img src="https://latex.codecogs.com/png.latex?v'/nc'">.</p>
<p>The takeaway is that collective action fails at scale because no one is in charge. The benefit of contributing is diluted across all <img src="https://latex.codecogs.com/png.latex?n"> agents, but the cost is borne individually. To produce scalable collective action, someone must control and allocate the budget.</p>
</section>
<section id="one-channel-allocation" class="level1">
<h1>One-Channel Allocation</h1>
<p>Let’s now extend the model to include a leader who controls a budget and allocates it to coalition members. The leader’s survival depends on maintaining a winning coalition, which requires buying loyalty from coalition members. The question is: how much does the leader need to spend to maintain their coalition?</p>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>Suppose we have a selectorate of size <img src="https://latex.codecogs.com/png.latex?S">. For the leader to remain in power, they require a winning coalition of size <img src="https://latex.codecogs.com/png.latex?W_%7Breq%7D%20%5Cleq%20S">. The leader is equipped with a budget <img src="https://latex.codecogs.com/png.latex?B">. The leader chooses a total targeted transfer <img src="https://latex.codecogs.com/png.latex?p"> to distribute among coalition members. Assuming equal distribution, each coalition member receives <img src="https://latex.codecogs.com/png.latex?p/W">. The remainder <img src="https://latex.codecogs.com/png.latex?B%20-%20p"> is discretionary surplus: rents, personal consumption, or waste.</p>
<p>In each round of the game, the leader must nominate a coalition of size <img src="https://latex.codecogs.com/png.latex?W_n"> and choose the transfer <img src="https://latex.codecogs.com/png.latex?p">. The coalition members can then choose to either stay loyal to the leader or defect. If the leader attracts <img src="https://latex.codecogs.com/png.latex?W_%7Bloyal%7D%20%5Cgeq%20W_%7Breq%7D"> supporters, they remain in power. If the leader fails to attract a coalition of at least size <img src="https://latex.codecogs.com/png.latex?W_%7Breq%7D">, they are replaced by a challenger<sup>1</sup>. For the static versions below, we set <img src="https://latex.codecogs.com/png.latex?W%20:=%20W_%7Breq%7D%20=%20W_n"> and suppress the distinction.</p>
</section>
<section id="payoffs" class="level2">
<h2 class="anchored" data-anchor-id="payoffs">Payoffs</h2>
<section id="remains-loyal" class="level3">
<h3 class="anchored" data-anchor-id="remains-loyal">Remains Loyal</h3>
<p>Each coalition member receives an equal share of the targeted transfer:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_L%20=%20%5Cfrac%7Bp%7D%7BW%7D%0A"></p>
<section id="defects" class="level4">
<h4 class="anchored" data-anchor-id="defects">Defects</h4>
<p>If a coalition member defects, their payoff depends on whether the challenger will include this particular member in the <em>new</em> winning coalition. The challenger needs to assemble a coalition of size <img src="https://latex.codecogs.com/png.latex?W_r"> from the full selectorate of size <img src="https://latex.codecogs.com/png.latex?S">. Assuming equal probability of inclusion, a given member’s probability of being selected is at most <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7BW_r%7D%7BS%7D"><sup>2</sup>.</p>
<p>If we assume the challenger is adversarial, the challenger allocates the entire budget as targeted transfers: <img src="https://latex.codecogs.com/png.latex?p'%20=%20B">. The defecting member’s expected payoff<sup>3</sup> is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_D%20=%20%5Cfrac%7BW_r%7D%7BS%7D%20%5Ccdot%20%5Cfrac%7BB%7D%7BW_r%7D%20=%20%5Cfrac%7BB%7D%7BS%7D%0A"></p>
<p>The size of the challenger’s coalition turns out to be irrelevant. The first term is the probability of inclusion (<img src="https://latex.codecogs.com/png.latex?W_r%20/%20S">) times the targeted transfer per coalition member (<img src="https://latex.codecogs.com/png.latex?B%20/%20W_r">), giving <img src="https://latex.codecogs.com/png.latex?B%20/%20S"> regardless of the challenger’s coalition size. The <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D"> we derive below is therefore a lower bound on required spending: the minimum the leader needs under the most favorable assumptions about defection risk.</p>
</section>
</section>
<section id="leader" class="level3">
<h3 class="anchored" data-anchor-id="leader">Leader</h3>
<p>A coalition member stays loyal if <img src="https://latex.codecogs.com/png.latex?u_L%20%5Cgeq%20u_D">. The loyalty constraint <img src="https://latex.codecogs.com/png.latex?u_L%20%5Cgeq%20u_D"> becomes:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bp%7D%7BW%7D%20%5Cgeq%20%5Cfrac%7BB%7D%7BS%7D%0A"></p>
<p>Therefore:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap%5E%7B%5Cmin%7D%20=%20B%20%5Ccdot%20%5Cfrac%7BW%7D%7BS%7D%0A"></p>
<p>The minimum transfer is proportional to the coalition ratio <img src="https://latex.codecogs.com/png.latex?r%20=%20W/S">. The leader’s discretionary surplus is <img src="https://latex.codecogs.com/png.latex?B%20-%20p%5E%7B%5Cmin%7D%20=%20B(1%20-%20r)">. In the one-shot version, stability is a tie at the minimum; persistence requires dynamics or additional frictions.</p>
</section>
</section>
<section id="consequences" class="level2">
<h2 class="anchored" data-anchor-id="consequences">Consequences</h2>
<section id="private-goods-are-cheap-in-small-coalitions" class="level3">
<h3 class="anchored" data-anchor-id="private-goods-are-cheap-in-small-coalitions">Private Goods Are Cheap In Small Coalitions</h3>
<p>When <img src="https://latex.codecogs.com/png.latex?W"> is small relative to <img src="https://latex.codecogs.com/png.latex?S">, the leader can buy loyalty with a small fraction of spending.</p>
<p>A small coalition means each member gets a large slice of the pie, and defecting to the challenger is unattractive because the probability of being included in the new coalition (<img src="https://latex.codecogs.com/png.latex?W/S">) is low. The leader can keep most of the budget for discretionary purposes or personal enrichment.</p>
<p>In equilibrium, with a small coalition the leader can spend just a small share of the budget on the coalition, which is enough to keep the coalition loyal, and the rest is available for discretionary use.</p>
</section>
<section id="in-large-coalitions-private-goods-are-expensive" class="level3">
<h3 class="anchored" data-anchor-id="in-large-coalitions-private-goods-are-expensive">In Large Coalitions Private Goods Are Expensive</h3>
<p>As <img src="https://latex.codecogs.com/png.latex?W"> grows toward <img src="https://latex.codecogs.com/png.latex?S">, <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D/B%20=%20W/S"> increases toward <img src="https://latex.codecogs.com/png.latex?1">. At <img src="https://latex.codecogs.com/png.latex?r%20=%201">, the leader must spend the entire budget on targeted transfers, leaving nothing for rents.</p>
<p>When the coalition is large, each member’s slice of the budget is thin, but each member’s probability of being included in a challenger’s coalition (<img src="https://latex.codecogs.com/png.latex?W/S">) is high. Private loyalty-buying is expensive and the leader’s rents are squeezed.</p>
</section>
<section id="numerical-examples" class="level3">
<h3 class="anchored" data-anchor-id="numerical-examples">Numerical Examples</h3>
<table class="caption-top table">
<colgroup>
<col style="width: 10%">
<col style="width: 16%">
<col style="width: 35%">
<col style="width: 14%">
<col style="width: 22%">
</colgroup>
<thead>
<tr class="header">
<th><img src="https://latex.codecogs.com/png.latex?W/S"></th>
<th>Regime</th>
<th><img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D/B"></th>
<th>Rents</th>
<th>Character</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>0.1</td>
<td>Autocracy</td>
<td>10%</td>
<td>90%</td>
<td>Loyalty is cheap; most of the budget is discretionary</td>
</tr>
<tr class="even">
<td>0.3</td>
<td>Junta</td>
<td>30%</td>
<td>70%</td>
<td>Small clique, affordable</td>
</tr>
<tr class="odd">
<td>0.6</td>
<td>Broad coalition</td>
<td>60%</td>
<td>40%</td>
<td>Getting expensive. Small perturbations in <img src="https://latex.codecogs.com/png.latex?W"> cause large swings</td>
</tr>
<tr class="even">
<td>0.8</td>
<td>Near-democracy</td>
<td>80%</td>
<td>20%</td>
<td>Private targeting consumes most of the budget</td>
</tr>
<tr class="odd">
<td>1.0</td>
<td>Full inclusion</td>
<td>100%</td>
<td>0%</td>
<td>The entire budget goes to targeted transfers</td>
</tr>
</tbody>
</table>
</section>
</section>
</section>
<section id="two-channel-allocation-the-selectorate-model" class="level1">
<h1>Two-Channel Allocation: The Selectorate Model</h1>
<p>The one-decision model isolates the core selectorate geometry: the leader’s survival cost is <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D%20=%20Br">. But real leaders have more than one channel or instrument by which to distribute the budget. In particular, they can provide public goods, which benefit everyone, not just coalition members. The selectorate model introduces a second control variable and an adversarial challenger who optimizes across channels.</p>
<section id="setup-1" class="level2">
<h2 class="anchored" data-anchor-id="setup-1">Setup</h2>
<p>Let the total population be <img src="https://latex.codecogs.com/png.latex?N">, with <img src="https://latex.codecogs.com/png.latex?S%20%5Cleq%20N"> the selectorate and <img src="https://latex.codecogs.com/png.latex?W%20%5Cleq%20S"> the winning coalition. The leader now allocates the budget across three categories:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AB%20=%20p%20+%20g%20+%20R%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?p"> is targeted private goods (split among <img src="https://latex.codecogs.com/png.latex?W">), <img src="https://latex.codecogs.com/png.latex?g"> is public goods spending (benefiting all <img src="https://latex.codecogs.com/png.latex?N">), and <img src="https://latex.codecogs.com/png.latex?R"> is rents (leader’s discretionary surplus that they retain).</p>
</section>
<section id="payoffs-1" class="level2">
<h2 class="anchored" data-anchor-id="payoffs-1">Payoffs</h2>
<section id="coalition-member" class="level3">
<h3 class="anchored" data-anchor-id="coalition-member">Coalition Member</h3>
<section id="remains-loyal-1" class="level4">
<h4 class="anchored" data-anchor-id="remains-loyal-1">Remains Loyal</h4>
<p>A coalition member’s payoff from loyalty is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_L%20=%20%5Cfrac%7Bp%7D%7BW%7D%20+%20v(g,%20N)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?v(g,%20N)"> is a per-capita public good function, increasing in spending <img src="https://latex.codecogs.com/png.latex?g"> and decreasing in population <img src="https://latex.codecogs.com/png.latex?N">. In general, the shape of <img src="https://latex.codecogs.com/png.latex?v"> matters a great deal: concave <img src="https://latex.codecogs.com/png.latex?v"> produces the classic BdM result where public goods provision increases with coalition size, while threshold effects and increasing returns from network goods can qualitatively change the predictions. For our purposes, however, the linear case <img src="https://latex.codecogs.com/png.latex?v(g,%20N)%20=%20%5Cbeta%20g%20/%20N"> suffices, where <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Cin%20(0,1%5D"> is an efficiency parameter capturing how effectively public spending translates into individual welfare<sup>4</sup>. This gives:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_L%20=%20%5Cfrac%7Bp%7D%7BW%7D%20+%20%5Cfrac%7B%5Cbeta%20g%7D%7BN%7D%0A"></p>
</section>
<section id="defects-1" class="level4">
<h4 class="anchored" data-anchor-id="defects-1">Defects</h4>
<p>If a coalition member defects, their payoff depends on the challenger’s allocation across both channels. The targeted channel works as before. The inclusion-probability argument gives <img src="https://latex.codecogs.com/png.latex?1/S"> per dollar to a random defector, independent of the challenger’s coalition size. But the challenger can now also use the universal channel, which yields <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N"> per dollar to everyone regardless of coalition membership.</p>
</section>
</section>
<section id="leader-1" class="level3">
<h3 class="anchored" data-anchor-id="leader-1">Leader</h3>
<p>The leader maximizes rents <img src="https://latex.codecogs.com/png.latex?R%20=%20B%20-%20p%20-%20g"> subject to the loyalty constraint <img src="https://latex.codecogs.com/png.latex?u_L%20%5Cgeq%20u_D">. Since both payoffs are linear in <img src="https://latex.codecogs.com/png.latex?(p,%20g)">, and spending is constrained by <img src="https://latex.codecogs.com/png.latex?p%20+%20g%20%5Cleq%20B"> with <img src="https://latex.codecogs.com/png.latex?p,%20g%20%5Cgeq%200">, this is a linear program over the spending simplex. The optimum is always at a corner, so the leader spends entirely on one channel or the other. The solution depends on which channel the adversarial challenger uses, which we consider next.</p>
</section>
<section id="adversarial-challenger" class="level3">
<h3 class="anchored" data-anchor-id="adversarial-challenger">Adversarial Challenger</h3>
<p>The adversarial challenger has the same budget <img src="https://latex.codecogs.com/png.latex?B"> and allocates it entirely to whichever channel yields the highest defector payoff per dollar. They can either target a new coalition of size <img src="https://latex.codecogs.com/png.latex?W_r"> with targeted transfers, or provide universal public goods. The defector’s expected payoff from the targeted channel is <img src="https://latex.codecogs.com/png.latex?B/S"> (same as before, since the challenger can pick any <img src="https://latex.codecogs.com/png.latex?W_r"> and the expected payoff per dollar is <img src="https://latex.codecogs.com/png.latex?1/S">), while the payoff from the universal channel is <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20B%20/%20N">. The challenger picks the channel that yields the higher payoff to the defector:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_D%20=%20B%20%5Ccdot%20%5Cmax%5C!%5Cleft(%5Cfrac%7B1%7D%7BS%7D,%5C%20%5Cfrac%7B%5Cbeta%7D%7BN%7D%5Cright)%0A"></p>
</section>
</section>
<section id="channel-comparison" class="level2">
<h2 class="anchored" data-anchor-id="channel-comparison">Channel Comparison</h2>
<p>The loyalty constraint <img src="https://latex.codecogs.com/png.latex?u_L%20%5Cgeq%20u_D"> reduces to comparing per-dollar coefficients on each side:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Channel</th>
<th>Loyalty coefficient</th>
<th>Defection coefficient</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Targeted</td>
<td><img src="https://latex.codecogs.com/png.latex?1/W"></td>
<td><img src="https://latex.codecogs.com/png.latex?1/S"></td>
</tr>
<tr class="even">
<td>Universal</td>
<td><img src="https://latex.codecogs.com/png.latex?%5Cbeta/N"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Cbeta/N"></td>
</tr>
</tbody>
</table>
<p>The incumbent has a positional advantage in the targeted channel (<img src="https://latex.codecogs.com/png.latex?1/W%20%3E%201/S"> since <img src="https://latex.codecogs.com/png.latex?W%20%3C%20S">) and no advantage in the universal channel (both sides get <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N">). The incumbent’s optimal instrument is <img src="https://latex.codecogs.com/png.latex?%5Cmax(1/W,%5C%20%5Cbeta/N)">: targeted dominates when <img src="https://latex.codecogs.com/png.latex?1/W%20%3E%20%5Cbeta/N"> (i.e., <img src="https://latex.codecogs.com/png.latex?W%20%3C%20N/%5Cbeta">), universal dominates when <img src="https://latex.codecogs.com/png.latex?W%20%3E%20N/%5Cbeta">.</p>
<p>The interesting question is which channel the challenger uses for the defection benchmark. When <img src="https://latex.codecogs.com/png.latex?1/S%20%3E%20%5Cbeta/N">, the challenger uses targeted transfers, and the defection benchmark is <img src="https://latex.codecogs.com/png.latex?B/S">. When <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N%20%5Cgeq%201/S">, the challenger uses public goods, and the defection benchmark becomes <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20B/N">.</p>
<p>The second case is sometimes called the <em>democratic region</em>, though the name is misleading. The condition <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20S%20%5Cgeq%20N"> is about selectorate breadth (<img src="https://latex.codecogs.com/png.latex?S"> close to <img src="https://latex.codecogs.com/png.latex?N">) and public goods efficiency (<img src="https://latex.codecogs.com/png.latex?%5Cbeta"> not too small), not about coalition size <img src="https://latex.codecogs.com/png.latex?W">. A polity can have a large winning coalition and still fall outside this region if the selectorate is narrow or public goods are inefficient. With <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Cleq%201"> and <img src="https://latex.codecogs.com/png.latex?S%20%5Cleq%20N">, the condition requires nearly universal selectorate and effective state capacity. When it holds, the challenger’s best response flips from the targeted to the universal channel, changing the binding constraint on the incumbent.</p>
</section>
<section id="equilibrium-in-the-democratic-region" class="level2">
<h2 class="anchored" data-anchor-id="equilibrium-in-the-democratic-region">Equilibrium in the Democratic Region</h2>
<p>When <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N%20%5Cgeq%201/S">, the loyalty constraint becomes:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bp%7D%7BW%7D%20+%20%5Cfrac%7B%5Cbeta%20g%7D%7BN%7D%20%5Cgeq%20%5Cfrac%7B%5Cbeta%20B%7D%7BN%7D%0A"></p>
<p>The leader maximizes rents <img src="https://latex.codecogs.com/png.latex?R%20=%20B%20-%20p%20-%20g"> subject to this constraint. Since both payoffs are linear, the solution is a corner: the incumbent uses whichever instrument has the larger loyalty coefficient per dollar.</p>
<p>In the first case, <img src="https://latex.codecogs.com/png.latex?1/W%20%3E%20%5Cbeta/N">, and targeted spending dominates. The leader sets <img src="https://latex.codecogs.com/png.latex?g%20=%200">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bp%7D%7BW%7D%20%5Cgeq%20%5Cfrac%7B%5Cbeta%20B%7D%7BN%7D%20%5Cimplies%20p%5E%7B%5Cmin%7D%20=%20%5Cfrac%7B%5Cbeta%20W%20B%7D%7BN%7D%0A"></p>
<p>This is more expensive than the non-democratic benchmark <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D%20=%20WB/S">, since <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20S%20%5Cgeq%20N"> in the democratic region implies <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20WB/N%20%5Cgeq%20WB/S">. The challenger’s access to an efficient public channel raises the defection threshold; the incumbent still responds with targeted transfers but must spend more to compensate.</p>
<p>In the second case, <img src="https://latex.codecogs.com/png.latex?1/W%20%5Cleq%20%5Cbeta/N">. Here, the coalition is large enough (<img src="https://latex.codecogs.com/png.latex?W%20%5Cgeq%20N/%5Cbeta">) that universal spending dominates even for the incumbent. The leader sets <img src="https://latex.codecogs.com/png.latex?p%20=%200">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cbeta%20g%7D%7BN%7D%20%5Cgeq%20%5Cfrac%7B%5Cbeta%20B%7D%7BN%7D%20%5Cimplies%20g%5E%7B%5Cmin%7D%20=%20B%0A"></p>
<p>The entire budget goes to public goods, leaving zero rents. Both challenger and incumbent operate through the same channel.</p>
<p>In the linear model, the challenger and incumbent are both optimizing linear programs over the spending simplex, so both always spend entirely on one channel. The challenger picks whichever channel maximizes defector payoff per dollar, and the incumbent picks whichever channel maximizes loyalty surplus per dollar.</p>
<p>The leader never provides a mix of public and private goods. At <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N%20=%201/S">, the challenger’s best response flips from the targeted to the universal channel, changing the binding constraint on the incumbent. The incumbent’s policy remains a corner solution throughout<sup>5</sup>.</p>
</section>
</section>
<section id="generalization-to-n-channels" class="level1">
<h1>Generalization to <img src="https://latex.codecogs.com/png.latex?n"> Channels</h1>
<p>The two-control model has two channels: targeted (to <img src="https://latex.codecogs.com/png.latex?W">) and universal (to <img src="https://latex.codecogs.com/png.latex?N">). What happens if we extend this? In fact, we could define a channel for any subset <img src="https://latex.codecogs.com/png.latex?T%20%5Csubseteq%20N">, where spending on <img src="https://latex.codecogs.com/png.latex?T"> distributes evenly across <img src="https://latex.codecogs.com/png.latex?%7CT%7C"> members<sup>6</sup>.</p>
<p>What is the marginal value of a dollar spent on subset <img src="https://latex.codecogs.com/png.latex?T"> for the defector? A defecting coalition member is included in the challenger’s new coalition with probability <img src="https://latex.codecogs.com/png.latex?W_r/S">, and the per-member payout from spending on <img src="https://latex.codecogs.com/png.latex?T"> is <img src="https://latex.codecogs.com/png.latex?1/%7CT%7C">. But the defector only benefits if they are in <img src="https://latex.codecogs.com/png.latex?T">. Under the adversarial challenger, the defector’s expected payoff per dollar on channel <img src="https://latex.codecogs.com/png.latex?T"> is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%7Ba%7D_T%20=%20%5Cfrac%7B%7CT%20%5Ccap%20S%7C%7D%7BS%7D%20%5Ccdot%20%5Cfrac%7B1%7D%7B%7CT%7C%7D%0A"></p>
<p>Targeting non-selectorate members wastes spending (since they cannot defect), so the only relevant channels have <img src="https://latex.codecogs.com/png.latex?T%20%5Csubseteq%20S">. For any such <img src="https://latex.codecogs.com/png.latex?T">, <img src="https://latex.codecogs.com/png.latex?%7CT%20%5Ccap%20S%7C%20=%20%7CT%7C">, and the subset size cancels:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%7Ba%7D_T%20=%20%5Cfrac%7B%7CT%7C%7D%7BS%7D%20%5Ccdot%20%5Cfrac%7B1%7D%7B%7CT%7C%7D%20=%20%5Cfrac%7B1%7D%7BS%7D%0A"></p>
<p>Whether the challenger targets 10 or 10,000 members of <img src="https://latex.codecogs.com/png.latex?S">, the expected defector payoff per dollar is the same. All subset-targeting channels are summarized by the same defector-side coefficient.</p>
<p>On the incumbent side, the loyalty constraint binds on the worst-off coalition member. If the incumbent targets subset <img src="https://latex.codecogs.com/png.latex?T">, members of <img src="https://latex.codecogs.com/png.latex?W%20%5Csetminus%20T"> receive nothing from this channel and will defect, so the incumbent must have <img src="https://latex.codecogs.com/png.latex?T%20%5Csupseteq%20W">. Given <img src="https://latex.codecogs.com/png.latex?T%20%5Csupseteq%20W">, each coalition member receives <img src="https://latex.codecogs.com/png.latex?1/%7CT%7C"> per dollar, which is strictly decreasing in <img src="https://latex.codecogs.com/png.latex?%7CT%7C">. The optimum is the minimum viable set <img src="https://latex.codecogs.com/png.latex?T%20=%20W">, giving <img src="https://latex.codecogs.com/png.latex?1/W"> per dollar. Any larger <img src="https://latex.codecogs.com/png.latex?T"> dilutes the transfer across non-coalition members.</p>
<p>What’s the adversarial equilibrium? For the challenger, the equilibrium is <img src="https://latex.codecogs.com/png.latex?%5Cmax_T%20%5Ctilde%7Ba%7D_T%20=%201/S"> for any targeted channel, or <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N"> from the universal channel. They pick <img src="https://latex.codecogs.com/png.latex?%5Cmax(1/S,%5C%20%5Cbeta/N)">.</p>
<p>For the incumbent, the equilibrium is <img src="https://latex.codecogs.com/png.latex?%5Cmax_%7BT%20%5Csupseteq%20W%7D%201/%7CT%7C%20=%201/W"> from targeted, or <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N"> from the universal channel. The incumbent chooses whichever channel yields the best surplus of loyalty.</p>
<p>Therefore, if spending on <img src="https://latex.codecogs.com/png.latex?T"> splits evenly among <img src="https://latex.codecogs.com/png.latex?%7CT%7C">, the entire channel space collapses to three numbers: <img src="https://latex.codecogs.com/png.latex?1/W">, <img src="https://latex.codecogs.com/png.latex?1/S">, and <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N">. The two-control model is not an arbitrary simplification. Instead, under adversarial play and uniform inclusion, channels defined by uniform subset transfers compress to three coefficients. The <img src="https://latex.codecogs.com/png.latex?1/S"> compression on the defector side depends on uniform inclusion (equal probability of being in the challenger’s coalition) and no commitment (the challenger cannot condition on who defected). Instruments with different transfer technologies (coercion, propaganda, targeted services with non-uniform delivery) need not compress this way. If challengers could preferentially recruit defectors, subset channels would no longer collapse.</p>
</section>
<section id="hierarchical-composition" class="level1">
<h1>Hierarchical Composition</h1>
<p>What happens when each coalition member is themselves a leader with a sub-selectorate? Assume that the sub-selectorates partition the top-level selectorate, so each member of <img src="https://latex.codecogs.com/png.latex?S_1"> belongs to one sub-leader’s domain<sup>7</sup>.</p>
<section id="channel-attenuation" class="level2">
<h2 class="anchored" data-anchor-id="channel-attenuation">Channel Attenuation</h2>
<p>We can think of the subleaders as “leaking” as the budget flows down the levels of the hierarchy. The top leader sends a targeted transfer, but the sub-leader must spend at least part of it to maintain their own coalition. The fraction that passes through determines how much the hierarchy costs. Call this the attenuation factor <img src="https://latex.codecogs.com/png.latex?%5Ckappa">.</p>
</section>
<section id="two-level-model" class="level2">
<h2 class="anchored" data-anchor-id="two-level-model">Two-Level Model</h2>
<p>A top leader has parameters <img src="https://latex.codecogs.com/png.latex?(W_1,%20S_1,%20B_1)">. Each of the <img src="https://latex.codecogs.com/png.latex?W_1"> coalition members is a sub-leader with their own selectorate <img src="https://latex.codecogs.com/png.latex?(W_2,%20S_2,%20B_2)">. Assume that the sub-leader’s only budget is the transfer <img src="https://latex.codecogs.com/png.latex?T"> received from above, and they have no independent revenue (no local taxes, fiefs, or alternative instruments). Under this assumption, <img src="https://latex.codecogs.com/png.latex?B_2"> drops out and the sub-leader’s equilibrium is determined entirely by <img src="https://latex.codecogs.com/png.latex?r_2%20=%20W_2/S_2"> (if sub-leaders had independent local budgets, the recursion would acquire additive terms and the clean one-parameter Möbius recursion below would not close). The sub-leader’s loyalty constraint is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_2%5E%7B%5Cmin%7D%20=%20T%20%5Ccdot%20r_2%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?T"> is the transfer received from the top leader. The sub-leader spends <img src="https://latex.codecogs.com/png.latex?T%20r_2"> to maintain their own coalition and can pass through at most <img src="https://latex.codecogs.com/png.latex?T(1-r_2)"> as usable value to their coalition members. From the top leader’s perspective, that means a dollar sent to a coalition member is discounted by the downstream factor <img src="https://latex.codecogs.com/png.latex?1-r_2">. The top-level loyalty constraint therefore becomes:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(1-r_2)%5Ccdot%20%5Cfrac%7Bp_1%7D%7BW_1%7D%20%5Cgeq%20%5Cfrac%7BB_1%7D%7BS_1%7D%0A"></p>
<p>Solving gives:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_1%5E%7B%5Cmin%7D%20=%20B_1%5Ccdot%20%5Cfrac%7Br_1%7D%7B1-r_2%7D%0A"></p>
<p>This is the two-level instance of the bottom-up effective-share recursion defined below: <img src="https://latex.codecogs.com/png.latex?r_2%5E%7B%5Ctext%7Beff%7D%7D=r_2"> and <img src="https://latex.codecogs.com/png.latex?r_1%5E%7B%5Ctext%7Beff%7D%7D=%5Cfrac%7Br_1%7D%7B1-r_2%5E%7B%5Ctext%7Beff%7D%7D%7D">. The only thing the top level needs from the sub-level is the scalar <img src="https://latex.codecogs.com/png.latex?r_2%5E%7B%5Ctext%7Beff%7D%7D">, which summarizes downstream incentive consumption.</p>
</section>
<section id="n-level-model" class="level2">
<h2 class="anchored" data-anchor-id="n-level-model"><img src="https://latex.codecogs.com/png.latex?n">-Level Model</h2>
<p>The two-level result generalizes recursively. A sub-leader’s effective cost share must include all downstream hierarchy costs, not just their local share <img src="https://latex.codecogs.com/png.latex?r_k">. Define the effective cost share bottom-up:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ar_n%5E%7B%5Ctext%7Beff%7D%7D%20=%20r_n,%20%5Cqquad%20r_k%5E%7B%5Ctext%7Beff%7D%7D%20=%20%5Cfrac%7Br_k%7D%7B1%20-%20r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D%7D%0A"></p>
<p>Each sub-level’s effective share reduces the available surplus, inflating the cost at the level above. For two levels this gives <img src="https://latex.codecogs.com/png.latex?r_1/(1-r_2)"> as before. For three levels:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ar%5E%7B%5Ctext%7Beff%7D%7D%20=%20%5Cfrac%7Br_1%7D%7B1%20-%20%5Cfrac%7Br_2%7D%7B1%20-%20r_3%7D%7D%20=%20%5Cfrac%7Br_1(1%20-%20r_3)%7D%7B1%20-%20r_2%20-%20r_3%7D%0A"></p>
<p>The hierarchy is viable when <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20%5Cleq%201">. For identical sub-levels <img src="https://latex.codecogs.com/png.latex?r_k%20=%20r">, the viability constraint tightens with depth. An infinite hierarchy converges only when <img src="https://latex.codecogs.com/png.latex?r%20%5Cleq%201/4"> (the fixed point of <img src="https://latex.codecogs.com/png.latex?x%20=%20r/(1-x)"> exists when <img src="https://latex.codecogs.com/png.latex?1%20-%204r%20%5Cgeq%200">).</p>
<p>Deep hierarchies selectively attenuate the targeted channel. The continued-fraction structure means costs compound, and each sub-level’s effective cost shrinks the surplus available to the level above. The universal channel (public goods), by contrast, is not attenuated by the hierarchy, since public goods benefit everyone regardless of intermediary structure. This differential attenuation is why deep hierarchies push toward public goods provision.</p>
<p>The asymmetric attenuation is a modeling assumption. Public goods pass through the hierarchy undiminished (<img src="https://latex.codecogs.com/png.latex?%5Cbeta%20g/N"> per citizen regardless of depth), while targeted transfers are consumed at each level. In practice, local public goods can be captured by intermediaries, and some targeted transfers (direct electronic payments) can bypass the hierarchy entirely. The general point is that different channels attenuate differently, and depth selects for whichever channel is least attenuated.</p>
</section>
<section id="interpretation" class="level2">
<h2 class="anchored" data-anchor-id="interpretation">Interpretation</h2>
<p>The upshot of the model is that downstream politics inflates the upstream effective cost share: <img src="https://latex.codecogs.com/png.latex?r_1%5E%7B%5Ctext%7Beff%7D%7D%20%5Cgeq%20r_1"> whenever <img src="https://latex.codecogs.com/png.latex?r_2%5E%7B%5Ctext%7Beff%7D%7D%20%3E%200">. Sub-leaders consume transfers to maintain their own coalitions, attenuating the targeted channel. Because autocracy is cheap (small <img src="https://latex.codecogs.com/png.latex?r">, low attenuation), the top leader is incentivized to prefer “autocratic” sub-leaders (small <img src="https://latex.codecogs.com/png.latex?r_2">, small <img src="https://latex.codecogs.com/png.latex?%5Ctau">). This can create top-down pressure for authoritarianism at every level of the hierarchy.</p>
<p>Furthermore, the attenuation compounds through the continued fraction. Even if each level’s local cost <img src="https://latex.codecogs.com/png.latex?r_k"> is small, the effective cost <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> grows as downstream costs eat into the surplus at every level. Under the assumption that public goods are not attenuated by intermediaries, the system must eventually switch to the universal channel when targeted transfers become too expensive. No single level’s parameters make private loyalty-buying impossible, but composition across levels can. Whether this produces a sharp threshold depends on the relative attenuation rates across channels.</p>
<p>There are two ways to read the causality. Either “deep hierarchies make targeted transfers unviable, selecting for less-attenuated channels” or “societies that rely on broadly distributed goods (defense, infrastructure, trade networks) can sustain deeper hierarchies.” The model itself is static and doesn’t distinguish these.</p>
<p>The robust prediction is narrower: when <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20%3E%201">, the targeted channel cannot fund the hierarchy (<img src="https://latex.codecogs.com/png.latex?p_1%5E%7B%5Cmin%7D%20%3E%20B_1">). Deep patronage hierarchies hit this bound. What replaces the targeted channel depends on the attenuation structure of available alternatives.</p>
<p>These preferences can reverse through a mechanism outside the static model. If sub-level public goods feed back into the top-level budget (democratic governors produce education, infrastructure, rule of law, raising local productivity and therefore <img src="https://latex.codecogs.com/png.latex?B_1"> through taxation), then the top leader faces a tradeoff not present in the formal setup: <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D/B"> doesn’t depend on <img src="https://latex.codecogs.com/png.latex?B">, but the absolute discretionary surplus is <img src="https://latex.codecogs.com/png.latex?(1%20-%20r)%20%5Ccdot%20B_1">, so the top leader prefers democratic governors when the productivity gain to <img src="https://latex.codecogs.com/png.latex?B_1"> outweighs the cost amplification from the hierarchy. This requires endogenizing <img src="https://latex.codecogs.com/png.latex?B_1"> as a function of sub-level policy, which the static selectorate game does not do.</p>
<p>This explains the logic of modern federal democracies: the central government tolerates democratic local governance because it produces a wealthier economy to tax. Empires that allow local self-governance (Rome at its peak, the British dominion model, America) seem to outperform centralized control in some cases (late-stage Ottoman, Soviet).</p>
</section>
<section id="renormalization" class="level2">
<h2 class="anchored" data-anchor-id="renormalization">Renormalization</h2>
<p>We can think of the <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> recursion as a type of renormalization. At each level of the hierarchy, we solve the sub-level equilibrium, extract the single scalar <img src="https://latex.codecogs.com/png.latex?r_k%5E%7B%5Ctext%7Beff%7D%7D">, and discard the rest. The full specification of sub-level strategies, payoffs, and coalition dynamics is replaced by one number that summarizes everything the level above needs to know. The top leader does not need to know how many agents are at level 3, or what their loyalty margins are, or how the sub-sub-leaders allocate. All of that information is compressed into <img src="https://latex.codecogs.com/png.latex?r_k%5E%7B%5Ctext%7Beff%7D%7D">.</p>
<p>This compression composes. The continued fraction <img src="https://latex.codecogs.com/png.latex?r_k%5E%7B%5Ctext%7Beff%7D%7D%20=%20r_k/(1-r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D)"> summarizes an arbitrarily deep hierarchy into one effective cost share. Budget invariance is what makes the composition one-directional, since the sub-level’s equilibrium depends only on <img src="https://latex.codecogs.com/png.latex?r_k">, not on the transfer it receives, so each step is independent of the top-level solution.</p>
<p>As depth increases under fixed local parameters (e.g.&nbsp;identical <img src="https://latex.codecogs.com/png.latex?r_k%20=%20r">), the effective share <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> evolves under repeated Möbius maps and eventually hits the pole at <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20=%201"> when the hierarchy becomes unviable. No single level’s parameters makes private loyalty-buying impossible, but the accumulated flow can.</p>
<p>For other queries (distributional outcomes at the bottom, total public goods delivered, probability of revolt), the compression is lossy. <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> tells you everything you need to know about <img src="https://latex.codecogs.com/png.latex?p_1%5E%7B%5Cmin%7D">, but not about everything.</p>
</section>
<section id="tradeoffs-on-width-and-depth" class="level2">
<h2 class="anchored" data-anchor-id="tradeoffs-on-width-and-depth">Tradeoffs on Width and Depth</h2>
<p>A flat structure (<img src="https://latex.codecogs.com/png.latex?n%20=%201">) pays no hierarchy tax. Every additional level inflates <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> through the recursion, making loyalty more expensive. So why not keep everything flat?</p>
<p>Flat structures face a control problem that lives outside this model. A single leader managing a large population directly is logistically impossible. Hierarchy exists to solve coordination and monitoring problems that scale with <img src="https://latex.codecogs.com/png.latex?S">. The hierarchy tax is the price paid for this coordination capacity.</p>
<p>The trade-off determines an implied maximum depth for targeted-transfer regimes. The feasibility constraint is <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20%5Cleq%201"> (the leader cannot spend more than the budget). For identical sub-levels <img src="https://latex.codecogs.com/png.latex?r_k%20=%20r">, the recursion <img src="https://latex.codecogs.com/png.latex?x_%7Bk+1%7D%20=%20r/(1-x_k)"> starting from <img src="https://latex.codecogs.com/png.latex?x_1%20=%20r"> determines the maximum viable depth: the largest <img src="https://latex.codecogs.com/png.latex?n"> for which <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20%3C%201">. For <img src="https://latex.codecogs.com/png.latex?r%20%5Cleq%201/4">, the continued fraction converges and arbitrarily deep hierarchies are viable. For <img src="https://latex.codecogs.com/png.latex?r%20%3E%201/4">, the recursion reaches the pole <img src="https://latex.codecogs.com/png.latex?x%20=%201"> at finite depth.</p>
<p>Beyond this depth, the targeted channel cannot sustain the hierarchy and the system must either switch to a less-attenuated channel or flatten.</p>
<p>Population size creates a constraint in the other direction. The total population at the bottom scales as <img src="https://latex.codecogs.com/png.latex?(rs)%5E%7Bn-1%7D%20%5Ccdot%20s(1-r)">, so managing a population of size <img src="https://latex.codecogs.com/png.latex?N"> with span <img src="https://latex.codecogs.com/png.latex?s"> requires at least <img src="https://latex.codecogs.com/png.latex?n%20%5Cgeq%20%5Clog%20N%20/%20%5Clog(rs)"> levels. A city-state can stay relatively flat, but a large empire cannot. This gives two competing bounds on hierarchy depth.</p>
<p>The lower bound (from population) is <img src="https://latex.codecogs.com/png.latex?n%20%5Cgeq%20%5Clog%20N%20/%20%5Clog(rs)">. Large populations require deep hierarchies.</p>
<p>The upper bound (from viability) is the <img src="https://latex.codecogs.com/png.latex?n"> at which <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20%3E%201">, i.e., <img src="https://latex.codecogs.com/png.latex?p_1%5E%7B%5Cmin%7D%20%3E%20B_1">.</p>
<p>The intersection is the feasible region. For small <img src="https://latex.codecogs.com/png.latex?r"> (with autocratic sub-levels), the recursion converges slowly and the upper bound is generous, but the lower bound still forces depth as <img src="https://latex.codecogs.com/png.latex?N"> grows. For large <img src="https://latex.codecogs.com/png.latex?r"> (democratic sub-levels), <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> diverges quickly and the upper bound is tight, but public goods provision sidesteps the attenuation problem entirely.</p>
<p>The implication is that large populations cannot sustain deep patronage hierarchies: the hierarchy tax accumulates exponentially, and the targeted channel eventually becomes unviable. What replaces it depends on which channels are less attenuated. If public goods pass through the hierarchy with lower attenuation than targeted transfers (our modeling assumption), then large <img src="https://latex.codecogs.com/png.latex?N"> selects for public goods provision<sup>8</sup>.</p>
<p>We can classify governance structures along these two axes<sup>9</sup>.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Small <img src="https://latex.codecogs.com/png.latex?r"> (private goods)</th>
<th>Large <img src="https://latex.codecogs.com/png.latex?r"> (public goods)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Shallow</strong> (<img src="https://latex.codecogs.com/png.latex?n%20%5Cleq%202">)</td>
<td>Personalist dictatorships, city-states. No hierarchy tax. Stable as long as <img src="https://latex.codecogs.com/png.latex?S"> is manageable.</td>
<td>Direct democracies, Swiss cantons. Stable but scale-limited: flat structure can’t coordinate large <img src="https://latex.codecogs.com/png.latex?S">.</td>
</tr>
<tr class="even">
<td><strong>Deep</strong> (<img src="https://latex.codecogs.com/png.latex?n%20%5Cgg%201">)</td>
<td>Feudalism, tributary empires, patronage networks. Fragile: <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> diverges toward the pole.</td>
<td>Federal democracies, imperial bureaucracies with civil service. Viable because broadly distributed goods sidestep the attenuation problem.</td>
</tr>
</tbody>
</table>
</section>
</section>
<section id="decision-count-analysis" class="level1">
<h1>Decision Count Analysis</h1>
<p>How many decisions does a hierarchy involve? In an Olsonian public goods game (stag hunt) with <img src="https://latex.codecogs.com/png.latex?n"> agents, the answer is simply <img src="https://latex.codecogs.com/png.latex?n">: each agent makes one symmetric binary decision (contribute or free-ride). There is no distinguished agent, no allocation variable, and no asymmetry.</p>
<p>The selectorate model breaks this symmetry. There are two types of decisions. Each leader makes an allocation decision (choose <img src="https://latex.codecogs.com/png.latex?p">), and each coalition member makes a loyalty decision (loyal or defect).</p>
<p>In the feudal nesting model, suppose each level has uniform selectorate size <img src="https://latex.codecogs.com/png.latex?S_k%20=%20s"> and coalition size <img src="https://latex.codecogs.com/png.latex?W_k%20=%20rs"> (so the coalition ratio is <img src="https://latex.codecogs.com/png.latex?r"> at every level):</p>
<ul>
<li><strong>Level 1</strong>: 1 leader allocates, <img src="https://latex.codecogs.com/png.latex?rs"> coalition members each decide loyalty. Total: <img src="https://latex.codecogs.com/png.latex?1%20+%20rs"> decisions.</li>
<li><strong>Level 2</strong>: <img src="https://latex.codecogs.com/png.latex?rs"> sub-leaders each allocate, each with <img src="https://latex.codecogs.com/png.latex?rs"> coalition members deciding loyalty. Total: <img src="https://latex.codecogs.com/png.latex?rs%20+%20(rs)%5E2"> decisions.</li>
<li><strong>Level <img src="https://latex.codecogs.com/png.latex?k"></strong>: <img src="https://latex.codecogs.com/png.latex?(rs)%5E%7Bk-1%7D"> allocation decisions + <img src="https://latex.codecogs.com/png.latex?(rs)%5Ek"> loyalty decisions.</li>
</ul>
<p>The total decision count across <img src="https://latex.codecogs.com/png.latex?n"> levels is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cunderbrace%7B%5Cfrac%7B(rs)%5En%20-%201%7D%7Brs%20-%201%7D%7D_%7B%5Ctext%7Ballocation%7D%7D%20+%20%5Cunderbrace%7B%5Cfrac%7Brs%20%5Ccdot%20((rs)%5En%20-%201)%7D%7Brs%20-%201%7D%7D_%7B%5Ctext%7Bloyalty%7D%7D%20=%20(1%20+%20rs)%20%5Ccdot%20%5Cfrac%7B(rs)%5En%20-%201%7D%7Brs%20-%201%7D%0A"></p>
<p>Loyalty decisions dominate by a factor of <img src="https://latex.codecogs.com/png.latex?rs">. For every leader choosing how to split a budget, there are <img src="https://latex.codecogs.com/png.latex?rs"> agents deciding whether to stay or defect. The total population (citizens at the bottom who don’t lead anyone) scales as <img src="https://latex.codecogs.com/png.latex?(rs)%5E%7Bn-1%7D%20%5Ccdot%20s(1-r)">.</p>
<p>The attenuation result indicates that all <img src="https://latex.codecogs.com/png.latex?(1+rs)%20%5Ccdot%20%5Cfrac%7B(rs)%5En%20-%201%7D%7Brs%20-%201%7D"> micro-level decisions are compressed into a single effective parameter <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> at the top, and so the top leader doesn’t need to know the internal politics of each sub-domain. They only need to know <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D">, which summarizes everything below into a single cost share. The continued-fraction recursion replaces exponentially many individual decisions with a small number of effective parameters. An Olsonian model with the same population would have the same number of decisions but no comparable compression. In a symmetric <img src="https://latex.codecogs.com/png.latex?n">-player game, you can exploit symmetry to characterize the equilibrium by a single mixed-strategy probability <img src="https://latex.codecogs.com/png.latex?p%5E*">, but this is an analytical convenience for the modeler, not a structural feature of the game. Finding <img src="https://latex.codecogs.com/png.latex?p%5E*"> requires solving the full system simultaneously. No agent inside the game has privileged access to the compressed description, and there is no modular decomposition, so you cannot solve “part of the game” independently and feed the result into another part. In the selectorate model, each sub-level’s equilibrium is computed from <img src="https://latex.codecogs.com/png.latex?r_k"> and <img src="https://latex.codecogs.com/png.latex?r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D">, producing <img src="https://latex.codecogs.com/png.latex?r_k%5E%7B%5Ctext%7Beff%7D%7D">, which feeds into the level above. Budget invariance ensures the decomposition is one-directional: the sub-problem doesn’t depend on the top-level solution.</p>
<p>More broadly, <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> is a lossless compression of sub-level coalition politics for the specific query “what <img src="https://latex.codecogs.com/png.latex?p_1%5E%7B%5Cmin%7D"> does the top leader need?” The “source” is the full specification of sub-level strategies, payoffs, and equilibria; the “compressed representation” is a single scalar; and the distortion is zero for this query. For other queries (distributional outcomes at the bottom, total public goods delivered, probability of revolt) the compression is lossy. This is a special case of a more general question: given an <img src="https://latex.codecogs.com/png.latex?n">-player game, when can you compress a coalition of players into an effective agent with fewer parameters while preserving the equilibrium structure at coarser levels? The selectorate model is compressible due to linearity and budget invariance. In general, the compression will be lossy<sup>10</sup>.</p>
<p>The decision count also constrains which hierarchies are feasible. Deeper hierarchies require exponentially larger populations, which is why deep patronage hierarchies are historically associated with empires rather than city-states.</p>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>The selectorate model distills the logic of political survival into a small set of parameters. When the winning coalition <img src="https://latex.codecogs.com/png.latex?W"> is small relative to the selectorate <img src="https://latex.codecogs.com/png.latex?S">, private loyalty-buying is cheap and the leader retains wide discretion. As <img src="https://latex.codecogs.com/png.latex?W"> grows, the cost of targeted transfers increases (<img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D%20=%20Br">), squeezing rents. In the linear model, this does not by itself produce public goods: the incumbent uses targeted transfers throughout unless <img src="https://latex.codecogs.com/png.latex?W%20%5Cgeq%20N/%5Cbeta"> (an extreme corner). The classic BdM result, where public goods provision increases smoothly with <img src="https://latex.codecogs.com/png.latex?W">, requires concavity in <img src="https://latex.codecogs.com/png.latex?v">. What the linear model does establish cleanly is the challenger’s best-response switch at <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20S%20=%20N"> and the three-coefficient geometry.</p>
<p>The hierarchical composition result is a separate mechanism. Depth compounds the effective cost <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D"> through the continued-fraction recursion, which can make the targeted channel unviable even when no single level does. This pushes deep hierarchies toward channels with lower attenuation (conditionally on public goods being less attenuated than targeted transfers, which is a modeling assumption, not a theorem).</p>
<p>The leader’s problem is to design an incentive scheme that induces loyalty among a coalition of agents. The model provides a closed-form solution for the minimum cost, and characterizes how it changes with institutional parameters. The hierarchical composition shows how local incentive problems aggregate into global constraints.</p>
<p>Is this continued-fraction structure specific to linear payoffs and budget invariance, or is it generic to any model where local equilibrium constraints rescale upstream transfers? The hierarchy composition acts via <img src="https://latex.codecogs.com/png.latex?PGL(2)"> (see appendix) on the effective cost share, with each level contributing a non-diagonal matrix <img src="https://latex.codecogs.com/png.latex?M_k%20=%20%5Cbigl(%5Cbegin%7Bsmallmatrix%7D%200%20&amp;%20r_k%20%5C%5C%20-1%20&amp;%201%20%5Cend%7Bsmallmatrix%7D%5Cbigr)">.</p>
<p>The channel-compression and hierarchical Möbius structure derived here rest on linearity, budget invariance, and symmetric inclusion. Whether similar renormalization-style recursions survive under more general transfer technologies or informational frictions remains an open question and suggests a broader research program.</p>
</section>
<section id="caveats" class="level1">
<h1>Caveats</h1>
<p>The selectorate model is elegant and generates sharp predictions. It is also, in certain popular treatments, sometimes oversold.</p>
<ol type="1">
<li><p>Binary loyalty. Coalition members choose Loyal or Defect. Real political actors face a spectrum of options: partial cooperation, conditional support, hedging, signaling.</p></li>
<li><p>No information asymmetry. Everyone observes the leader’s allocation <img src="https://latex.codecogs.com/png.latex?p">, the challenger’s strategy, and the coalition structure. Real authoritarian politics is rife with private information: leaders don’t know who is truly loyal, coalition members don’t know the leader’s true budget, and challengers can’t credibly commit to future allocations. Models that incorporate these features (e.g., <a href="https://doi.org/10.1016/j.jet.2011.06.012" class="external" target="_blank">Egorov and Sonin, 2009</a>, on dictators and their viziers) yield richer and sometimes different predictions.</p></li>
<li><p>Linear public goods. With linear payoffs, the leader always uses one channel or the other, never a mix, and uses targeted transfers exclusively unless <img src="https://latex.codecogs.com/png.latex?W%20%5Cgeq%20N/%5Cbeta"> (typically infeasible). The headline qualitative result, that large coalitions push leaders toward public goods, does not follow from the linear model; it requires concavity in <img src="https://latex.codecogs.com/png.latex?v">, which produces interior solutions with <img src="https://latex.codecogs.com/png.latex?g%5E*%20%3E%200"> increasing smoothly in <img src="https://latex.codecogs.com/png.latex?W">. Concave returns can also generate public goods provision outside the democratic region, since the high marginal return at low <img src="https://latex.codecogs.com/png.latex?g"> can justify some public spending even when <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N%20%3C%201/S">. The linear model captures the regime switch and the three-coefficient geometry but misses this interior structure.</p></li>
<li><p>Exogenous institutions. The model takes <img src="https://latex.codecogs.com/png.latex?W"> and <img src="https://latex.codecogs.com/png.latex?S"> as given. But real leaders actively manipulate these parameters: expanding the selectorate (extending suffrage), shrinking the coalition (purging rivals), creating new institutional structures. Endogenizing <img src="https://latex.codecogs.com/png.latex?W"> and <img src="https://latex.codecogs.com/png.latex?S"> is a much harder problem<sup>11</sup>.</p></li>
</ol>
<ol start="5" type="1">
<li>Oversimplified mapping to real regimes. Popular presentations sometimes map <img src="https://latex.codecogs.com/png.latex?W/S"> ratios too directly onto regime types: “democracy = large <img src="https://latex.codecogs.com/png.latex?W">, dictatorship = small <img src="https://latex.codecogs.com/png.latex?W">.” Reality is messier. Some democracies have effectively small winning coalitions (gerrymandered single-party states); some autocracies maintain large coalitions (Singapore’s PAP). The model provides useful intuition about <em>incentives</em> but should not be mistaken for a precise taxonomy of political systems.</li>
</ol>
<p>None of this invalidates the model. The selectorate framework remains one of the most productive formal theories in comparative politics. But its predictions are best understood as comparative statics about incentives, not as iron laws of political behavior.</p>
</section>
<section id="appendix" class="level1">
<h1>Appendix</h1>
<section id="additional-model-analysis" class="level2">
<h2 class="anchored" data-anchor-id="additional-model-analysis">Additional Model Analysis</h2>
<section id="projective-structure" class="level3">
<h3 class="anchored" data-anchor-id="projective-structure">Projective Structure</h3>
<p>The minimum transfer share <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D/B%20=%20r%20=%20W/S"> has clean structural properties:</p>
<section id="budget-invariance" class="level4">
<h4 class="anchored" data-anchor-id="budget-invariance">Budget Invariance</h4>
<p><img src="https://latex.codecogs.com/png.latex?B"> cancels completely. A rich autocracy and a poor autocracy have identical equilibrium shares. This is a formal version of the “institutions, not resources” thesis. A singular source of wealth (i.e.&nbsp;oil) increases <img src="https://latex.codecogs.com/png.latex?B"> without changing <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D/B">, so the discretionary surplus grows proportionally. Foreign aid has the same problem if it enters as <img src="https://latex.codecogs.com/png.latex?B">. More money flowing to a small-coalition regime likely makes governance worse, not better.</p>
</section>
<section id="population-scaling" class="level4">
<h4 class="anchored" data-anchor-id="population-scaling">Population Scaling</h4>
<p><img src="https://latex.codecogs.com/png.latex?(W,%20S)%20%5Cto%20(%5Clambda%20W,%20%5Clambda%20S)"> leaves <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D/B"> invariant. Only the ratio between <img src="https://latex.codecogs.com/png.latex?W"> and <img src="https://latex.codecogs.com/png.latex?S"> matters.</p>
</section>
<section id="linearity-and-projective-structure" class="level4">
<h4 class="anchored" data-anchor-id="linearity-and-projective-structure">Linearity and Projective Structure</h4>
<p>In the one-decision model, <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D/B%20=%20r"> is simply linear. The interesting structure emerges when we consider hierarchy. The recursion <img src="https://latex.codecogs.com/png.latex?r_k%5E%7B%5Ctext%7Beff%7D%7D%20=%20r_k/(1%20-%20r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D)"> is a <a href="https://en.wikipedia.org/wiki/M%C3%B6bius_transformation" class="external" target="_blank">Möbius transformation</a> in <img src="https://latex.codecogs.com/png.latex?r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D">. Work in projective coordinates: identify nonzero vectors <img src="https://latex.codecogs.com/png.latex?(x,y)%20%5Csim%20(%5Clambda%20x,%5Clambda%20y)"> for <img src="https://latex.codecogs.com/png.latex?%5Clambda%20%5Cneq%200">. On the affine chart <img src="https://latex.codecogs.com/png.latex?y%20%5Cneq%200">, the coordinate is <img src="https://latex.codecogs.com/png.latex?r%20=%20x/y">.</p>
<p>To see the matrix structure, represent <img src="https://latex.codecogs.com/png.latex?r"> as the vector <img src="https://latex.codecogs.com/png.latex?(r,%201)%5ET">. A <img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%202"> matrix <img src="https://latex.codecogs.com/png.latex?%5Cbigl(%5Cbegin%7Bsmallmatrix%7D%20a%20&amp;%20b%20%5C%5C%20c%20&amp;%20d%20%5Cend%7Bsmallmatrix%7D%5Cbigr)"> sends <img src="https://latex.codecogs.com/png.latex?(r,1)%5ET%20%5Cmapsto%20(ar+b,%5C,%20cr+d)%5ET">, which corresponds in the affine chart <img src="https://latex.codecogs.com/png.latex?cr+d%20%5Cneq%200"> to the value <img src="https://latex.codecogs.com/png.latex?(ar+b)/(cr+d)">. The recursion <img src="https://latex.codecogs.com/png.latex?r_k/(1-x)=%20(0%20%5Ccdot%20x%20+%20r_k)/(-1%20%5Ccdot%20x%20+%201)"> gives <img src="https://latex.codecogs.com/png.latex?a%20=%200">, <img src="https://latex.codecogs.com/png.latex?b%20=%20r_k">, <img src="https://latex.codecogs.com/png.latex?c%20=%20-1">, <img src="https://latex.codecogs.com/png.latex?d%20=%201">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AM_k%20=%20%5Cbegin%7Bpmatrix%7D%200%20&amp;%20r_k%20%5C%5C%20-1%20&amp;%201%20%5Cend%7Bpmatrix%7D%20%5Cin%20PGL(2)%0A"></p>
<p>For example, <img src="https://latex.codecogs.com/png.latex?M_k"> sends <img src="https://latex.codecogs.com/png.latex?(r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D,%201)%5ET%20%5Cmapsto%20(r_k,%5C,%201-r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D)%5ET">, representing <img src="https://latex.codecogs.com/png.latex?r_k/(1-r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D)%20=%20r_k%5E%7B%5Ctext%7Beff%7D%7D">. For <img src="https://latex.codecogs.com/png.latex?n"> levels, the composition <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20=%20f_1(f_2(%5Ccdots%20f_%7Bn-1%7D(r_n)))"> corresponds to the matrix product:</p>
<p>Write the bottom parameter as the vector <img src="https://latex.codecogs.com/png.latex?(r_n,%201)%5ET">. Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(M_1%20M_2%20%5Ccdots%20M_%7Bn-1%7D)(r_n,%201)%5ET%20=%20(x,%20y)%5ET%0A"></p>
<p>and the effective share is the affine coordinate <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20=%20x/y"> (when <img src="https://latex.codecogs.com/png.latex?y%20%5Cneq%200">).</p>
<p>These matrices are not diagonal; composition is genuinely <img src="https://latex.codecogs.com/png.latex?PGL(2)">, not just scaling. For three levels:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AM_1%20M_2%20=%20%5Cbegin%7Bpmatrix%7D%200%20&amp;%20r_1%20%5C%5C%20-1%20&amp;%201%20%5Cend%7Bpmatrix%7D%20%5Cbegin%7Bpmatrix%7D%200%20&amp;%20r_2%20%5C%5C%20-1%20&amp;%201%20%5Cend%7Bpmatrix%7D%20=%20%5Cbegin%7Bpmatrix%7D%20-r_1%20&amp;%20r_1%20%5C%5C%20-1%20&amp;%201-r_2%20%5Cend%7Bpmatrix%7D%0A"></p>
<p>Applied to <img src="https://latex.codecogs.com/png.latex?r_3">, this gives <img src="https://latex.codecogs.com/png.latex?(-r_1%20r_3%20+%20r_1)/(-r_3%20+%201%20-%20r_2)%20=%20r_1(1-r_3)/(1-r_2-r_3)">. The off-diagonal entries are real. The pole at <img src="https://latex.codecogs.com/png.latex?r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D%20=%201"> (where the sub-hierarchy consumes the entire transfer) is a fixed point of the group action. The viability boundary <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20%5Cleq%201"> is the condition that the effective cost share does not exceed the budget<sup>12</sup>.</p>
<p>With <img src="https://latex.codecogs.com/png.latex?n"> spending channels, the loyalty constraint is a linear inequality in <img src="https://latex.codecogs.com/png.latex?n"> spending variables, and the constraint coefficients live in <img src="https://latex.codecogs.com/png.latex?(%5Cmathbb%7BRP%7D%5En)%5E*">. The natural conjecture is that hierarchical composition acts via <img src="https://latex.codecogs.com/png.latex?PGL(n+1)"> on this coefficient space. This post derives the single-channel case; the general multi-channel composition remains open.</p>
</section>
</section>
</section>
<section id="code" class="level2">
<h2 class="anchored" data-anchor-id="code">Code</h2>
<p>We can encode the model as a differentiable PyTorch module, making the comparative statics computable rather than just algebraic. Caveat Emptor: Claude wrote this code.</p>
<div id="c41c20e2" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> SelectorateEquilibrium:</span>
<span id="cb1-3">    p_min: torch.Tensor              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># minimum targeted transfer</span></span>
<span id="cb1-4">    rents: torch.Tensor              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># B - p_min</span></span>
<span id="cb1-5">    coalition_payoff: torch.Tensor   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># p_min / W</span></span>
<span id="cb1-6">    defection_payoff: torch.Tensor   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># B / S</span></span>
<span id="cb1-7">    inclusion_prob: torch.Tensor     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># W / S</span></span>
<span id="cb1-8">    loyalty_margin: torch.Tensor     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># coalition - defection</span></span></code></pre></div>
</div>
<div id="3d36d685" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> SelectorateModel(nn.Module):</span>
<span id="cb2-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, W<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>, S<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>, B<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>):</span>
<span id="cb2-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb2-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.tensor(W))</span>
<span id="cb2-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.tensor(S))</span>
<span id="cb2-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.tensor(B))</span>
<span id="cb2-7"></span>
<span id="cb2-8">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb2-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> r(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.S</span>
<span id="cb2-11"></span>
<span id="cb2-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> p_min(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.r</span>
<span id="cb2-14"></span>
<span id="cb2-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> tau(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-16">        r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.r</span>
<span id="cb2-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> torch.clamp(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> r, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-8</span>)</span>
<span id="cb2-18"></span>
<span id="cb2-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> kappa(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Attenuation factor: fraction of transfer that passes through."""</span></span>
<span id="cb2-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.r</span>
<span id="cb2-22"></span>
<span id="cb2-23">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-24">        p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.p_min()</span>
<span id="cb2-25">        rents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p</span>
<span id="cb2-26">        coalition_pay <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.W</span>
<span id="cb2-27">        defection_pay <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.S</span>
<span id="cb2-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> SelectorateEquilibrium(</span>
<span id="cb2-29">            p_min<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>p, rents<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rents,</span>
<span id="cb2-30">            coalition_payoff<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>coalition_pay, defection_payoff<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>defection_pay,</span>
<span id="cb2-31">            inclusion_prob<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.r,</span>
<span id="cb2-32">            loyalty_margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>coalition_pay <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> defection_pay,</span>
<span id="cb2-33">        )</span></code></pre></div>
</div>
<p>The <img src="https://latex.codecogs.com/png.latex?p%5E%7B%5Cmin%7D%20=%20B%20%5Ccdot%20W/S"> formula, wrapped so that PyTorch’s autograd can differentiate through it:</p>
<div id="8635e2b6" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SelectorateModel(W<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>, S<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>, B<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>)</span>
<span id="cb3-2">eq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model()</span>
<span id="cb3-3">eq.rents.backward()</span>
<span id="cb3-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"d(rents)/dW = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>model<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>W<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>grad<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># negative: more W, less rents</span></span></code></pre></div>
</div>
<p>Each additional coalition member decreases rents, confirming that larger coalitions squeeze the leader’s discretionary surplus.</p>
<p>There is no separate hierarchical model. A hierarchy is just composition of flat selectorates. Each level has its own <code>SelectorateModel</code>. Hierarchical composition follows the continued-fraction recursion <img src="https://latex.codecogs.com/png.latex?r_k%5E%7B%5Ctext%7Beff%7D%7D%20=%20r_k/(1-r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D)">, not multiplication of per-level factors:</p>
<div id="3f0f5ec9" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> r_eff_from_levels(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>levels):</span>
<span id="cb4-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb4-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    levels ordered top to bottom.</span></span>
<span id="cb4-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    returns the top-level effective share r_eff under the recursion</span></span>
<span id="cb4-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    r_n^eff = r_n,  r_k^eff = r_k / (1 - r_{k+1}^eff).</span></span>
<span id="cb4-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb4-7">    x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> levels[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].r</span>
<span id="cb4-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> level <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reversed</span>(levels[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]):</span>
<span id="cb4-9">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> level.r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> torch.clamp(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> x, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-8</span>)</span>
<span id="cb4-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x</span>
<span id="cb4-11"></span>
<span id="cb4-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> p_min_composed(top, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>sub_levels):</span>
<span id="cb4-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Top-level p_min accounting for hierarchy composition."""</span></span>
<span id="cb4-14">    r_eff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> r_eff_from_levels(top, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>sub_levels)</span>
<span id="cb4-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> top.B <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> r_eff</span></code></pre></div>
</div>
<div id="a36a971b" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Two-level hierarchy: autocratic sub-leaders</span></span>
<span id="cb5-2">top <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SelectorateModel(W<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>, S<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>, B<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>)</span>
<span id="cb5-3">sub_auto <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SelectorateModel(W<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>, S<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>)</span>
<span id="cb5-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"kappa = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sub_auto<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>kappa()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)                     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 0.900</span></span>
<span id="cb5-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"p_min = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p_min_composed(top, sub_auto)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 11.111</span></span>
<span id="cb5-6"></span>
<span id="cb5-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Democratic sub-leaders</span></span>
<span id="cb5-8">sub_dem <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SelectorateModel(W<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">80.0</span>, S<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>)</span>
<span id="cb5-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"kappa = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sub_dem<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>kappa()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)                       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 0.200</span></span>
<span id="cb5-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"p_min = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p_min_composed(top, sub_dem)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 50.000</span></span>
<span id="cb5-11"></span>
<span id="cb5-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Gradient: how does sub-level democratization affect top cost?</span></span>
<span id="cb5-13">p_min_composed(top, sub_auto).backward()</span>
<span id="cb5-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"dp/dW2 = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>sub_auto<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>W<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>grad<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
</div>
<p>Because everything is differentiable, we can compute how sensitive the top-level equilibrium is to sub-level institutional changes. The gradient <img src="https://latex.codecogs.com/png.latex?%5Cpartial%20p%5E%7B%5Cmin%7D_1%20/%20%5Cpartial%20W_2"> tells us how much expanding the sub-level coalition costs the top leader.</p>
</section>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>I used Claude to help draft, revise, and edit this essay. Claude wrote the caveats section and the code. I did ideation, and also made significant edits, reviews, and revisions to the text.</p>
</section>
<section id="novelty" class="level1">
<h1>Novelty</h1>
<p>The channel attenuation framing, the compression of uniform-transfer subset channels to three coefficients (<img src="https://latex.codecogs.com/png.latex?1/W">, <img src="https://latex.codecogs.com/png.latex?1/S">, <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N">), the continued-fraction recursion <img src="https://latex.codecogs.com/png.latex?r_k%5E%7B%5Ctext%7Beff%7D%7D%20=%20r_k/(1-r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D)">, the <img src="https://latex.codecogs.com/png.latex?PGL(2)"> representation of hierarchy composition via <img src="https://latex.codecogs.com/png.latex?M_k%20=%20%5Cbigl(%5Cbegin%7Bsmallmatrix%7D%200%20&amp;%20r_k%20%5C%5C%20-1%20&amp;%201%20%5Cend%7Bsmallmatrix%7D%5Cbigr)">, and the renormalization interpretation are, to the author’s knowledge, novel observations that do not appear in the original selectorate theory literature. The differential attenuation insight (targeted channels degrade through hierarchy while universal channels do not) is a modeling assumption that generates the depth-selects-channel-switching result. The <img src="https://latex.codecogs.com/png.latex?PGL(n+1)"> generalization to multi-channel composition is conjectured but not derived here.</p>
<!-- 

Possible Graveyard

# Intellectual Ancestry

The selectorate model synthesizes several traditions in game theory and political economy.

**Minimal winning coalitions.** William Riker's [*The Theory of Political Coalitions*](https://www.amazon.com/Theory-Political-Coalitions-William-Riker/dp/0300010494){.external target="_blank"} (1962) introduced the **size principle**: in zero-sum games, rational players form the smallest coalition sufficient to win. A larger coalition means more people to split the spoils with. The selectorate model's Rule 1 ("keep $W$ small") is a direct descendant. The key extension is that Bueno de Mesquita et al. embed this in a non-cooperative game with explicit payoffs and defection possibilities, rather than relying on cooperative game theory's solution concepts[^4].

[^4]: Riker worked primarily in cooperative game theory, reasoning about which coalitions are stable via core, bargaining set, and related concepts. The selectorate model reformulates the same intuition in a non-cooperative framework where coalition members individually choose loyalty or defection. This is related to the broader question of how [equilibrium concepts](../../game_theory/posts/gradient_learning_nash/index.qmd) structure multi-agent behavior.

**Rent-sharing and patronage.** The leader's problem, distributing rents to maintain a support coalition, is a standard principal-agent setup. Each agent decides loyalty or defection based on their expected share. The equilibrium condition (the leader sets transfers so the coalition is just indifferent to defecting) is a textbook result in repeated rent-distribution games and appears throughout the literature on [clientelism](https://en.wikipedia.org/wiki/Clientelism){.external target="_blank"} and patronage politics.

**Credible commitment and the folk theorem.** The challenger's ability to attract defectors by promising higher payoffs raises a credibility problem: why should a coalition member believe the challenger's promises? In the selectorate model, the answer is that they *discount* the promise by the inclusion probability $W/S$. In a fully dynamic version, the leader's survival is sustained by a folk-theorem-style equilibrium where coalition members cooperate because the present value of continued loyalty exceeds the expected value of defection[^5].

[^5]: In the infinite-horizon version with discount factor $\delta$, the loyalty constraint becomes easier to satisfy as $\delta$ increases, because coalition members weigh future private-goods flows more heavily. The qualitative result (small $W$ favors private goods, large $W$ favors public goods) is unchanged. See Chapter 3 of *The Logic of Political Survival* for the full dynamic treatment.

**Samuelsonian public goods.** The split between private goods ($\alpha B$ divided among $W$) and public goods ($(1-\alpha)B$ benefiting all of $S$) is lifted directly from the [Samuelsonian theory of public goods](https://en.wikipedia.org/wiki/Public_good_(economics)){.external target="_blank"}. The selectorate model grafts this distinction onto a survival game: the leader's choice of $\alpha$ is a choice along the private-public spectrum, and the equilibrium $\alpha$ is determined by the coalition structure[^weingast].

[^weingast]: This is also related to Weingast's work on "particularism vs. universalism" in legislative politics: the tension between targeted pork-barrel spending (private goods for a district) and broad national programs (public goods for all).

**Authoritarian bargaining.** The selectorate model is not the only game-theoretic treatment of dictatorship. Ronald Wintrobe's [*The Political Economy of Dictatorship*](https://www.cambridge.org/core/books/political-economy-of-dictatorship/E88B2C07C68F3C26EC2BE6E22F9E6D6F){.external target="_blank"} (1998) models the "dictator's dilemma" as a trust-repression game. Milan Svolik's [*The Politics of Authoritarian Rule*](https://www.cambridge.org/core/books/politics-of-authoritarian-rule/C1C3B3C2A16BBD11984D253D79929EB9){.external target="_blank"} (2012) uses dynamic games to analyze power-sharing between the ruler and ruling elites. Both address dimensions (information, repression, intra-elite bargaining) that the toy model above abstracts away. -->


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>The full model includes additional complexities such as multiple rounds, discounting, and the possibility of the challenger being a former coalition member. For now, we’ll focus on the static version to learn about the core insights.↩︎</p></li>
<li id="fn2"><p>This is a key modeling assumption and an upper bound. The challenger has no particular loyalty to those who helped them seize power and can pick any <img src="https://latex.codecogs.com/png.latex?W_r"> out of <img src="https://latex.codecogs.com/png.latex?S">. Note that <img src="https://latex.codecogs.com/png.latex?W_r"> doesn’t necessarily equal the incumbent’s <img src="https://latex.codecogs.com/png.latex?W_n">; the challenger can form a coalition of a different size. Crucially, the standard BdM model does not explicitly model punishment of the defectors for <em>failed</em> defection. The entire cost of defection comes from the inclusion lottery. In reality, failed defectors in autocracies are purged, imprisoned, or killed, which would introduce a deposition probability and a punishment payoff into the constraint.↩︎</p></li>
<li id="fn3"><p>Using <img src="https://latex.codecogs.com/png.latex?W_r"> makes this technically the maximum payout for the defector. This is desired as it makes the game “adversarial” for the leader. As we see, the size of challenger’s coalition ends up not affecting the expected payoff. This is a consequence of uniformity assumption we made. If inclusion were non-uniform (e.g., the challenger preferentially recruits defectors), <img src="https://latex.codecogs.com/png.latex?W_r"> would matter and the bound would tighten for the leader. This also creates incentives for the leader to equally distribute private goods among coalition members, to minimize the chance of a “weak link” spoiling their coalition.↩︎</p></li>
<li id="fn4"><p>The linear specification produces corner solutions: both the challenger and incumbent solve linear programs over the spending simplex and pick corners. The challenger’s best-response switch at <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N%20=%201/S"> exists without concavity, but the leader always uses one channel or the other, never a mix. Concave <img src="https://latex.codecogs.com/png.latex?v"> (e.g., <img src="https://latex.codecogs.com/png.latex?v%20=%20%5Cbeta%20g%5E%5Cgamma%20/%20N"> with <img src="https://latex.codecogs.com/png.latex?%5Cgamma%20%3C%201">) produces interior solutions with <img src="https://latex.codecogs.com/png.latex?g%5E*%20%3E%200"> increasing smoothly in <img src="https://latex.codecogs.com/png.latex?W">, and operates across the full parameter range. This is the classic BdM result. The original <em>Logic of Political Survival</em> handles the general case.↩︎</p></li>
<li id="fn5"><p>In BdM, the result is a bit more realistic due to concavity in <img src="https://latex.codecogs.com/png.latex?v">. This produces interior solutions where the leader provides a mix of public and private goods. With concave <img src="https://latex.codecogs.com/png.latex?v">, the optimum satisfies <img src="https://latex.codecogs.com/png.latex?v'(g%5E*)%20=%201/W">: the marginal loyalty per dollar must equalize across channels. As <img src="https://latex.codecogs.com/png.latex?W"> grows, <img src="https://latex.codecogs.com/png.latex?1/W"> shrinks, so <img src="https://latex.codecogs.com/png.latex?g%5E*"> increases smoothly. Concavity can also generate public goods provision outside the democratic region, since the high marginal return at low <img src="https://latex.codecogs.com/png.latex?g"> can justify some public spending even when <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N%20%3C%201/S">. The linear model captures the regime switch but misses this interior structure. For us, the key point is that the challenger and incumbent optimize across channels, and the selectorate geometry compresses to three coefficients: <img src="https://latex.codecogs.com/png.latex?1/W">, <img src="https://latex.codecogs.com/png.latex?1/S">, and <img src="https://latex.codecogs.com/png.latex?%5Cbeta/N">.↩︎</p></li>
<li id="fn6"><p>We could generalize even further to allow for arbitrary inclusion probabilities, per-member payoffs, multiple types of currencies, etc., but the uniform-inclusion assumption suffices to show the compression result.↩︎</p></li>
<li id="fn7"><p>This is the cleanest case, but you can imagine parallel hierarchies (overlapping sub-selectorates, matrix organizations) or cross-level externalities that break the modular structure. Corporate conglomerates and federal systems with concurrent jurisdiction are examples where the parallel case matters.↩︎</p></li>
<li id="fn8"><p>The same typology applies to firms. Map <img src="https://latex.codecogs.com/png.latex?W"> to key employees whose departure threatens the firm, <img src="https://latex.codecogs.com/png.latex?S"> to the labor market, <img src="https://latex.codecogs.com/png.latex?B"> to the compensation budget, <img src="https://latex.codecogs.com/png.latex?p"> to targeted retention (bonuses, equity grants), and defection to leaving for a competitor. Startups are flat autocracies (founder and a few key people, targeted equity, “founder-mode”). Partnerships and cooperatives are flat democracies (broad profit-sharing). Conglomerates with deep management layers and patronage-heavy compensation (GE under Welch) occupy the fragile quadrant. Large tech companies with broad equity compensation occupy the viable one. The hierarchy tax predicts that middle managers consume transfers before passing them down, attenuating the targeted channel, which is why deep corporate hierarchies either move toward broad compensation or suffer talent drain at the bottom.↩︎</p></li>
<li id="fn9"><p>The same typology applies to firms. Map <img src="https://latex.codecogs.com/png.latex?W"> to key employees whose departure threatens the firm, <img src="https://latex.codecogs.com/png.latex?S"> to the labor market, <img src="https://latex.codecogs.com/png.latex?B"> to the compensation budget, <img src="https://latex.codecogs.com/png.latex?p"> to targeted retention (bonuses, equity grants), and defection to leaving for a competitor. Startups are flat autocracies (founder and a few key people, targeted equity, “founder-mode”). Partnerships and cooperatives are flat democracies (broad profit-sharing). Conglomerates with deep management layers and patronage-heavy compensation (GE under Welch) occupy the fragile quadrant. Large tech companies with broad equity compensation occupy the viable one. The hierarchy tax predicts that middle managers consume transfers before passing them down, attenuating the targeted channel, which is why deep corporate hierarchies either move toward broad compensation or suffer talent drain at the bottom.↩︎</p></li>
<li id="fn10"><p>This connects to a broader research programme I’ve been thinking about on coalition formation as information compression. The general claim is that whenever maintaining cooperation is a control problem under uncertainty, viable coalitions are those that achieve the target cooperative outcome with minimal information rate, but that’s beyond the scope of this note.↩︎</p></li>
<li id="fn11"><p>Bueno de Mesquita and Smith’s “Political Survival and Endogenous Institutional Change” (2005) makes some progress on this, modeling institutional change as an equilibrium outcome. But the endogenous-institutions version is considerably more complex and less clean than the baseline model.↩︎</p></li>
<li id="fn12"><p>The matrix representation makes the algebraic structure explicit. Each level contributes <img src="https://latex.codecogs.com/png.latex?M_k%20%5Cin%20PGL(2)">; the total composition is <img src="https://latex.codecogs.com/png.latex?%5Cprod%20M_k">; the viability boundary is <img src="https://latex.codecogs.com/png.latex?r%5E%7B%5Ctext%7Beff%7D%7D%20%5Cleq%201">; and the pole at <img src="https://latex.codecogs.com/png.latex?r_%7Bk+1%7D%5E%7B%5Ctext%7Beff%7D%7D%20=%201"> is a fixed point. Population scaling <img src="https://latex.codecogs.com/png.latex?(W,%20S)%20%5Cto%20(%5Clambda%20W,%20%5Clambda%20S)"> acts trivially on <img src="https://latex.codecogs.com/png.latex?r%20=%20W/S">, confirming that only the projective coordinate matters.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Governance</category>
  <category>Research</category>
  <guid>https://demonstrandom.com/governance/posts/game_theory_dictatorships_selectorate/</guid>
  <pubDate>Mon, 16 Feb 2026 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/governance/posts/game_theory_dictatorships_selectorate/eclipse_of_the_sun_grosz.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Do We See the Same Colors?</title>
  <link>https://demonstrandom.com/theory_of_mind/posts/color_qualia_riemannian/</link>
  <description><![CDATA[ 





<p><a href="https://en.wikipedia.org/wiki/Homage_to_the_Square"><img src="https://demonstrandom.com/theory_of_mind/posts/color_qualia_riemannian/homage_to_square_albers.jpg" class="img-fluid" style="width:55.0%" alt="Josef Albers. *Homage to the Square*. (1950-1976)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<blockquote class="blockquote">
<p>Neither would it carry any Imputation of Falshood to our simple Ideas, if by the different Structure of our Organs, it were so ordered, That the same Object should produce in several Men’s Minds different Ideas at the same time; v.g. if the Idea, that a Violet produced in one Man’s Mind by his Eyes, were the same that a Marigold produced in another Man’s, and vice versa. - John Locke, <em>Essay Concerning Human Understanding</em> (1690)</p>
</blockquote>
<p>What if your “red” is my “blue”?</p>
<p>The “inverted spectrum” thought experiment is an old favorite among philosophers of mind, cognitive scientists and undergraduates souped up on cannabis. The concept is simple: maybe the colors you see are systematically switched around relative to the colors I see. That is, your internal experience of “red” is my internal experience of “blue”. When I see a ripe tomato, it’s the color you call “blue”, and vice versa. But because we all call the sky “blue” and refer to tomatoes as “red”, our naming systems are equally permuted, so no one can tell the difference.</p>
<p>This is a canonical example for the “hard problem of consciousness”. Since there’s a gap between the physical process the eyes and optic nerve use to process color, and the subjective experience of color perceived within a consciousness, it’s considered impossible to run and experiment that resolves the inverted spectrum argument.</p>
<p>This post makes a mathematical argument (based on the geometry of color space) that there is no inverted spectrum, and that there is a set of experiments we can run to determine whether the argument holds water.</p>
</section>
<section id="the-thought-experiment" class="level1">
<h1>The Thought Experiment</h1>
<p>The “functionalist” view holds that mental states are defined by their functional roles. If two people have functionally identical behaviors, then they have the same mental states. The “qualia realist” view holds that mental states have intrinsic qualitative properties, and that the internal qualia of experience goes beyond functional roles<sup>1</sup>. In theory, two functionally identical systems could differ in their qualia.</p>
<p>The inverted spectrum requires a systematic remapping of colors such that every perceptual relationship is preserved. If even one relationship breaks (the difference between those two colors used to look the same and now it doesn’t) then the inversion is detectable, and the thought experiment fails.</p>
<p>We’ll assume that the functional role of a color experience is fully captured by its position in the subject’s perceptual similarity structure. That is, the complete pattern of “how different does this color look from every other color?” If that’s right, then preserving all perceptual relationships means preserving functional role.</p>
<p>So the inverted spectrum reduces to a precise mathematical question: does there exist a non-trivial remapping of color space that preserves all perceptual relationships? If yes, functionalism is in trouble. If it is not possible, the case for qualia as something over and above functional structure is weakened.</p>
</section>
<section id="the-shape-of-color" class="level1">
<h1>The Shape of Color</h1>
<section id="color-wheel" class="level2">
<h2 class="anchored" data-anchor-id="color-wheel">Color Wheel</h2>
<p>The question of “how different do two colors look?” is an empirical science.</p>
<p>In color science, the basic unit of measurement is the just-noticeable difference (JND), which is the smallest change in a stimulus that a subject can reliably detect. This is typically measured by taking a color patch and slowly changing it’s wavelength, structure, or brightness until the subject notices. JNDs define a measurable notion of distance in color space.</p>
<p>Given some notion of measurement, what does “remapping colors” mean precisely? Imagine a function <img src="https://latex.codecogs.com/png.latex?%5Cphi"> that sends each color to a different color. The inverted spectrum claims there exists a non-trivial <img src="https://latex.codecogs.com/png.latex?%5Cphi"> that preserves all pairwise perceptual distances.</p>
<p>A simple model of colors is the color wheel. On the color wheel, each color is represented as a point on a circle. The distance between colors is the angle between them.</p>
<p>The color wheel has a few natural candidate automorphisms: rotations, reflections, and complement maps.</p>
<div id="fig-color-wheel-permutations" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-color-wheel-permutations-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://demonstrandom.com/theory_of_mind/posts/color_qualia_riemannian/color_wheel_permutations.png" class="img-fluid figure-img" style="width:85.0%">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-color-wheel-permutations-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Color-wheel permutations: identity, rotation, reflection, and complement map.
</figcaption>
</figure>
</div>
<p>If the color wheel were the whole story, then these automorphisms would work. Rotations and reflections are isometries of the circle. The inverted spectrum would be trivially possible, and pure functionalism would be in trouble.</p>
<p>But the color wheel is a cartoon model of human color perception. Does the empirical distance function on color space have any symmetries?</p>
</section>
<section id="chromaticity-diagrams" class="level2">
<h2 class="anchored" data-anchor-id="chromaticity-diagrams">Chromaticity Diagrams</h2>
<p>The CIE (Commission Internationale de l’Eclairage) chromaticity diagram, introduced in 1931, maps the visible colors onto a two-dimensional space<sup>2</sup>. But the Euclidean distances in this diagram do <em>not</em> correspond to perceptual distances. Two colors that look wildly different might be close together in the diagram, and two that look similar might be far apart.</p>
<div id="fig-cie-chromaticity" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-cie-chromaticity-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://demonstrandom.com/theory_of_mind/posts/color_qualia_riemannian/cie_chromaticity.png" class="img-fluid figure-img" style="width:65.0%">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-cie-chromaticity-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: CIE 1931 Chromaticity Diagram. Euclidean distances in this space do not correspond to perceptual distances.
</figcaption>
</figure>
</div>
<p>The mismatch between coordinate distance and perceptual distance means the metric changes from place to place. In 1942, David MacAdam measured this directly<sup>3</sup>. At various points in the CIE diagram, he tested subject’s ability to distinguish a color from a central color. Near green, the subjects were bad at discriminating, but near blue-violet, the subjects were very sensitive. The equivalent regions form ellipses of varying size, shape, and orientation across the diagram. These are the MacAdam ellipses.</p>
<div id="fig-macadam" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-macadam-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://demonstrandom.com/theory_of_mind/posts/color_qualia_riemannian/macadam_ellipses.png" class="img-fluid figure-img" style="width:70.0%">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-macadam-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: MacAdam ellipses (shown at 10x actual size) on the CIE 1931 chromaticity diagram. The ellipses vary in size, shape, and orientation — the metric is non-uniform.
</figcaption>
</figure>
</div>
<p>Subsequently, the CIE has released a series of increasingly sophisticated color difference formulas over the decades: CIELAB (1976), CIE94 (1994), and CIEDE2000 (2000)<sup>4</sup>. Each represents an improved approximation to the true perceptual metric, incorporating additional empirical data about how humans discriminate colors under various conditions. All of them confirm and refine MacAdam’s basic finding: the perceptual metric on color space is non-uniform and varies from region to region.</p>
<div id="fig-ciede2000" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-ciede2000-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://demonstrandom.com/theory_of_mind/posts/color_qualia_riemannian/ciede2000_ellipses.png" class="img-fluid figure-img" style="width:70.0%">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-ciede2000-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: CIEDE2000 discrimination ellipses (approximate, projected onto CIE xy). More uniform than MacAdam’s original measurements, but still position-dependent. The metric has no global symmetry.
</figcaption>
</figure>
</div>
<p>So we can think of color as a Riemannian manifold. The metric tensor encodes JND structure at each point, with large eigenvalues where discriminability is fine and small eigenvalues where discriminability is coarse. The MacAdam ellipses directly determine <img src="https://latex.codecogs.com/png.latex?g_%7Bij%7D"> at each point, as the ellipse of just-noticeable differences is the unit ball of the local metric<sup>5</sup>.</p>
<p>A spectrum inversion that preserves all perceptual distances is an isometry <img src="https://latex.codecogs.com/png.latex?%5Cphi:%20M%20%5Cto%20M"> with <img src="https://latex.codecogs.com/png.latex?%5Cphi%5E*%20g%20=%20g">. But it’s also true that<sup>6</sup> for a generic Riemannian metric on a manifold of dimension <img src="https://latex.codecogs.com/png.latex?%5Cgeq%202">, the only isometry is the identity.</p>
<p><strong>Theorem.</strong> <em>Let <img src="https://latex.codecogs.com/png.latex?M"> be a smooth manifold of dimension <img src="https://latex.codecogs.com/png.latex?n%20%5Cgeq%202">. The set of Riemannian metrics on <img src="https://latex.codecogs.com/png.latex?M"> whose isometry group is trivial (i.e., <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BIsom%7D(M,%20g)%20=%20%5C%7Be%5C%7D">, consisting of only the identity) is generic: it is a residual set (countable intersection of open dense sets) in the space of all smooth metrics on <img src="https://latex.codecogs.com/png.latex?M">, equipped with the <img src="https://latex.codecogs.com/png.latex?C%5E%5Cinfty"> topology.</em></p>
<p>In plain language: if you pick a Riemannian metric “at random”<sup>7</sup> from the space of all possible metrics, it will almost certainly have <em>no non-trivial isometries</em>. The only distance-preserving map from the space to itself will be the map that sends every point to itself.</p>
<p>In the Appendix, we provide computational evidence for this by fitting metric tensors from MacAdam’s ellipse data and searching for Killing vector fields (generators of continuous symmetries). No non-trivial solutions are found, consistent with a trivial continuous isometry group.</p>
</section>
</section>
<section id="the-philosophical-payoff" class="level1">
<h1>The Philosophical Payoff</h1>
<p>What does this mean for the debate between functionalism and qualia realism?</p>
<p>The Riemannian argument shows that the inverted spectrum is not possible. The relational structure is rich enough to pin down the identity of each color up to the trivial isometry. There is no room for a non-trivial automorphism. If functional role includes the full discriminability structure, then two people who share the same color metric have the same color experiences. At least for color, the inverted spectrum is ruled out, and the case for functionalism over qualia realism is strengthened.</p>
<p>This is also evidence for structuralism. If the relational structure of color space has no non-trivial automorphisms, then there is no sense in which two colors could be “swapped” while preserving all the perceptual relations. In some sense, the “what it’s like” of red may be defined by its position in the web of perceptual relations<sup>8</sup>.</p>
</section>
<section id="individual-variation" class="level1">
<h1>Individual Variation</h1>
<p>The computation above addresses <em>within-subject</em> symmetry. Given one person’s color metric, can that person’s own color space be nontrivially remapped onto itself? But the assumption that “any two people [have] the same perceptual distance function” is doing a lot of work. The <em>between-subject</em> question is different. If two people have different JND structures (different metrics), can we still compare their color experiences?</p>
<p>Humans are not all alike. To start, trichromats, dichromats (i.e.&nbsp;color blind individuals), anomalous trichromats, and tetrachromats (people with four types of cone cells) all have different color spaces with different metrics. Secondly, the argument does not directly say that two different people must have the <em>same</em> color experiences. Two different people have two different manifolds with two different metrics. Comparing across individuals requires more than isometry theory: it requires some way to identify corresponding points across different metric spaces.</p>
<p>At a biological level, people have differing cone distributions, cone spectral sensitivities, lens and macular filtering, and different rod contributions in low-light regimes. Those differences imply slightly different empirical metrics. So the right conclusion is not “everyone sees exactly the same colors” but that (among people in the same phenotpyical category) colors differ by <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">-level distortion.</p>
<p>In short: we probably do see <em>slightly</em> different colors.</p>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>The inverted spectrum thought experiment asks: could two people have systematically different color experiences while being functionally identical? The traditional assumption is that this question is permanently open.</p>
<p>But color space is an empirical object with measurable geometry. The MacAdam ellipses show that this geometry is non-uniform and position-dependent, and a generic metric with these properties has no non-trivial isometries. If the color metric is generic (and the data strongly suggests it is) then there is no way to remap colors while preserving all perceptual distances.</p>
<p>We probably see close to, but not exactly, the same colors.</p>
</section>
<section id="caveats-and-extensions" class="level1">
<h1>Caveats and Extensions</h1>
<section id="is-the-color-metric-actually-generic" class="level2">
<h2 class="anchored" data-anchor-id="is-the-color-metric-actually-generic">Is the Color Metric Actually Generic?</h2>
<p>The theorem says a <em>generic</em> metric has no non-trivial isometries. But “generic” is a topological claim about the space of all metrics. The empirical question is whether the <em>actual</em> color metric, the one determined by MacAdam ellipses and CIE formulas, is in this generic set.</p>
<p>The empirical evidence is strongly suggestive. The MacAdam ellipses vary substantially and irregularly across the chromaticity diagram. There is no obvious axis of symmetry, no rotational invariance, no discrete symmetry group that jumps out of the data. But “strongly suggestive” is not a proof. In the Appendix, we search for Killing vector fields (generators of continuous symmetries) by fitting metric tensors from MacAdam’s ellipse data. No non-trivial solutions are found within a polynomial ansatz, which is suggestive but not conclusive, since the search is restricted to a finite-dimensional function class, and Killing fields only rule out continuous symmetries, not discrete ones.</p>
</section>
<section id="approximate-isometries" class="level2">
<h2 class="anchored" data-anchor-id="approximate-isometries">Approximate Isometries</h2>
<p>Perhaps the strongest objection: what if the inverted spectrum does not need to be <em>exact</em>? What if an approximate inversion is enough?</p>
<p>Define an <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">-isometry as a diffeomorphism <img src="https://latex.codecogs.com/png.latex?%5Cphi:%20M%20%5Cto%20M"> such that:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Csup_%7Bx%20%5Cin%20M%7D%20%5C%7C%20%5Cphi%5E*g(x)%20-%20g(x)%20%5C%7C%20%3C%20%5Cepsilon%0A"></p>
<p>For small <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">, this is a map that <em>almost</em> preserves all perceptual distances. Could such a map exist even when exact isometries do not?</p>
<p>Possibly. If there exists a map that permutes colors while distorting each JND distance by a small amount, it might be the case that the distortion is below the threshold of detectability. This would give a “fuzzy” inverted spectrum. The inversion would be imperfect close enough to be undetectable in practice.</p>
<p>Whether such approximate isometries exist for the empirical color metric is an open question. It depends on how “close” the metric is to one with symmetry. One might study this via the space of Killing-like vector fields that satisfy <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Cmathcal%7BL%7D_X%20g%5C%7C%20%3C%20%5Cepsilon"> for small <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">, an area sometimes called “approximate symmetry” in the physics literature.</p>
<p>If approximate isometries exist, the philosophical upshot is more subtle, and the debate would shift from “is inversion possible?” to “how much perceptual distortion is compatible with behavioral indistinguishability?”</p>
</section>
<section id="does-jnd-capture-everything" class="level2">
<h2 class="anchored" data-anchor-id="does-jnd-capture-everything">Does JND Capture Everything?</h2>
<p>The argument assumes that JNDs capture <em>all</em> perceptually relevant structure. But color experience might have structure that is not captured by pairwise discriminability. For example, there could be higher-order relations, temporal dynamics, or categorical boundaries that are not captured by JNDs alone.</p>
<p>For example, the “distance” between red and green might not fully capture the fact that they are “opponent” colors in a way that red and blue are not. Color opponency creates categorical structure that may not reduce to metric distances. However, opponent processing is itself a structural property. If red-green opponency is a feature of the wiring of the visual system, it provides additional constraints that make non-trivial remappings <em>harder</em>, not easier.</p>
<p>In general, if we enrich the structure of color space beyond the Riemannian metric, the automorphism group gets smaller, not larger. So these considerations, if anything, strengthen the conclusion.</p>
</section>
<section id="is-color-space-even-riemannian" class="level2">
<h2 class="anchored" data-anchor-id="is-color-space-even-riemannian">Is Color Space Even Riemannian?</h2>
<p>Recent work by Bujack et al.&nbsp;(2022) argues that perceptual color space is not Riemannian at all<sup>9</sup>. Large color differences are perceived as less than the sum of small differences, creating a “diminishing returns” effect that violates the path-additivity required by Riemannian geometry. If that’s correct, the MacAdam-ellipse metric is only valid locally (for small differences), and the global geometry requires a different framework.</p>
<p>If anything, this also strengthens the argument. The Riemannian case is the most symmetric possibility: a smooth, well-behaved metric with clean transformation properties. A non-Riemannian structure with diminishing returns is more irregular, making non-trivial distance-preserving self-maps even harder to construct. The Killing field computation above uses only local metric data and remains valid regardless.</p>
</section>
<section id="other-sensory-modalities" class="level2">
<h2 class="anchored" data-anchor-id="other-sensory-modalities">Other Sensory Modalities</h2>
<p>Does the argument generalize?</p>
<ul>
<li><p><strong>Pitch</strong>: Pitch perception defines a metric space (JNDs for frequency discrimination). The octave structure introduces a periodicity, but the metric is non-uniform within an octave. Does pitch space have non-trivial isometries? The octave equivalence might generate a discrete isometry (translation by one octave), but this is not a “full inversion” and its existence is already part of the known structure.</p></li>
<li><p><strong>Taste and Smell</strong>: Taste and small are high-dimensional and poorly characterized metrically. The argument applies in principle, but we lack the empirical data to say whether the taste metric is generic.</p></li>
<li><p><strong>Pain</strong>: Pain has an intensity metric but unclear spatial or qualitative geometry. Same caveat.</p></li>
</ul>
</section>
<section id="the-quidditism-response" class="level2">
<h2 class="anchored" data-anchor-id="the-quidditism-response">The Quidditism Response</h2>
<p>A committed quidditist can simply deny the premise. Qualia, they might say, have non-structural properties that no relational structure can capture. Even if the metric pins down the structural identity of each color, the intrinsic <em>feel</em> could still differ.</p>
<p>This is logically consistent. But it comes at a cost. If qualia have properties that make no difference to any relational, discriminative, or behavioral fact, then those properties are by definition epiphenomenal, and have no causal powers or no detectable consequences. The quidditist is committed to the existence of properties that are undetectable.</p>
</section>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>I used Claude to help research, draft, and edit this essay, based on my notes. Claude also devised several of the caveats and extensions, which I then edited and expanded on. Claude wrote the Killing field code in the appendix, which I then verified and modified. The related work survey was produced using ChatGPT Deep Research and then edited for accuracy.</p>
</section>
<section id="appendix-computing-the-killing-fields" class="level1">
<h1>Appendix A: Computing the Killing Fields</h1>
<p>The theorem tells us that <em>generic</em> metrics have no symmetries. But is the empirical color metric generic? We can check directly by fitting a metric tensor from the MacAdam ellipse data and solving for Killing vector fields.</p>
<p>Each MacAdam ellipse defines the local metric tensor <img src="https://latex.codecogs.com/png.latex?g_%7Bij%7D"> at its center: the ellipse of just-noticeable differences is the unit ball of the local metric. An ellipse with semi-axes <img src="https://latex.codecogs.com/png.latex?a">, <img src="https://latex.codecogs.com/png.latex?b"> and orientation <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> gives:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag_%7B11%7D%20=%20%5Cfrac%7B%5Ccos%5E2%5Ctheta%7D%7Ba%5E2%7D%20+%20%5Cfrac%7B%5Csin%5E2%5Ctheta%7D%7Bb%5E2%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0Ag_%7B12%7D%20=%20%5Ccos%5Ctheta%5Csin%5Ctheta%5Cleft(%5Cfrac%7B1%7D%7Ba%5E2%7D%20-%20%5Cfrac%7B1%7D%7Bb%5E2%7D%5Cright)%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cquad%20g_%7B22%7D%20=%20%5Cfrac%7B%5Csin%5E2%5Ctheta%7D%7Ba%5E2%7D%20+%20%5Cfrac%7B%5Ccos%5E2%5Ctheta%7D%7Bb%5E2%7D%0A"></p>
<p>We interpolate between the 25 measurements to get a smooth metric field, then ask: does any smooth vector field <img src="https://latex.codecogs.com/png.latex?X"> generate a flow that preserves all distances? Such a field must satisfy the Killing equation, so the Lie derivative of the metric along <img src="https://latex.codecogs.com/png.latex?X"> vanishes:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(%5Cmathcal%7BL%7D_X%20g)_%7Bij%7D%20=%20X%5Ek%20%5Cpartial_k%20g_%7Bij%7D%20+%20g_%7Bkj%7D%20%5Cpartial_i%20X%5Ek%20+%20g_%7Bik%7D%20%5Cpartial_j%20X%5Ek%20=%200%0A"></p>
<p>In 2D this gives 3 equations at every point:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AX%5E1%20%5Cpartial_x%20g_%7B11%7D%20+%20X%5E2%20%5Cpartial_y%20g_%7B11%7D%20+%202g_%7B11%7D%5C,%5Cpartial_x%20X%5E1%20+%202g_%7B12%7D%5C,%5Cpartial_x%20X%5E2%20=%200%0A"> <img src="https://latex.codecogs.com/png.latex?%0AX%5E1%20%5Cpartial_x%20g_%7B12%7D%20+%20X%5E2%20%5Cpartial_y%20g_%7B12%7D%20+%20g_%7B12%7D%5C,%5Cpartial_x%20X%5E1%20+%20g_%7B22%7D%5C,%5Cpartial_x%20X%5E2%20+%20g_%7B11%7D%5C,%5Cpartial_y%20X%5E1%20+%20g_%7B12%7D%5C,%5Cpartial_y%20X%5E2%20=%200%0A"> <img src="https://latex.codecogs.com/png.latex?%0AX%5E1%20%5Cpartial_x%20g_%7B22%7D%20+%20X%5E2%20%5Cpartial_y%20g_%7B22%7D%20+%202g_%7B12%7D%5C,%5Cpartial_y%20X%5E1%20+%202g_%7B22%7D%5C,%5Cpartial_y%20X%5E2%20=%200%0A"></p>
<p>We parameterize <img src="https://latex.codecogs.com/png.latex?X"> as a degree-3 polynomial vector field (20 unknown coefficients), evaluate the Killing equation at 332 grid points inside the gamut (<img src="https://latex.codecogs.com/png.latex?3%20%5Ctimes%20332%20=%20996"> constraints), and stack everything into an overdetermined linear system <img src="https://latex.codecogs.com/png.latex?A%5Cmathbf%7Bc%7D%20=%200">. If a non-trivial Killing field existed within this polynomial ansatz, <img src="https://latex.codecogs.com/png.latex?A"> would have a near-zero singular value. It doesn’t, though this is evidence against continuous symmetries within a restricted function class, not a proof of their absence.</p>
<div id="6fbf145b" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Step 1: Convert MacAdam ellipses to metric tensors and interpolate</summary>
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> scipy.interpolate <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> RBFInterpolator</span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># MacAdam (1942) ellipse data, Table III. 25 ellipses at 10-step magnification.</span></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Source: Wyszecki &amp; Stiles (1982), Color Science, Table 5(5.4.1);</span></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#         digitized via the LuxPy colour science library.</span></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Format: (x, y, semi-major a, semi-minor b, angle in degrees)</span></span>
<span id="cb1-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Note: since the Killing equation is homogeneous (L_X g = 0), the 10x</span></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># scaling factor cancels and does not affect whether solutions exist.</span></span>
<span id="cb1-10">macadam_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb1-11">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.160</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.057</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0085</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0035</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">62.5</span>),</span>
<span id="cb1-12">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.187</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.118</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0220</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0055</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">77.0</span>),</span>
<span id="cb1-13">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.253</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.125</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0250</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0050</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">55.5</span>),</span>
<span id="cb1-14">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.150</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.680</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0960</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0230</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">105.0</span>),</span>
<span id="cb1-15">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.131</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.521</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0470</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0200</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">112.5</span>),</span>
<span id="cb1-16">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.212</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.550</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0580</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0230</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span>),</span>
<span id="cb1-17">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.258</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.450</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0500</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0200</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">92.0</span>),</span>
<span id="cb1-18">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.152</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.365</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0380</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0190</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">110.0</span>),</span>
<span id="cb1-19">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.280</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.385</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0400</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0150</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">75.5</span>),</span>
<span id="cb1-20">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.380</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.498</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0440</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0120</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">70.0</span>),</span>
<span id="cb1-21">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.160</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.200</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0210</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0095</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">104.0</span>),</span>
<span id="cb1-22">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.228</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.250</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0310</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0090</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">72.0</span>),</span>
<span id="cb1-23">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.305</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.323</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0230</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0090</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">58.0</span>),</span>
<span id="cb1-24">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.385</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.393</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0380</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0160</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">65.5</span>),</span>
<span id="cb1-25">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.472</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.399</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0320</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0140</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">51.0</span>),</span>
<span id="cb1-26">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.527</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.350</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0260</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0130</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">20.0</span>),</span>
<span id="cb1-27">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.475</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.300</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0290</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0110</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">28.5</span>),</span>
<span id="cb1-28">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.510</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.236</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0240</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0120</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">29.5</span>),</span>
<span id="cb1-29">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.596</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.283</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0260</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0130</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">13.0</span>),</span>
<span id="cb1-30">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.344</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.284</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0230</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0090</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60.0</span>),</span>
<span id="cb1-31">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.390</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.237</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0250</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0100</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">47.0</span>),</span>
<span id="cb1-32">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.441</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.198</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0280</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0095</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">34.5</span>),</span>
<span id="cb1-33">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.278</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.223</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0240</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0055</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">57.5</span>),</span>
<span id="cb1-34">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.300</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.163</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0290</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0060</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">54.0</span>),</span>
<span id="cb1-35">    (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.365</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.153</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0360</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0095</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">40.0</span>),</span>
<span id="cb1-36">]</span>
<span id="cb1-37"></span>
<span id="cb1-38"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Convert each ellipse to metric tensor components</span></span>
<span id="cb1-39">points, g11_vals, g12_vals, g22_vals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [], [], [], []</span>
<span id="cb1-40"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (cx, cy, a, b, angle_deg) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> macadam_data:</span>
<span id="cb1-41">    theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.radians(angle_deg)</span>
<span id="cb1-42">    c, s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.cos(theta), np.sin(theta)</span>
<span id="cb1-43">    g11 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>a)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>b)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb1-44">    g12 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb1-45">    g22 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>a)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>b)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb1-46">    points.append([cx, cy])</span>
<span id="cb1-47">    g11_vals.append(g11)</span>
<span id="cb1-48">    g12_vals.append(g12)</span>
<span id="cb1-49">    g22_vals.append(g22)</span>
<span id="cb1-50"></span>
<span id="cb1-51">points <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(points)</span>
<span id="cb1-52"></span>
<span id="cb1-53"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Interpolate each component using thin-plate splines</span></span>
<span id="cb1-54">interp_g11 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> RBFInterpolator(points, g11_vals, kernel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'thin_plate_spline'</span>, smoothing<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb1-55">interp_g12 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> RBFInterpolator(points, g12_vals, kernel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'thin_plate_spline'</span>, smoothing<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb1-56">interp_g22 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> RBFInterpolator(points, g22_vals, kernel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'thin_plate_spline'</span>, smoothing<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb1-57"></span>
<span id="cb1-58"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_metric(x, y):</span>
<span id="cb1-59">    pt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([[x, y]])</span>
<span id="cb1-60">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.array([[interp_g11(pt)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], interp_g12(pt)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]],</span>
<span id="cb1-61">                     [interp_g12(pt)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], interp_g22(pt)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]]])</span></code></pre></div>
</details>
</div>
<div id="5d6ac581" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Step 2: Build and solve the Killing equation system</summary>
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> matplotlib.path <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Path</span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># CIE 1931 spectral locus (for determining which points are inside the gamut)</span></span>
<span id="cb2-4">wl_x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1741</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1740</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1714</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1644</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1566</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1440</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1241</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0913</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0633</span>,</span>
<span id="cb2-5">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0235</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0082</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0139</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0743</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1547</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2296</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2950</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3616</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4294</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5028</span>,</span>
<span id="cb2-6">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5706</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6256</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6658</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6915</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7079</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7190</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7260</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7300</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7320</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7334</span>,</span>
<span id="cb2-7">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7344</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7347</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7347</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7347</span>])</span>
<span id="cb2-8">wl_y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0050</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0050</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0065</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0109</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0177</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0297</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0578</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1327</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2650</span>,</span>
<span id="cb2-9">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4073</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5384</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6548</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7243</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7514</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7543</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7449</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7300</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7106</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6858</span>,</span>
<span id="cb2-10">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6562</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6229</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5858</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5475</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5123</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4813</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4562</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4353</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4188</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4044</span>,</span>
<span id="cb2-11">    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3935</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3872</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3848</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3830</span>])</span>
<span id="cb2-12">locus_path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Path(np.column_stack([np.append(wl_x, wl_x[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]), np.append(wl_y, wl_y[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])]))</span>
<span id="cb2-13"></span>
<span id="cb2-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build grid inside the gamut</span></span>
<span id="cb2-15">grid_pts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-16"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> xi <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.65</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>):</span>
<span id="cb2-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> yi <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> np.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.08</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.65</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>):</span>
<span id="cb2-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> locus_path.contains_point((xi, yi)):</span>
<span id="cb2-19">            grid_pts.append((xi, yi))</span>
<span id="cb2-20">grid_pts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(grid_pts)</span>
<span id="cb2-21"></span>
<span id="cb2-22"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> poly_basis(x, y, degree<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb2-23">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Monomials up to given degree: 1, x, y, x^2, xy, y^2, ..."""</span></span>
<span id="cb2-24">    basis <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(degree <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb2-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(degree <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> i):</span>
<span id="cb2-27">            basis.append(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>j)</span>
<span id="cb2-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.array(basis)</span>
<span id="cb2-29"></span>
<span id="cb2-30">h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.005</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># finite difference step</span></span>
<span id="cb2-31">deg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb2-32">n_basis <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(poly_basis(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, deg))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># = 10</span></span>
<span id="cb2-33">n_params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n_basis                 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># = 20</span></span>
<span id="cb2-34"></span>
<span id="cb2-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Assemble the constraint matrix: 3 Killing equations per grid point</span></span>
<span id="cb2-36">rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-37"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (px, py) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> grid_pts:</span>
<span id="cb2-38">    g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_metric(px, py)</span>
<span id="cb2-39">    dg_dx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (get_metric(px<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>h, py) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> get_metric(px<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>h, py)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>h)</span>
<span id="cb2-40">    dg_dy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (get_metric(px, py<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>h) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> get_metric(px, py<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>h)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>h)</span>
<span id="cb2-41"></span>
<span id="cb2-42">    phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> poly_basis(px, py, deg)</span>
<span id="cb2-43">    dphi_dx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (poly_basis(px<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>h, py, deg) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> poly_basis(px<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>h, py, deg)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>h)</span>
<span id="cb2-44">    dphi_dy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (poly_basis(px, py<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>h, deg) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> poly_basis(px, py<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>h, deg)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>h)</span>
<span id="cb2-45"></span>
<span id="cb2-46">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (L_X g)_ij = X^k dk(g_ij) + g_kj di(X^k) + g_ik dj(X^k)</span></span>
<span id="cb2-47">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)]:</span>
<span id="cb2-48">        row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros(n_params)</span>
<span id="cb2-49">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Term 1: X^k partial_k g_ij</span></span>
<span id="cb2-50">        row[:n_basis] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dg_dx[i,j]   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># X^1 contribution</span></span>
<span id="cb2-51">        row[n_basis:] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dg_dy[i,j]   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># X^2 contribution</span></span>
<span id="cb2-52">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Term 2: g_kj partial_i X^k</span></span>
<span id="cb2-53">        di <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [dphi_dx, dphi_dy][i]</span>
<span id="cb2-54">        row[:n_basis] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> di</span>
<span id="cb2-55">        row[n_basis:] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> di</span>
<span id="cb2-56">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Term 3: g_ik partial_j X^k</span></span>
<span id="cb2-57">        dj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [dphi_dx, dphi_dy][j]</span>
<span id="cb2-58">        row[:n_basis] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> g[i,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dj</span>
<span id="cb2-59">        row[n_basis:] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> g[i,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dj</span>
<span id="cb2-60">        rows.append(row)</span>
<span id="cb2-61"></span>
<span id="cb2-62">A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(rows)</span>
<span id="cb2-63"></span>
<span id="cb2-64"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Solve via SVD: any Killing field lives in the null space of A</span></span>
<span id="cb2-65">U, S, Vt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.svd(A, full_matrices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span></code></pre></div>
</details>
</div>
<section id="results" class="level2">
<h2 class="anchored" data-anchor-id="results">Results</h2>
<p>If a non-trivial Killing field existed, the matrix <img src="https://latex.codecogs.com/png.latex?A"> would have a near-zero singular value, a direction in parameter space that (approximately) satisfies all 996 constraints. Here are the singular values:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th style="text-align: center;">Index</th>
<th style="text-align: center;">Singular value</th>
<th style="text-align: center;">Ratio to <img src="https://latex.codecogs.com/png.latex?%5Csigma_%7B%5Cmax%7D"></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">0</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.85%20%5Ctimes%2010%5E6"></td>
<td style="text-align: center;">1.0000</td>
</tr>
<tr class="even">
<td style="text-align: center;">1</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?8.47%20%5Ctimes%2010%5E5"></td>
<td style="text-align: center;">0.4578</td>
</tr>
<tr class="odd">
<td style="text-align: center;">2</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?5.11%20%5Ctimes%2010%5E5"></td>
<td style="text-align: center;">0.2764</td>
</tr>
<tr class="even">
<td style="text-align: center;">3</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?3.70%20%5Ctimes%2010%5E5"></td>
<td style="text-align: center;">0.2002</td>
</tr>
<tr class="odd">
<td style="text-align: center;">4</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?2.66%20%5Ctimes%2010%5E5"></td>
<td style="text-align: center;">0.1440</td>
</tr>
<tr class="even">
<td style="text-align: center;">5</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.96%20%5Ctimes%2010%5E5"></td>
<td style="text-align: center;">0.1057</td>
</tr>
<tr class="odd">
<td style="text-align: center;">6</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.31%20%5Ctimes%2010%5E5"></td>
<td style="text-align: center;">0.0708</td>
</tr>
<tr class="even">
<td style="text-align: center;">7</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.10%20%5Ctimes%2010%5E5"></td>
<td style="text-align: center;">0.0595</td>
</tr>
<tr class="odd">
<td style="text-align: center;">8</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?9.34%20%5Ctimes%2010%5E4"></td>
<td style="text-align: center;">0.0505</td>
</tr>
<tr class="even">
<td style="text-align: center;">9</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?5.48%20%5Ctimes%2010%5E4"></td>
<td style="text-align: center;">0.0296</td>
</tr>
<tr class="odd">
<td style="text-align: center;">10</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?3.04%20%5Ctimes%2010%5E4"></td>
<td style="text-align: center;">0.0164</td>
</tr>
<tr class="even">
<td style="text-align: center;">11</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?2.42%20%5Ctimes%2010%5E4"></td>
<td style="text-align: center;">0.0131</td>
</tr>
<tr class="odd">
<td style="text-align: center;">12</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.76%20%5Ctimes%2010%5E4"></td>
<td style="text-align: center;">0.0095</td>
</tr>
<tr class="even">
<td style="text-align: center;">13</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.72%20%5Ctimes%2010%5E4"></td>
<td style="text-align: center;">0.0093</td>
</tr>
<tr class="odd">
<td style="text-align: center;">14</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.25%20%5Ctimes%2010%5E4"></td>
<td style="text-align: center;">0.0068</td>
</tr>
<tr class="even">
<td style="text-align: center;">15</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.03%20%5Ctimes%2010%5E4"></td>
<td style="text-align: center;">0.0055</td>
</tr>
<tr class="odd">
<td style="text-align: center;">16</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?5.61%20%5Ctimes%2010%5E3"></td>
<td style="text-align: center;">0.0030</td>
</tr>
<tr class="even">
<td style="text-align: center;">17</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?4.19%20%5Ctimes%2010%5E3"></td>
<td style="text-align: center;">0.0023</td>
</tr>
<tr class="odd">
<td style="text-align: center;">18</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?2.32%20%5Ctimes%2010%5E3"></td>
<td style="text-align: center;">0.0013</td>
</tr>
<tr class="even">
<td style="text-align: center;">19</td>
<td style="text-align: center;"><img src="https://latex.codecogs.com/png.latex?1.75%20%5Ctimes%2010%5E3"></td>
<td style="text-align: center;">0.0009</td>
</tr>
</tbody>
</table>
<p>The smallest singular value is <img src="https://latex.codecogs.com/png.latex?%5Csigma_%7B19%7D%20=%201.75%20%5Ctimes%2010%5E3">, with a ratio of <img src="https://latex.codecogs.com/png.latex?9.5%20%5Ctimes%2010%5E%7B-4%7D"> relative to the largest. The singular values decay smoothly with no sharp drop toward zero, which is what we would expect if no non-trivial Killing field exists within the polynomial ansatz.</p>
<p>Caveats: the absolute magnitudes of the singular values depend on coordinate scaling, ellipse conventions, and interpolation choices, so “three orders of magnitude” should not be over-interpreted. Killing fields detect only continuous symmetries; a discrete isometry (e.g., a reflection) would not appear as a Killing field. And the degree-3 polynomial parameterization, while generous (a Killing field on a 2-manifold is determined by 3 parameters), is still a restricted function class. The computation is best read as evidence consistent with the generic-metric theorem, not as an independent proof.</p>
</section>
</section>
<section id="appendix-related-work" class="level1">
<h1>Appendix B: Related Work and Novelty</h1>
<section id="related-work" class="level2">
<h2 class="anchored" data-anchor-id="related-work">Related Work</h2>
<p>The inverted spectrum is one of the most extensively discussed thought experiments in philosophy of mind. The Stanford Encyclopedia of Philosophy entry on inverted qualia surveys the landscape of structural and empirical constraints on inversion scenarios<sup>10</sup>. Philosophers have long noted that real color perception is not a simple hue circle but a structured, asymmetric quality space, and that these asymmetries undermine naive “hue rotation” models of inversion.</p>
<p>On the empirical side, perceptual color differences have been studied since MacAdam’s 1942 measurements of discrimination ellipses in CIE space<sup>11</sup>. These ellipses are widely interpreted as defining a local perceptual metric. Modern color difference formulas such as CIEDE2000 formalize this further<sup>12</sup>.</p>
<p>Several authors treat color discrimination geometry in explicitly differential-geometric terms. Gravesen (2015) models MacAdam ellipses as defining a Riemannian metric and studies coordinate constructions that approximate perceptual uniformity<sup>13</sup>. Chevallier and Farup (2018) analyze how MacAdam ellipses should be interpolated to produce a consistent metric tensor field, showing that naive interpolation can be geometrically misleading<sup>14</sup>. Bujack et al.&nbsp;(2022) argue that perceptual color space may not be globally Riemannian at all, due to violations of path additivity for large color differences<sup>15</sup>.</p>
<p>In differential geometry, the generic triviality of isometry groups for smooth Riemannian manifolds is a classical result<sup>16</sup>. Non-trivial self-symmetries of generic metrics are exceptional rather than typical.</p>
</section>
<section id="novelty" class="level2">
<h2 class="anchored" data-anchor-id="novelty">Novelty</h2>
<p>The philosophical literature contains informal asymmetry arguments against simple inverted spectrum models. The color science literature contains metric and geometric analyses of perceptual color space. I arrived at the argument in this essay independently before discovering the geometric color science literature, and to my knowledge the two threads have not been combined in the way proposed here.</p>
<p>What is distinctive here is threefold:</p>
<ol type="1">
<li><p>Formalization: Undetectable inversion is identified with a non-trivial isometry of empirical perceptual geometry. Rather than arguing from qualitative asymmetry, the condition is made mathematically precise.</p></li>
<li><p>Reduction to symmetry detection: If inversion requires a non-trivial automorphism, then the question becomes whether the empirically fitted perceptual metric admits such symmetries. The generic-metric theorem answers this in the negative for “almost all” metrics.</p></li>
<li><p>A computational test: The Killing field computation in the preceding appendix treats inversion as a concrete question about the symmetry group of a metric estimated from discrimination data.</p></li>
</ol>
<p>The underlying empirical and geometric components are established in prior work. The novelty lies in reframing the inverted spectrum debate as a problem about the symmetry structure of perceptual geometry and proposing concrete methods to evaluate it.</p>


</section>
</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>This is similar to the axiom of <a href="https://ncatlab.org/nlab/show/function+extensionality">function extensionality</a> in <a href="../../../reasoning/posts/proof_assistant_retrospective/index.html">type theory</a>, which says that two functions are equal if they give the same outputs for all inputs. The functionalist is committed to a kind of extensionality for mental states: if all inputs and outputs are the same, the states are the same.↩︎</p></li>
<li id="fn2"><p>The full color space is three-dimensional (hue, saturation, brightness), but much of the key structure can be seen in the two-dimensions, holding brightness constant.↩︎</p></li>
<li id="fn3"><p>MacAdam, D. L. (1942). “Visual Sensitivities to Color Differences in Daylight.” Journal of the Optical Society of America, 32(5), 247–274.↩︎</p></li>
<li id="fn4"><p>Sharma, G., Wu, W., and Dalal, E. N. (2005). “The CIEDE2000 color-difference formula: Implementation notes, supplementary test data, and mathematical observations.” <em>Color Research &amp; Application</em>, 30(1), 21–30.↩︎</p></li>
<li id="fn5"><p>Treating MacAdam ellipses as defining a Riemannian metric is standard in color science. See Gravesen, J. (2015), “The metric of colour space,” <em>Graphical Models</em>, 82, 77-86. Chevallier, E. and Farup, I. (2018), “Interpolation of the MacAdam Ellipses,” <em>SIAM Journal on Imaging Sciences</em>, 11(3), 1979-2000, show that naive component-wise interpolation of the metric tensor can be geometrically misleading; proper interpolation requires care about the manifold structure of the space of positive-definite matrices.↩︎</p></li>
<li id="fn6"><p>See, e.g., Kobayashi, S. (1972). <em>Transformation Groups in Differential Geometry</em>. Springer-Verlag. Also: Ebin, D. G. (1970). “The manifold of Riemannian metrics.” <em>Proceedings of Symposia in Pure Mathematics</em>, Vol. 15, AMS.↩︎</p></li>
<li id="fn7"><p>“Generic” here is used in the topological sense, not the probabilistic sense. But the intuition is similar: non-trivial isometries require the metric to satisfy special symmetry conditions, and these conditions are rare. Color space is effectively compact (bounded by the spectral locus), so the standard genericity results apply. Metrics that <em>do</em> have non-trivial isometries, like the sphere, Euclidean space, or hyperbolic space, are very special, and they have enormous amounts of symmetry. A generic metric, without special structure, has no symmetry at all.↩︎</p></li>
<li id="fn8"><p>This connects to broader structuralist positions in philosophy of science and metaphysics. See, e.g., Ladyman, J. (2014). “Structural Realism.” <em>Stanford Encyclopedia of Philosophy</em>. The idea that physical (or experiential) properties are individuated by their structural roles has a long history.↩︎</p></li>
<li id="fn9"><p>Bujack, R., Teti, E., Miller, J., Caffrey, E., and Turton, T. L. (2022). “The non-Riemannian nature of perceptual color space.” Proceedings of the National Academy of Sciences, 119(18), e2119753119.↩︎</p></li>
<li id="fn10"><p>Tye, M. (2025). “Inverted Qualia.” <em>Stanford Encyclopedia of Philosophy</em>.↩︎</p></li>
<li id="fn11"><p>MacAdam, D. L. (1942). “Visual Sensitivities to Color Differences in Daylight.” Journal of the Optical Society of America, 32(5), 247–274.↩︎</p></li>
<li id="fn12"><p>Sharma, G., Wu, W., and Dalal, E. N. (2005). “The CIEDE2000 color-difference formula: Implementation notes, supplementary test data, and mathematical observations.” <em>Color Research &amp; Application</em>, 30(1), 21–30.↩︎</p></li>
<li id="fn13"><p>Gravesen, J. (2015). “The metric of colour space.” <em>Graphical Models</em>, 82, 77-86.↩︎</p></li>
<li id="fn14"><p>Chevallier, E. and Farup, I. (2018). “Interpolation of the MacAdam Ellipses.” <em>SIAM Journal on Imaging Sciences</em>, 11(3), 1979-2000.↩︎</p></li>
<li id="fn15"><p>Bujack, R., Teti, E., Miller, J., Caffrey, E., and Turton, T. L. (2022). “The non-Riemannian nature of perceptual color space.” Proceedings of the National Academy of Sciences, 119(18), e2119753119.↩︎</p></li>
<li id="fn16"><p>See, e.g., Kobayashi, S. (1972). <em>Transformation Groups in Differential Geometry</em>. Springer-Verlag. Also: Ebin, D. G. (1970). “The manifold of Riemannian metrics.” <em>Proceedings of Symposia in Pure Mathematics</em>, Vol. 15, AMS.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Theory of Mind</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/theory_of_mind/posts/color_qualia_riemannian/</guid>
  <pubDate>Thu, 12 Feb 2026 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/theory_of_mind/posts/color_qualia_riemannian/homage_to_square_albers.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Functional Explanations of Art</title>
  <link>https://demonstrandom.com/essays/posts/functional_theories_of_art/</link>
  <description><![CDATA[ 





<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/SantaCruz-CuevaManos.jpg" class="img-fluid" style="width:50.0%"></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>What is art’s purpose in society? Why do we spend so much time and money consuming, producing, and criticizing it? What separates “good” art from “bad” art (is there even such a thing?), and why do we honor and esteem “good” artists while ridiculing the bad ones? Why do we read movie reviews or discuss movies we’ve seen on internet message boards (especially if we’ve already seen the movie)? Can a urinal be art? Can a machine make art? And why does the internet hate Nickelback?</p>
<p>Theories about art’s function tend to fall into three main categories:</p>
<ol type="1">
<li>Art is for pleasure, as it induces a “pure aesthetic experience” (not unlike a drug).</li>
<li>Art is for signaling and communication (emotional, sexual, political, or otherwise), which includes communication for social coordination and maintaining social order.</li>
<li>Art is an “ontological research program” for learning “true information” about reality.</li>
</ol>
<p>None of these ideas are new individually, but this essay argues that these three theories are actually all nested layers of a single, unified coevolutionary process.</p>
<p>Some information-communication processes coordinate groups around shared “meanings” in the short-run (or coordinate a single agent with its future self). “Good” processes are those that produce information with “useful meanings” that help a group to persist, whereas “bad” processes are those that are detrimental to group persistence. Therefore, there is selection pressure<sup>1</sup> for individuals with “taste” intuitions that track utility. In particular, the artistic process is driven by “entrepreneurial” intragroup status competitions among artists or tastemakers, who increase and decrease in status based on their ability to accurately predict the current and future consensus tastes of the group at large. On the longest timescales, experiential heuristics like pure aesthetic pleasure evolve for subconsciously recognizing the utility of information. “Art” is (extrabiological) information interacted with primarily through these taste mechanisms, rather than through direct instrumental evaluation and verification. The “fine arts” are the paradigmatic domain where taste-mediated evaluation dominates<sup>2</sup>.</p>
<p>This essay extends and references several previous essays where I explored art as a kind of “ontological research program” using techniques inspired by the <a href="../../../essays/posts/preference_oracles/index.html">Library of Babel</a>, <a href="../../../essays/posts/picture_worth_thousand_words/index.html">information theory</a>, and <a href="../../../essays/posts/cultural_saturation/index.html">statistical mechanics</a><sup>3</sup>.</p>
</section>
<section id="foundations-artifacts-and-processes" class="level1">
<h1>Foundations: Artifacts and Processes</h1>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/Baldassare_Peruzzi_Dance_of_Apollo_and_the_Muses.jpg" class="img-fluid" style="width:50.0%"> <!-- https://commons.wikimedia.org/wiki/File:Baldassare_Peruzzi_-_Dance_of_Apollo_and_the_Muses.jpg --></p>
<p>The “fine arts” as a category began to be codified in 18th-century Europe, though the exact five varied by author<sup>4</sup>. Hegel’s <em>Aesthetics</em>, for instance, proposed architecture, sculpture, painting, music, and poetry as the fundamental forms. Various scholars have since attempted to update this taxonomy, especially as new technologies began to expand the space of possible art. For example, in “Manifesto of the Seven Arts” (1911, revised 1923) Ricciotto Canudo adds “dance” as a sixth art, and “cinema” as a seventh (made possible by inventions like the Lumière brothers’ Cinématographe in 1895).</p>
<section id="artifacts" class="level2">
<h2 class="anchored" data-anchor-id="artifacts">Artifacts</h2>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/trevi_fountain.jpeg" class="img-fluid" style="width:50.0%"></p>
<p>What kinds of things count as art? Before asking what art is <em>for</em>, we need a rough inventory of what it <em>is</em>.</p>
<section id="data-types" class="level3">
<h3 class="anchored" data-anchor-id="data-types">Data Types</h3>
<p>For the purposes of this essay, I prefer a taxonomy that emphasizes art’s nature as information-bearing artifacts. In previous essays, I considered both <a href="../../../essays/posts/preference_oracles/index.html">texts</a> and <a href="../../../essays/posts/picture_worth_thousand_words/index.html">pictures</a> as information-theoretic instantiations drawn from vast possibility spaces. Extending this framework, I propose organizing art by the “data type” of its output:</p>
<ul>
<li>Literature (text, including poetry, prose, drama, stories, or any static string of symbols)</li>
<li>Visual arts (2D static fixed images, including photography, painting, digital art)</li>
<li>Sculpture (3D static objects, includes ceramics, jewelry, craft objects)</li>
<li>Architecture (modified (static) environments, includes buildings, landscapes, land art, interior design, public monuments, installation. Differs from sculpture in that the viewer is “inside” the art, rather than “outside”).</li>
<li>Music (time-based audio)</li>
<li>Film (time-based images)</li>
<li>Games (“interactive technology” or any other UX, which includes video games, board games, interactive fiction, net art, generative art, user interfaces, VR/AR experiences, software art, etc.)</li>
</ul>
<p>The names of the categories are merely suggestive. Furthermore, many common forms are really hybrids of multiple data structure types<sup>5</sup> (comics, “movies”, etc.)</p>
</section>
<section id="missing-senses" class="level3">
<h3 class="anchored" data-anchor-id="missing-senses">The Missing Senses</h3>
<p><em>Section added 3/1/2026.</em></p>
<p>I missed a few senses from the taxonomy above on my first pass through this essay (Hegel also missed these so I don’t feel too bad about it). There are entire artistic traditions built around taste, smell, and touch (and possibly more obscure senses). Consider the following:</p>
<ul>
<li>Cuisine (gustatory arts): the composition of flavor and taste. Includes gastronomy, patisserie, mixology, tea ceremony, fermentation. A chef’s tasting menu can be as deliberately structured as a symphony.</li>
<li>Perfumery (olfactory arts): the composition of scent. Includes fragrance design, incense, and aromatics. A perfumer working with base, middle, and top notes is constructing a time-based experience not unlike a musical composition<sup>6</sup>.</li>
<li>Tactilia (haptic/somatic arts): art experienced primarily through touch and the body. Includes textile design, ceramics (as tactile objects, not just visual), fashion (as worn experience, not just seen), massage, and tactile installation art.</li>
<li>Thermae (thermoceptive arts): the deliberate design of thermal experience. The Japanese onsen, the Finnish sauna, the Roman bath, the hammam, the temperature of a served dish, the spiciness of a food (capsaicin literally activates the thermoceptive TRPV1 channel). These are elaborately designed sensory experiences with strong cultural and aesthetic traditions<sup>7</sup>.</li>
</ul>
<p>And then there are the “exotic” sensory arts, increasingly speculative but not without real traditions:</p>
<ul>
<li>Interoceptia (interoceptive arts): art that acts on internal bodily sensation. Guided breathwork, guided meditation, yoga pose design, fasting protocols, psychoactive ceremony, certain endurance rituals. The “artifact” is a reproducible way to guide a body or mind into a particular state<sup>8</sup>.</li>
<li>Proprioceptia (proprioceptive/kinesthetic arts): art experienced through the body’s sense of its own position and movement. Dance is partly this (from the dancer’s side, not the audience’s). Martial arts forms, tai chi, rock climbing routes, parkour lines. The aesthetic is in how the movement <em>feels</em>, not how it looks.</li>
<li>Nociceptia (nociceptive arts): art involving pain or extreme sensation. Tattooing, scarification, piercing, BDSM aesthetics, the “runner’s high” as designed experience. Overlaps with tactilia but the primary channel is nociceptive, not haptic<sup>9</sup>.</li>
<li>Vestibulia (vestibular arts): art experienced through balance and spatial orientation. Roller coasters, swing rides, acrobatics (from the performer’s side), spinning dances (Sufi whirling). The designed experience of falling, tilting, accelerating.</li>
</ul>
<p>This list is probably not exhaustive. The boundaries between categories are blurry (is Sufi whirling vestibulia, interoceptia, or performance?), and there may be sensory dimensions I haven’t considered. The taxonomy is open.</p>
<p>Why were these left off the classical lists? These arts resist durable, scalable recording. You can’t transmit a smell over a wire. A recipe is a set of instructions (text), not the experience itself, in the same way that a musical score is not the music. But unlike music, we have no “playback device” for flavor or scent that can faithfully reproduce the original from a compressed encoding<sup>10</sup>. The arts that made it onto the classical lists are precisely those whose artifacts could survive transmission across time and space. The sensory arts are trapped in the present tense.</p>
<p>This has consequences for the theory. If art’s long-run value depends on persistence and transmission, then arts that can’t produce durable artifacts are at a structural disadvantage in the canonization process. Cuisine has no canon comparable to the Western literary or musical canon, not because food is less artful, but because individual dishes don’t survive long enough to be selected across generations. The art is remade each time from instructions. What persists is the recipe (text), the technique (embodied knowledge passed master to apprentice), and the tradition (institutional memory), but not the artifact itself.</p>
</section>
<section id="performance" class="level3">
<h3 class="anchored" data-anchor-id="performance">Performance</h3>
<p>There is one additional category we must consider, that doesn’t quite fit into the information-theoretic schema:</p>
<ul>
<li>Performance (ephemeral live performance, including dance, theatre, live music, stand-up comedy, improvisational comedy<sup>11</sup>). While performances can be documented (turning them into film, audio, or photographic artifacts), their core nature is transient.</li>
</ul>
</section>
</section>
<section id="processes" class="level2">
<h2 class="anchored" data-anchor-id="processes">Processes</h2>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/Marcel_Duchamp_1917_Fountain_photograph_by_Alfred_Stieglitz.jpg" class="img-fluid" style="width:35.0%"> <!-- Maybe swap for less vertical images --></p>
<p>The ephemeral nature of performance presents an issue for the account of art as durable, transmissible artifacts.</p>
<p>One possible resolution is to view artifacts as “frozen” performances. For example, a theatrical performance can be recorded as combination of film and sound recordings. In this framing, the Lumière brothers didn’t add a seventh art, but instead invented a new preservation technology for existing performances (like theatre and dance). In this view, art is fundamentally a human <em>process</em>: the musical performance isn’t an imperfect approximation of the score, but rather the score is an imperfect approximation of the musical performance. A sculpture is evidence of the sculptor’s chiseling. A novel is the record of the author’s careful choice of words and story beats. Even a painting involved a performance (the selection of paints and manipulation of the paintbrush) that we don’t witness.</p>
<p>This introduces some additional questions. The invention of film allows a wider range of possibilities for how to present information to an audience (for example, jump cuts are not possible in the theatre) than existed prior to its invention. But filmmaking can also be viewed as a recorded performance as well (the processes of direction, editing, acting, choosing lighting etc.). The construction of a new technology can expand the range of possible performances.</p>
<p>French sociologist Antoine Hennion described art as a collective process, where the entire network of “mediators” that transform or distort the information (bodies, instruments, scores, spaces, techniques, institutions, etc.) comprises the art. Hennion even argues that the process of art includes the act of receiving and comprehending it. Taste isn’t passive, but rather is an active skill. Part of the art is consuming it, and this skill can be trained through deliberate practice. The amateur and the connoisseur literally perceive art differently. As <a href="https://en.wikipedia.org/wiki/The_medium_is_the_message">McLuhan</a> famously said, “the medium is the message”: the entire process by which the art has been conveyed affects its meaning. The process of recording grants “scale” (allowing the art to reach a larger audience), but some aspect of the art is changed. A performance carries presence, contingency, and risk that a machine reproducible data structure does not.</p>
<p>The importance of process is even more salient in art centering on curation over creation. For example, a DJ might select from among existing music to create a playlist. No new data was actually created, but different playlists may still exhibit different “emergent” aesthetics based on the curation. Similarly, while many photographers make choices around composition and technical settings, often the objects they take pictures of already existed prior to any input from the photographer.</p>
<p>The limiting example of this phenomenon is Duchamp and his “<a href="https://en.wikipedia.org/wiki/Readymades_of_Marcel_Duchamp">Readymades</a>”, the most famous of which is his “Fountain” (pictured), a urinal signed “R. Mutt” and turned on its side. A Readymade involves minimal creation and is instead almost entirely based on curation and institutional process (even with an object as ugly, most unhygienic, and purely functional as a urinal).</p>
<p>So art involves both artifacts and processes. Processes (whether performative co-production, active taste, or institutional framing) bear art from artist to audience. Durable artifacts may form as steps in these processes, enabling scalable transmission across time and space.</p>
</section>
</section>
<section id="functions-of-art" class="level1">
<h1>Functions of Art</h1>
<p>Given that art involves both durable artifacts and ephemeral processes, we can now ask: why create art at all? Setting aside the choice of urinal itself, why was Duchamp entering anything into an art show? And why have art shows in the first place?</p>
<section id="overview-and-definition" class="level2">
<h2 class="anchored" data-anchor-id="overview-and-definition">Overview and Definition</h2>
<p>Evolution tends to eliminate costly behaviors that provide no adaptive benefit. Art’s universality and costliness suggests it serves some adaptive function. We’ve already discussed art as a process (mediated performances) that sometimes leaves behind durable, scalable artifacts. And if art is process, then asking “what is art for?” is also asking “what is this social process for?”</p>
</section>
<section id="primary-functions" class="level2">
<h2 class="anchored" data-anchor-id="primary-functions">Primary Functions</h2>
<section id="communication-signalling-and-coordination" class="level3">
<h3 class="anchored" data-anchor-id="communication-signalling-and-coordination">1. Communication, Signalling, and Coordination</h3>
<blockquote class="blockquote">
<p>In this world, nothing causes true anxiety except death and status. - Procopio</p>
</blockquote>
<p>Mediated performances, where an artist encodes and transmits information across mediators to an audience that then decodes it, are by definition a type of communication. So let us start by investigating communication as a social process. What are the evolutionary benefits of communication?</p>
<section id="a.-natural-selection-and-communication" class="level4">
<h4 class="anchored" data-anchor-id="a.-natural-selection-and-communication">1a. Natural Selection and Communication</h4>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/howl.jpg" class="img-fluid" style="width:50.0%"> <!-- https://folsomzoofriends.org/howling/ - image source --></p>
<p>Ultimately, natural selection is concerned with persistence. The most “primitive” type of selection is <a href="../../../ml/posts/inspection_bias/index.html">inspection bias</a>: if we inspect a sample from a distribution of objects with variable lifetimes, the sample overrepresents those with longer lifetimes. If this sampling occurs repeatedly, the effect is concentrated.</p>
<p>How does communication enable persistence? One way is via coordination, which enables collective action. Organisms that act collectively may be able to pool resources, specialize, or act more efficiently, enhancing survival. A second way is via replication and persistence of the information itself, outside the original organism. Communication allows adaptive information to persist and accumulate across generations without waiting for genetic encoding<sup>12</sup>. Otherwise, organisms would have to learn from scratch each generation. Both mechanisms are downstream of communication’s basic function: making one agent’s information available to another.</p>
<!-- ??? outside scope, ignore: Here we have 1. efficiency/prediction 2. memory? -->
<p>What kind of information can be transmitted via art? I’d sort these into two categories: “affective” and “ideological”<sup>13</sup>.</p>
<p>“Affective” or “phenomenal” information describes “what it’s like to be” another agent. In Tolstoy’s 1897 <a href="https://sreda.v-a-c.org/en/read-00">essay</a>, “What is Art”, Tolstoy suggests that the art’s function is primarily to transfer emotional content (pleasant or unpleasant) from the artist to the audience, saying that art begins when one person, with the object of joining another or others to himself in one and the same feeling, expresses that feeling by certain external indications.” Tolstoy goes on to say that art, like speech, “serves as a means of union among [people]”. Similarly, in <em>The Principles of Art</em> (1938) Collingwood argues that art is the clarification and expression of emotion, though he claims that the artist discovers what they feel through the process of creating the art.</p>
<p>One issue with purely affective theories is that artistic creators may engineer in the observer an emotion or a belief that they themselves do not experience or ascribe to, often in an attempt to control the observer or induce a specific behavior. That is, art is not inherently “true”: an artist may lie or behave strategically. <em>Battleship Potemkin</em> (1925), while art, is designed to evoke solidarity with the revolutionaries opposing Tsarist oppression. More innocuously, the creators of movie posters or designers of brand may use emotion as an instrument to incentivize purchases, even if they themselves do not enjoy the product.</p>
<p>This brings us to ideological art. The goal of some art is not (or is not only) to make the audience feel something, but also to make them <em>believe</em> something. This could be about history, religion, morality, or politics, among others.</p>
<p>Recognition of the power of art to shape belief goes back at least to Plato, who wanted to censor poets. In <em>The Republic</em> (especially Books 3 and 10), Plato argues that poetry’s capacity to implant convictions through vivid imitation (mimesis) make them dangerous. Homer’s epics, for instance, portray gods as petty and immoral, and portray heroes as driven by passion over reason. Plato feared that this would lead listeners to accept flawed models of virtue, justice, or the divine. Similarly, Byzantine or medieval Christian panels were designed not just for devotion but also to convey specific theological doctrines, like the divinity of Christ or the intercession of saints.</p>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/transfiguration.jpg" class="img-fluid" style="width:50.0%"> <!-- https://smarthistory.org/saint-catherines-monastery-sinai/ --></p>
<p>Ideological art exploits the communicative link between creator and receiver to transfer convictions, which may be held genuinely by the artist or deployed strategically. Such art feeds into broader social coordination (which we will explore shortly) and aligns entire groups around shared beliefs (as with state propaganda or national epics). This distinguishes it from purely affective art, though the two often intertwine: belief is harder to instill without emotional resonance.</p>
</section>
<section id="b.-sexual-selection-and-signalling" class="level4">
<h4 class="anchored" data-anchor-id="b.-sexual-selection-and-signalling">1b. Sexual Selection and Signalling</h4>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/bower.jpg" class="img-fluid" style="width:50.0%"> <!-- different bird pic ? --></p>
<p>In Darwin’s 1871 book, <em>The Descent of Man, and Selection in Relation to Sex</em>, he introduced the concept of sexual selection. Sexual selection favors traits that enhance mating success despite not necessarily favoring survival. In Geoffrey Miller’s book, <em>The Mating Mind</em> (2000), he argues art signals fitness: the capacity to create complex, novel, aesthetically compelling work demonstrates intelligence and creativity. In fact, simply observing that the art is there indicates that the creator had surplus resources to conduct the performance, whether that’s excess energy for a bird to perform a mating dance or excess economic and social capital to produce a film<sup>14</sup>. The costliness makes the signal more honest, as you can’t spend resources you don’t have<sup>15</sup>.</p>
<p>A common theory of sexual reproduction is that it’s evolutionary function is to exaggerate genetic variance through genetic recombination, producing more diverse phenotypes. Less remarked upon is that the <em>incentives</em> of sexual selection also favor increased variance: mate choice (on behalf of females) creates pressure to stand out from competitors (on behalf of males), increasing variance<sup>16</sup>. In fact, as Richard Prum argues in <em>The Evolution of Beauty</em>, runaway selection can produce arbitrary preferences. Aesthetics can be self-reinforcing, and beauty doesn’t have to track useful traits (at least in the short-term).</p>
<p>For example, consider an illustrative example: a population of men and women, where the men vary in penis length and the women vary in penis size preference. Having a larger penis is detrimental to long term fitness, as growing and maintaining a large penis requires additional resources, like energy. However, suppose due to random variation there is a slight preference among the female population (or a subpopulation) for larger penises. This will bias the descendant men to have larger penises, as the large penised men will have a higher probability of reproducing. Since the women with the strongest preferences for large penises will tend to breed with men with larger penises, the women of the largest penised men will tend to have strong preferences for long penises. After many generations, we can expect both long-penis-having and long-penis preserving to increase in the population, perhaps until those characteristics becomes detrimental to fitness<sup>17</sup>. Many similar examples exist in biology, such as peacock’s tails and bowerbird’s nest construction. Runaway sexual processes are also well-documented in stag beetles (antler size), Irish elk (antler span reaching maladaptive extremes), and numerous bird species.</p>
<!-- Might need to touch up this to make sure we switch from sexual selection to art properly -->
<p>However, a large penis is not art. Just because a signal is subject to sexual selection is not sufficient to call it “art”. This is why we include “extrabiological” in our conditions for art. However, there are behavioral exceptions as well. For example, many sports can be instrumentally validated<sup>18</sup>, so they are not art even if used in sexual signalling. Similarly, economic success in and of itself is not art, even if it may be employed to construct sexual signals.</p>
<p>Our example also shows how the act of interpreting a signal is itself part of the sexual selection process. Preferences propagate alongside the relevant trait. By analogy, this also applies to art: appreciating art is itself a signal in the signalling game.</p>
<p>Good taste indicates you can distinguish “good” from “bad” art, which plausibly correlates with intelligence, social awareness, and reasoning ability. And good taste and good art are mutually reinforcing. If we assume “good art” is art with high-fitness meaning (sexual or natural) while “bad art” carries low-fitness, then good taste signals overall mate fitness through the ability to detect the relevant signals well. Good taste shows you can identify true art; choosing true art shows you have good taste.</p>
<p>However, signalling doesn’t occur in a vacuum. Standing out requires differentiation from the local context, not just absolute quality. So a signal’s value depends on the current distribution of current signals. This explains why artistic norms vary across cultures and eras. Art is relative and multidimensional.</p>
<p>In sexual selection, signalling is typically just to a potential mate or mates. But humans also signal to groups, and groups signal to other groups. For instance, a cathedral signals not just individual piety but also collective wealth, coordination capacity, and devotion. Art can be used as a general social coordination mechanism.</p>
</section>
<section id="c.-group-selection-and-social-coordination" class="level4">
<h4 class="anchored" data-anchor-id="c.-group-selection-and-social-coordination">1c. Group Selection and Social Coordination</h4>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/andy_goldsworthy.jpg" class="img-fluid" style="width:50.0%"></p>
<p>If art is relative, then for any specific piece we have to ask who it’s “good” for, and over what timeframe. Art that enhances individual mating success may conflict with art that enhances group cohesion. Art that coordinates a subculture may alienate the mainstream (or vice-versa). These conflicts are expected: multi-level selection produces competing pressures, and what counts as “good” depends on the level being optimized<sup>19</sup>.</p>
<p>Beyond sexual signaling, art serves broader social coordination. We discuss films, share reviews, and debate rankings not just to inform but to align preferences and identities. As Bourdieu says:</p>
<blockquote class="blockquote">
<p>Taste classifies, and it classifies the classifier. Social subjects, classified by their classifications, distinguish themselves by the distinctions they make, between the beautiful and the ugly, the distinguished and the vulgar, in which their position in the objective classifications is expressed or betrayed. — Pierre Bourdieu, <em>Distinction</em> (1979)</p>
</blockquote>
<p>The “art” you make or claim to like marks your identity socially<sup>20</sup>. Similarly, the art you <em>dislike</em> marks your identity socially. As Bourdieu says:</p>
<blockquote class="blockquote">
<p>Taste is first and foremost distaste, disgust and visceral intolerance of the taste of others. - Pierre Bourdieu, <em>Distinction</em> (1979)</p>
</blockquote>
<p>For example, the internet widely despises certain bands, like Nickelback, who have achieved a widespread popular hit. By hating Nickelback, Nickelback-haters signal their non-mainstream tastes. Negative coordination is at least as powerful as positive coordination for drawing group boundaries, and possibly more so, as disliking popular things early carries higher risk of social exile and thus may signal more independence. This leads to a type of “coordination game” where agents are attempting to anticipate the current and future tastes of others.</p>
<section id="keyness-beauty-contest" class="level5">
<h5 class="anchored" data-anchor-id="keyness-beauty-contest">Keynes’s Beauty Contest</h5>
<blockquote class="blockquote">
<p>Successful investing is anticipating the anticipations of others. - John Maynard Keynes</p>
</blockquote>
<p>Keynes considered coordination games of this nature in his 1936 book<sup>21</sup>, using the metaphor of the “beauty contest” (allegedly based on a real practice in British newspapers).</p>
<p>In the original beauty contest, readers were asked to select the prettiest faces from a set of photographs. The prize went to the participants whose selections matched the <em>most popular selections</em> across all participants. This involves anticipating others’ preferences rather than expressing your own. This involves some degree of “social metacognition”.</p>
<p>Simple versions of the beauty contest are empirically testable. For example, consider the game “guess 2/3 of the average”. If you make the “zeroth-order” assumption (that everyone else chooses uniformly at randomly from the list of numbers) then your prediction of the average is 50, so you should make first-order guess is ~33. If you assume everyone else is making the first-order prediction, then your prediction of the average is ~33, and you should make the second-order guess of ~22. This process can be repeated (giving a Nash equilibrium guess of zero). This thought experiment (literally a <em>beauty</em> contest) can be extended to art. Level 0 is naive aesthetics (“I like this”), level 1 is “others will like this”, level two is “others will predict others will like this”, and so on and so forth. In the limit, players converge on a Schelling point: the choice that’s salient because everyone expects everyone else to choose it.</p>
<p>It’s important to note that in practice, the winning guess is likely not zero, as the actual distribution of guesses depends on the level of strategy actual used among the general population. The game extends to a metagame: players are judged not only on their choices but on their strategies. If a player behaves too “strategically”, it seems “fake” and the behavior is punished. Level 0 grounding is needed to seem “authentic”.</p>
<p>Based on the game, we now have two different definitions of beauty. On the one hand, we have the individual definition of beauty, based on “pure aesthetics”. On the other hand, we can define beauty socially, as whatever wins the beauty contest. But this invites analysis of the social domain. Who constructs the beauty contest, who participates, and who judges?</p>
</section>
<section id="institutions" class="level5">
<h5 class="anchored" data-anchor-id="institutions">Institutions</h5>
<p>In <em>The Construction of Social Reality</em> (1995), John Searle argues that coordination can create entirely new ontological phenomena. “Collective intentionality”, he writes, “is a biologically primitive phenomenon”. Through collective acceptance, groups bring “institutional facts” into existence, like money, property, and marriage. A piece of paper becomes a money not through any physical process but through collective agreement. However, in context there are still “objective facts” about money (for example, how much money someone has in their bank account). Similar social processes affect art: collective acceptance turns certain artifacts into “great art.”</p>
<!-- Concern this is too much "what is art" and not "what is arts function" -->
<p>In George Dickie’s <em>Art and the Aesthetic: An Institutional Analysis</em> (1974), he offers the most extreme version of this argument, claiming that “art is whatever an ‘artworld’ presents as art.” The “artworld” is simply an institution, and there is no essence of art beyond institutional recognition. We once again reminded of Duchamp’s “fountain”, a mass-produced urinal, signed with a pseudonym and placed on a pedestal. It functions as art purely through institutional nomination.</p>
<p>This framing helps explain the social machinery around art. Artists, critics, and curators gain status by influencing what the group coordinates on. In some ways, institutions are <em>defined</em> by what art (or other signals) they coordinate on<sup>22</sup>. A gallery is differentiated by its exhibits and a canon is differentiated by what it includes.</p>
</section>
<section id="state-coordination-and-weaponized-aesthetics" class="level5">
<h5 class="anchored" data-anchor-id="state-coordination-and-weaponized-aesthetics">State Coordination and Weaponized Aesthetics</h5>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/Stalin_and_Voroshilov_in_the_Kremlin_Gerasimov.jpg" class="img-fluid" style="width:50.0%"></p>
<p>The coordination function of art has not escaped the attention of the state. If art shapes what groups believe and coordinates around, then controlling art is a lever of power.</p>
<p>During the Cold War, the CIA embarked on a well-documented project of artistic control. Frank Wisner, head of the Office of Policy Coordination, described his propaganda apparatus as “the mighty Wurlitzer”, imagining his program as an organ capable of playing tunes across the world. Through fronts like the Congress for Cultural Freedom, the CIA covertly funded literary magazines (Encounter, Partisan Review), art exhibitions, symphonic tours, and academic conferences. Abstract Expressionism was promoted internationally as evidence of American freedom and creative individualism, deliberately contrasted against Soviet Socialist Realism.</p>
<p>The explicit goal of this project was to coordinate Western intellectuals (and wavering non-aligned intellectuals) around meanings favorable to American interests and conduct information warfare against the Soviets. The program argued that West represented creative freedom and that Marxism was artistically sterile.</p>
<p>A different model of state involvement in art appeared in post-revolutionary Mexico. The Muralist movement (Rivera, Orozco, Siqueiros) was explicitly commissioned by the state to construct a national Mexican identity out of disparate culture groups. Education Minister José Vasconcelos funded monumental public murals depicting Mexican history, indigenous heritage, and revolutionary ideals. The goal was to coordinate a fractured post-revolutionary population around shared meanings and to make “Mexican national identity” real by giving it visible, public, unavoidable form. Unlike the CIA’s covert operations, the Mexican project was explicit and state-sponsored without disguise. The murals were designed to teach the (often illiterate) population their desired historical narrative.</p>
<p>To what degree does state-sponsored art persist outside its sponsoring context? On the one hand, Soviet Socialist Realism has not fared well in the post-Soviet canon. Mexican Muralism has fared better. This may reflect genuine artistic quality, or it may reflect different power dynamics in how art history has been written.</p>
<p>Institutions can persist or fail; the survival of the art and survival of the institutions are linked. Some “canons” endure for millennia; others are forgotten within a generation. If short-run art value is whatever a group coordinates on, what determines which coordination equilibria persist in the long run?</p>
</section>
</section>
</section>
<section id="ontological-research" class="level3">
<h3 class="anchored" data-anchor-id="ontological-research">2. Ontological Research</h3>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/Monolito_de_la_Piedra_del_Sol.jpg" class="img-fluid" style="width:50.0%"></p>
<p>We have played fast and loose with the word “good” in relation to art. But what do we mean by “good”?</p>
<p>In the short run, artistic value is whatever the group converges on. But if value were entirely socially constructed, all art would be equally valid. This is clearly not true, as some artistic ideas survive centuries, while others quickly lost or forgotten. What determines why some information persists in societies, and other information does not?</p>
<p>The answer is that art does something beyond simply coordinating: it encodes “true” information about reality. Even practices with false explicit justifications can persist if they confer adaptive advantage. For example, ritual child sacrifice during famines, however horrifying, can function as population control or signal commitment. For these reasons, groups practicing child sacrifice may outcompete those that don’t, in some sense “justifying” the sacrifice. Similarly, Leni Riefenstahl’s <em>Triumph of the Will</em> was effective at coordination despite leading to heinous outcomes. Groups that coordinate on “useful” information (information that helps the group model the world and cohere socially) persist better than groups that coordinate on less “useful” information. In the long run, cultural selection<sup>23</sup> filters for art that tracks “truth”.</p>
<p>“Truth” comes in multiple non-equivalent senses. <em>Ontological truth</em> concerns the world’s actual structure (physical existence, cause-and-effect, invariants) whether or not anyone is there who can articulate that structure. <em>Epistemic truth</em> is about where the specific claims a work advances correspond to reality. <em>Pragmatic truth</em> is about evolutionary utility: a belief or practice is “true” in the sense that it improves persistence, even if the explicit content of the knowledge is epistemically or ontologically false<sup>24</sup>.</p>
<p>The view that art may represent some “truth” about the world stretches back at least as far as Plato. Plato argued that true reality consists of eternal Forms. He was suspicious of art, claiming that since art was copied from physical objects, which were in turn imperfect copies of Forms, that art was thrice-removed from Truth, thus rendering it a poor form of inquiry.</p>
<p>We previously explored Borges’ metaphor of <a href="../../../essays/posts/preference_oracles/index.html">The Library of Babel</a>. Almost all books are noise, but somewhere in the stacks are “texts” (or other artwork represented as strings) that present genuine truths about reality, predict the future, etc., or at least present them in compressed form.</p>
<p>Why do compressed ideas tend to be beautiful? One answer<sup>25</sup> is that beauty <em>is</em> compression. That is, we find things pleasing when they help us compress our model of the world. But in the framing of this essay the causation runs the other way. Compressed ideas are easier to transmit, remember, and coordinate around. A compact formulation spreads faster than a sprawling one. Compression correlates with persistence, and the ideas that persists are the beautiful ones. The aesthetic preference for elegance is downstream of transmission dynamics<sup>26</sup>.</p>
<p>Regardless, the problem is finding the texts, evaluating them, and ultimately agreeing on them.</p>
<section id="grounding" class="level4">
<h4 class="anchored" data-anchor-id="grounding">Grounding</h4>
<p>If long-run selection filters for useful information, how does this filtering actually work? The utility of a belief or practice may not be apparent for generations. A group might coordinate on a harmful idea and not discover the cost until it’s too late.</p>
<p>Several mechanisms help close this gap:</p>
<ol type="1">
<li>Proxies</li>
</ol>
<p>Taste intuitions evolved to track utility without computing it directly. If your ancestors who preferred certain landscapes survived more often, you inherit that preference as a felt sense of beauty.</p>
<ol start="2" type="1">
<li>Cross-group observation</li>
</ol>
<p>Groups can observe which other groups thrive and imitate their practices. A canon that persists across multiple independent cultures is more likely to encode genuine truth than one confined to a single group.</p>
<ol start="3" type="1">
<li>Nested selection</li>
</ol>
<p>Selection operates across all groups and timescales simultaneously. Within a group, individuals compete for status by predicting future consensus. Across groups, cultural packages compete for adoption. Across generations, biological evolution shapes the taste machinery itself. Faster loops provide feedback to slower ones<sup>27</sup>.</p>
<p>Over many generations, these mechanisms select for individuals with good taste intuitions and for the preservation of objects and texts those individuals create. Groups also develop meta-taste, such as judgment about which curation mechanisms to trust, which preservation traditions to maintain, which critics to follow (or at the very least, the bad ones are selected out).</p>
<p>Can coordination itself create ontological depth where none existed? Searle argued that collective intentionality creates institutional facts. Perhaps art works similarly: collective acceptance doesn’t just recognize value but instead bootstraps it into existence. The canon becomes real because we treat it as real, and treating it as real makes it function as a coordination device that actually helps the group persist.</p>
<p>There are real, historical examples of art instantiating cultural practices and reorganizing institutions <em>ab initio</em>. For example, Upton Sinclair’s <em>The Jungle</em> contributed to the passage of major U.S. food safety laws in 1906. More recently (and weirdly) the movie <em>Spectre</em> (2015) depicted a Mexico City “Day of the Dead” parade, and the city subsequently created a real parade beginning in 2016. Fiction can seed tradition. Successful works become shared reference points, which shift coordination, which shifts policy and practice.</p>
<p>If aesthetic pleasure is a heuristic for utility, why do we sometimes coordinate on art that makes us depressed, nihilistic, or self-destructive? Does the theory account for art that hacks the pleasure heuristic without providing ontological benefit? I would argue yes. First of all, unpleasant art can still encode ontological truth. Tragedy, horror, and nihilistic fiction may accurately model ideas such as mortality or betrayal. Facing these truths, may be more adaptive than ignoring them. Secondly, consuming difficult art signals differentiation and resilience. If most people avoid confronting hard truths, those who seek them out signal cognitive toughness and independence. Third, some art may genuinely be parasitic. Superstimuli exist in other domains (junk food, pornography, gambling), so it stands to reason there could be “parasitic” art as well.Selection is slow and imperfect, so parasitic art can exist in equilibrium, especially if its harms are diffuse or delayed. Finally, harm may operate at different levels. As we’ve discussed, art that damages individuals may still benefit groups (martyrdom narratives, sacrifice myths), or vice-versa. What looks parasitic from one level may be functional from another.</p>
</section>
<section id="search-process" class="level4">
<h4 class="anchored" data-anchor-id="search-process">Search Process</h4>
<!-- In efficient market: have property X -> gives signal S
gives signal S -> has property X -->
<p>How does new “true” art enter the canon?:</p>
<ol type="1">
<li>An artist finds a text outside the current consensus.</li>
<li>Early adopters recognize it, taking reputational risk by endorsing something unproven.</li>
<li>If the text spreads and becomes a new coordination point, the early adopters gain status.</li>
<li>As adoption increases, the signal degrades. “Everyone likes it now” means liking it no longer differentiates you.</li>
<li>Status-seekers must find new true texts to distinguish themselves.</li>
<li>Return to step 1.</li>
</ol>
<p>This is kind of a “high-dimensional” version of the Keynesian beauty contest.</p>
<p>This mechanism explains why avant-garde art is polarizing by design (high variance means high expected status payoff for correct bets), the power of critics and curators (they offload risk onto others while reaping rewards for correct calls), and why AI-generated art feels “cheap” (zero risk taken in production, so low signaling value).</p>
<p>Signals naturally degrade, hence the red queen race of getting “ahead of the curve”, the behavior of hipsters<sup>28</sup>, etc. Good taste involves predicting future consensus. While it may correlate with other desirable mental properties (openness, political views, etc), it presumably is also high value as it predicts <em>what the group wants now and in the future</em>, which is key to leading a group. Similarly, if taste defines the group in some way, then very poor taste could result in exile (or death). Naturally, the successful artists and tastemakers will rise in status<sup>29</sup><sup>30</sup><sup>31</sup>.</p>
</section>
<section id="agents-perspective" class="level4">
<h4 class="anchored" data-anchor-id="agents-perspective">Agent’s Perspective</h4>
<p>From the artist’s perspective, the layers we have considered are all blended together. A creator is simultaneously (a) following a local aesthetic gradient (b) considering the audience (c) placing a reputational wager and sometimes (d) trying to compress something real about the world into transmissible form.</p>
</section>
<section id="why-disagreement-persists" class="level4">
<h4 class="anchored" data-anchor-id="why-disagreement-persists">Why Disagreement Persists</h4>
<p>If long-run selection filters for useful art, why does taste vary so widely?</p>
<p>A few possible hypotheses. First, division of labor. Groups benefit from having members with heterogeneous taste. Some may favor novelty, while others favor tradition. A group of pure novelty-seekers would lose accumulated wisdom, while a group of pure traditionalists would fail to adapt. Variance in taste is itself adaptive.</p>
<p>Second, usefulness depends on context. Art useful for a warrior caste (glorifying honor, sacrifice, or physical prowess) differs from art useful for a priestly caste (emphasizing contemplation, transcendence, and textual authority). Subgroups within a society may correctly coordinate on different art for different functions. Disagreement across niches is specialization.</p>
<p>Third, ongoing search. The space of possible texts is vast, and which texts are “true” depends on the current situation. Disagreement is part of the exploration mechanism.</p>
</section>
<section id="art-and-science" class="level4">
<h4 class="anchored" data-anchor-id="art-and-science">Art and Science</h4>
<p>Art and science are closer than they appear. Both are collective processes for discovering and coordinating on “true” information. Both operate through institutions that canonize some contributions and forget/ignore others. Both advance through individuals who break existing conventions and (if vindicated) reshape the consensus.</p>
<p>In <em>The Structure of Scientific Revolutions</em> (1962), Thomas Kuhn argued that science doesn’t progress through steady accumulation but instead through “paradigm shifts”: periods of “normal science” punctuated by revolutionary breaks that reorganize the entire field. The same dynamic appears in art.</p>
<p>Most artistic production is competent work within established conventions (“normal art”). Occasionally, someone produces work that violates those conventions in a way that others come to recognize as revelatory rather than merely deviant. If the break succeeds, it becomes the new convention. If it fails, it’s forgotten or dismissed as incompetent.</p>
<p>Crucially, “good art” breaks <em>artistic</em> conventions, not necessarily political or moral ones. Transgression for its own sake (shock value, provocation) is not the same as genuine innovation. The test is whether the break opens new expressive or coordinative possibilities that others can adopt and build on. Duchamp’s urinal was revolutionary not because it was offensive but because it revealed something about the institutional structure of art itself. A merely offensive urinal would have been forgotten.</p>
</section>
</section>
<section id="direct-aesthetic-experience" class="level3">
<h3 class="anchored" data-anchor-id="direct-aesthetic-experience">3. Direct Aesthetic Experience</h3>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/frederic_edwin_church_heart_of_the_andes_1859.jpg" class="img-fluid" style="width:50.0%"></p>
<p>We have investigated the relationship between social coordination and ontological research. Let us now consider the relation to direct aesthetic experience. Why do we experience “beauty” at all? Why do we enjoy seeing a well-composed image, or discomfort at hearing a dissonant chord?</p>
<p>Some thinkers treat aesthetic experience almost as a type of drug, referring to the experience of art as a “disinterested pleasure” (Kant), an “aesthetic emotion” (Beardsley), or an “intensified experience (Dewey).” Perhaps these reactions could also be extended to to explain the creation of art (although many artists seem to view their art as labor). But why would we have these innate reactions?</p>
<p>Denis Dutton’s <em>The Art Instinct</em> (2009) offers a better answer. In it, he argues that aesthetic pleasure is an evolved heuristic. We find certain landscapes beautiful because the ancestors who preferred such landscapes were more likely to survive. We find symmetrical faces attractive because symmetry correlates with developmental health. We enjoy narrative because tracking social causation was essential for navigating coalition politics. Conversely, our disgust reaction to certain types of art signals long-run evolutionary disadvantage. In this account, aesthetic pleasure and displeasure are compressed, preconscious signals that information is likely to be adaptively useful.</p>
<p>If art is information evaluated through taste rather than direct verification, then taste must track something real, otherwise groups relying on it would be differentially outcompeted. Aesthetic pleasure guides individuals through the coordination game without explicitly computing fitness consequences.</p>
<p>But heuristics are imperfect. Ideas can be beautiful and wrong. Pleasure is only an individual proxy, not a guarantee. This is why the social machines around art and other signals exist.</p>
<p>Aesthetic pleasure is the base layer. Social coordination amplifies and filters this signal, and long-run selection pressure ensures (slowly and imperfectly) that what we find beautiful tends to track what actually helps us persist.</p>
</section>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>In the short run, art is a coordination game: value accrues to whatever the group converges on. On medium timescales, “artistic entrepreneurs” (artists, critics, curators) compete for status by anticipating and shaping future coordination. In the long run, the groups that coordinate on “useful” signals (that best help model reality and cohere socially) tend to persist; the art that persists is therefore the “good” art. Aesthetic pleasure is the evolved heuristic that lets individuals navigate this process without computing it explicitly.</p>
<p>Art, then, is ontological research conducted through social coordination and experienced as beauty.</p>
</section>
<section id="additional-content" class="level1">
<h1>Additional Content</h1>
<section id="major-open-questions" class="level2">
<h2 class="anchored" data-anchor-id="major-open-questions">Major Open Questions</h2>
<ul>
<li><p>I already compared this process to science, but there’s no reason many of the mechanisms can’t apply to various other socially defined processes or symbols (like legal concepts, status of specific people, etc). To what extent can these be delineated?</p></li>
<li><p>On that note, how do specific institutional architectures vary when producing different types of art, and how to specific institutional architectures vary for producing other types of social information? Can we engineer institutions to produce the desired effect?</p></li>
<li><p>This is a very broad and abstract theory. Similar to evolution, it likely fails to produce mesoscale or microscale explanations (Why did this artistic movement arise? Why was this particular piece of art formed?). This theory would probably admit multiple hypotheses for these questions. Is there a more granular theory that can be tested empirically and used instrumentally?</p></li>
<li><p>Is there an alternate theory of art, completely alien to this one? One vague idea I’ve seen kicking around (but not fully developed) is a kind of “financial” theory (think auctions, NFTs, tax evasion, etc).</p></li>
<li><p>Not all evolutionary theorists agree that art is directly adaptive. Stephen Davies, in <em>The Artful Species</em> (2012), argues that art may be a byproduct of other adaptations (language, imagination, social cognition, play) rather than selected for its own benefits. On this view, we make art because we have big brains that evolved for other reasons. The byproduct view and the adaptive view are not mutually exclusive. Art could have originated as a byproduct and subsequently been recruited for adaptive functions (coordination, signaling, ontological compression). Once art existed, groups that used it well would outcompete groups that didn’t, even if the initial capacity was incidental. The stronger claim of this essay is that art is now under selection pressure regardless of its origins. Whether the capacity for art was a target of selection or a side effect, the <em>use</em> of art is clearly functional, and that function shapes which art persists. Byproduct origins would explain why the art instinct is imperfect and hackable, while adaptive function explains why it’s structured and convergent.</p></li>
<li><p>We are still missing a lot of the mechanism. How are texts evaluated, for example? Are there particular functional forms? Could we implement a working model in code?</p></li>
<li><p>What does a “beauty contest” look like across high dimensional embeddings or encodings?</p></li>
<li><p>Different institutional architectures produce different selection dynamics. For example, centralized curation (academies, state patronage) has faster convergence, risk of capture, less exploration Market-based has more exploration, risk of pure popularity-tracking, winner-take-all dynamics. Decentralized prestige (peer networks, critical communities) has intermediate properties. How does this affect the art produced? Can we tell?</p></li>
</ul>
</section>
<section id="implications-predictions" class="level2">
<h2 class="anchored" data-anchor-id="implications-predictions">Implications &amp; Predictions</h2>
<p>If the preceding analysis is correct, what should we expect as technology (especially AI) reshapes the conditions of artistic production, distribution, and coordination?</p>
<section id="falling-costs" class="level3">
<h3 class="anchored" data-anchor-id="falling-costs">Falling Costs</h3>
<p>These values are falling simultaneously:</p>
<ol type="1">
<li>Cost/time required for production.</li>
</ol>
<p>AI can now generate text, images, music, and video at near-zero marginal cost. This weakens signaling via artifacts as technical impressiveness no longer signals fitness.</p>
<ol start="2" type="1">
<li>Cost/time required for search.</li>
</ol>
<p>For most of human history, the bottleneck was preservation (most art was lost: monks used to spend enormous time and resources copying manuscripts by hand). Now the bottleneck is search. The problem is now finding the good books in the Library of Babel. Discovery arbitrage (finding hidden gems before others) may collapse as search tools improve.</p>
<ol start="3" type="1">
<li>Time for a signal to diffuse and opinions to “equilibrate”.</li>
</ol>
<p>The internet accelerates how fast groups can converge on (and abandon) consensus. Signals degrade faster. The result is a permanent Red Queen race: by the time something is widely recognized as good, the status value of recognizing it has already dissipated.</p>
</section>
<section id="value-migration" class="level3">
<h3 class="anchored" data-anchor-id="value-migration">Value Migration</h3>
<p>We should expect value to accrue to the constraint. As production and discovery become commoditized, value migrates up the stack:</p>
<ol type="1">
<li>Object-level taste: Which works are good? (Increasingly automated)</li>
<li>Meta-taste: Which curators are good? (Still requires judgment)</li>
<li>Meta-meta-taste: Which curation mechanisms are good? (Emerging frontier)</li>
</ol>
<p>Alternatively, the signal migrates to something AI can’t (yet) fake: 1. Consistency over time (hard to fake at scale) 2. Physical presence (performance, live art) 3. Relational (I trust your taste because I know you personally)</p>
<p>In the limit, value may shift entirely to identity: I trust you because you’re you, not because of any specific judgment you’ve made.</p>
<p>It’s also possible that value may collapse entirely. If anyone can find any text, finding texts is worthless. Artistic signalling will become too noisy and humans will increasingly divvy up status through “games” with instrumental, empirical outcomes (sports with score, twitter likes, etc.)</p>
<p>One (outlandish) thought: in the <a href="../../../essays/posts/cultural_saturation/index.html#negative-temperature">cultural saturation</a> essay we discussed Onsager like vortices. If individuality ceases to provide signalling information, it’s possible that the only possible signal will instead be <em>conformity</em>. This will cause an inversion in the game, and a type of “emergent structure” as people seek to gain status by rushing to conform.</p>
<p>Another possibility: if material wealth is mostly solved due to AI and economics, and money doesn’t matter, then human society will be reduced to a popularity contest. How many views and followers someone has will entirely determine their worth. (Maybe this has already happened).</p>
</section>
<section id="cultural-variation" class="level3">
<h3 class="anchored" data-anchor-id="cultural-variation">Cultural Variation</h3>
<p>This framework should explain cultural differences related to respect for tradition vs.&nbsp;novelty. Groups that historically faced stable vs.&nbsp;volatile conditions selected for different preservation/innovation ratios. For example, cultures from stable environments (e.g., long-settled agricultural societies) should value tradition, canonical texts, and respect for elders’ taste, while cultures from volatile environments (e.g., frontier societies, frequent disruption) should value novelty, young tastemakers, and rapid fashion cycles. This seems to somewhat match rural/urban political divides? Similarly, established institutions should be tradition-oriented, while marginal/startup institutions should be novelty-oriented.</p>
</section>
<section id="ai-evolution" class="level3">
<h3 class="anchored" data-anchor-id="ai-evolution">AI Evolution</h3>
<p>Since AI can falsely point to texts, signs associated with AI pointed to texts will initially decline in status. However, the “highest status” AI companies will survive. Over time, there is thus an evolutionary selection mechanism such that AI will eventually align with human tastes</p>
</section>
</section>
<section id="edge-cases-puzzles" class="level2">
<h2 class="anchored" data-anchor-id="edge-cases-puzzles">Edge Cases &amp; Puzzles</h2>
<p><img src="https://demonstrandom.com/essays/posts/functional_theories_of_art/tiger.jpg" class="img-fluid" style="width:50.0%"></p>
<p>Let’s (ask ChatGPT to) generate some edge cases and questions that don’t fit neatly into the framework and attempt to justify them:</p>
<ol type="1">
<li>Outsider art. Consider cases like Van Gogh or Henri Rousseau, untrained creators who toil in obscurity and are only acknowledged late in life (or) posthumously. Why do they do this? And why the delay to discover them? Easy to explain why they become popular posthumously (they are coopted by others for status later).</li>
<li>Why do children make art? Children have minimal coordination sophistication. However, it could be for private pleasure (biologically ingrained), for parental approval, as “practice” or a “test drive” for later artistic endeavors, or to help the child coordinate across time with their own future selves.</li>
<li>Why do people enjoy art privately? A few ideas:</li>
</ol>
<ul>
<li>Private enjoyment trains the taste module for future coordination games.</li>
<li>Taste coordinates with your future self. Good art creates stable preferences over time and thus predictable internal common knowledge (“I know I will still value this in the future”).</li>
<li>Aesthetic pleasure evolved for survival-relevant stimuli (symmetrical faces, fertile landscapes) and now serves a purpose vestigially.</li>
</ul>
<ol start="5" type="1">
<li>Biophilia - No artist, no pointing, no social game, yet we find mountains beautiful. Explained via Dutton and pure natural selection.</li>
</ol>
</section>
<section id="other-open-questions" class="level2">
<h2 class="anchored" data-anchor-id="other-open-questions">Other Open Questions</h2>
<ol type="1">
<li><p>Cross-species aesthetics. Humans enjoy the plumage of birds, whale songs, birdsong, etc. Possibly some of the aesthetic machinery evolved quite early and is shared? Or could it be convergent for some reason? Alternatively, could selection have acted strongly enough on the [human + (species)] groups to create interspecies communication? Certainly it’s possible for interspecies signalling to occur (flowers and bees, poison dart frogs, mimicry, etc.)</p></li>
<li><p>What is status? How should we define it in general?</p></li>
<li><p>Can we design markets (or other statistical estimators) that can automatically converge to the correct “taste ranking” methods? Similarly, can we automate taste?</p></li>
<li><p>Can you tell the difference between short-term sexually selected characteristics and long-term evolve naturally selected characteristics? And is there a way to actually determine the utility of a trait in the environment?</p></li>
</ol>
</section>
</section>
<section id="appendices" class="level1">
<h1>Appendices</h1>
<section id="appendix-a-ostensive-definition-of-art" class="level2">
<h2 class="anchored" data-anchor-id="appendix-a-ostensive-definition-of-art">Appendix A: Ostensive Definition of Art</h2>
<p>Let’s take our definition and try to delineate between art and not art (especially among the edge cases).</p>
<ol type="1">
<li><strong>Paradigmatic Art</strong></li>
</ol>
<p>Painting, music, novels, film, sculpture, dance, theatre, poetry, opera, etc. Evaluated almost entirely through taste mechanisms.</p>
<ol start="2" type="1">
<li><strong>Clear Art, Sometimes Contested</strong></li>
</ol>
<p>Video games, fashion, cuisine, graphic novels, graffiti, advertising, product design. These are sometimes excluded from “fine art” discourse for institutional or historical reasons, but they fit the functional definition: extrabiological information evaluated primarily through taste.</p>
<ol start="3" type="1">
<li><strong>Hybrid Cases (Art + Instrumental)</strong></li>
</ol>
<p>Architecture (must be structurally sound <em>and</em> beautiful), industrial design (must function <em>and</em> please), rhetoric (must persuade <em>and</em> move). The artifact has verifiable constraints, but significant latitude remains for taste-mediated evaluation.</p>
<ol start="4" type="1">
<li><strong>Art in Process/Curation</strong></li>
</ol>
<p>DJing, playlists, photography of found objects, Readymades, anthology editing. Minimal creation; the art lies in selection, framing, and institutional presentation.</p>
<ol start="5" type="1">
<li><strong>Edutainment</strong></li>
</ol>
<p>Documentaries, infographics, educational games. The <em>information</em> transmitted is verifiable; the <em>choices of presentation</em> are where taste operates.</p>
<ol start="6" type="1">
<li><strong>Taste-Engaging Non-Art</strong></li>
</ol>
<p>Natural landscapes (no creator, no social process—pure evolved heuristic). Mathematical proofs (correctness is verifiable, but choice of proof is aesthetic). Athletic performance in objectively-scored sports (outcome isn’t art; form still engages taste).</p>
<ol start="7" type="1">
<li><strong>Clear Non-Art</strong></li>
</ol>
<p>Engineering solutions evaluated purely by function. Scientific data. Sports scores. Tax returns. These are evaluated instrumentally, not through taste.</p>
</section>
</section>
<section id="changelog" class="level1">
<h1>Changelog</h1>
<ul>
<li><strong>2026-03-01</strong>: Added “The Missing Senses” subsection covering cuisine (gustatory), perfumery (olfactory), tactilia (haptic), thermae (thermoceptive), and exotic sensory arts (interoceptia, proprioceptia, nociceptia, vestibulia), and why they resist canonization. Reorganized Artifacts section into three parallel subsections (Data Types, The Missing Senses, Performance).</li>
</ul>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>I used Claude to help research and edit this essay.</p>
<!-- 
----

Still to integrate:
Evolutionary: Lean into Dissanayake ("making special" for cohesion/ritual) vs. Miller (sexual signaling/virtuosity)—your view aligns more with group-level benefits (coordination on useful meanings).
In science, to what degree are we looking at "coordinative symbols" vs "real progress"?
"art" versus "engineering" or "sport".
Tools create tools create tools -->
<p><!-- 
 Different institutional architectures produce different selection dynamics:
Centralized curation (academies, state patronage): Faster convergence, risk of capture, less exploration
Market-based: More exploration, risk of pure popularity-tracking, winner-take-all dynamics
Decentralized prestige (peer networks, critical communities): Intermediate properties
  --></p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>“Selection pressure” here refers to both cultural and biological. The pressure is primarily cultural selection, which filters which meanings and practices spread between groups on a short timeframe. Biological evolution, which more slowly alters the underlying taste and learning machinery based on how those cultural patterns affect survival and reproduction, is secondary.↩︎</p></li>
<li id="fn2"><p>See the <a href="../../../essays/posts/functional_theories_of_art/index.html#appendix--ostensive-definition-of-art">appendix</a> for my attempt to delineate between art and non-art.↩︎</p></li>
<li id="fn3"><p>This essay omits any financial theory of art.↩︎</p></li>
<li id="fn4"><p>The “fine arts” are typically distinguished from the seven “liberal arts”, which split into the trivium (rhetoric, grammar, and logic) and the quadrivium (astronomy, arithmetic, geometry, and music).↩︎</p></li>
<li id="fn5"><p>There is also some art that fits these categories in ways that don’t match up with the names. For example, a programmatic sequence of rhythmically flashing LEDs would presumably fall under “film”.↩︎</p></li>
<li id="fn6"><p>The “organ” (the perfumer’s palette of raw materials) typically contains 500-2000 ingredients, and a finished fragrance may use 30-80. The structure of a perfume (top notes that evaporate in minutes, heart notes that last hours, base notes that persist for days) could also make some scents time-based.↩︎</p></li>
<li id="fn7"><p>One could argue thermae are a subcategory of architecture (designed environments). But the primary aesthetic dimension is thermal and somatic, not visual. A great bathhouse with bad water is a failure; a bare concrete room with perfect water temperature and mineral content can be sublime.↩︎</p></li>
<li id="fn8"><p>A tea ceremony combines cuisine, perfumery, tactilia (the feel of the bowl), thermae (the warmth), and interoceptia (the meditative state) into a single integrated experience. The ceremony is evaluated almost entirely through taste, not through any instrumental metric.↩︎</p></li>
<li id="fn9"><p>Whether nociceptia is a subcategory of tactilia or its own thing is debatable. The distinction matters if you think the aesthetic of pain is qualitatively different from the aesthetic of touch, which most people who have experienced both would affirm.↩︎</p></li>
<li id="fn10"><p>In some cases we have partial substitutes, like recipes or perfume formulas. But these still require labor on the part of the consumer. This is changing slowly. Electronic noses, flavor profiling, headspace capture technology, and scent diffusion devices are primitive recording and playback systems for smell. If a reliable “scent codec” were developed, perfumery might undergo the same explosion that music did after the phonograph.↩︎</p></li>
<li id="fn11"><p>“Sport” is also a type of performance. Is sport art? I think the answer is: sometimes. Sport is about finding the fundamental limits of the human body. Some sports are adjacent to art (figure skating, diving), especially the ones judged subjectively. Sports with purely objective scores are probably not art. That being said, spectators might find certain running styles more beautiful than others, and athletes do seem to care about form beyond pure optimization. The artifact itself (the score/outcome) isn’t art, but the process still engages taste mechanisms. Similarly, the design of sports (modulo economic considerations) is art.↩︎</p></li>
<li id="fn12"><p>Genes are themselves a type of communication, but this is outside the scope of this essay.↩︎</p></li>
<li id="fn13"><p>We can also ask if art can transmit “factual” information. Are documentaries, infographics, educational games, or other forms of edutainment art? I’d argue yes, but with an asterisk (see the appendix on the ostensive definition of art). In these cases, the art is predominately in the <em>choice of how</em> the information is transmitted rather than the information itself; “photosynthesis takes sunlight and carbon dioxide and produces sugar” is not art, but the choices of how to convey that information to children via a <a href="https://www.youtube.com/watch?v=Q__T8_zHSrs">cartoon</a> is art.↩︎</p></li>
<li id="fn14"><p>One question I plan to explore: Is there some statistical notion of “primitive signalling” akin to the <a href="../../../ml/posts/inspection_bias/index.html">inspection paradox</a>?↩︎</p></li>
<li id="fn15"><p>With caveats. Of course, there are numerous examples both in nature and human society of false signalling. But this is out of scope of this post.↩︎</p></li>
<li id="fn16"><p>While I have seen the mechanism in Prum’s book, I haven’t seen the parallel point about variance framed specifically in these terms before (but I haven’t looked that hard). One question I have is whether the incentives (via mate selection) or the sexual reproduction came first. It feels more truthy to me that incentives would come first, but I don’t know enough about the subject to comment.↩︎</p></li>
<li id="fn17"><p>How far do we expect a trait to change based purely on this phenomenon? Is there a mathematical way to calculate this? Probably it exists in the literature but I have yet to explore it.↩︎</p></li>
<li id="fn18"><p>One potential philosophical issue is that the validation itself may be part of a social process. Resolving this is out of scope of this essay. There are attempts to deal with these delimitations in works such as Searle’s <em>The Construction of Social Reality</em> or Epstein’s <em>The Ant Trap</em>.↩︎</p></li>
<li id="fn19"><p>In my opinion, this is (or is at least deeply related to) the Fundamental Problem of Ethics: an individual may be part of a group (or groups), and the group’s preference may conflict with the individual’s. Should the individual take the best action for the group or for themself? Hopefully more on this in a future essay.↩︎</p></li>
<li id="fn20"><p>I previously discussed art and identity with respect to the <a href="../../../essays/posts/preference_oracles/index.html">Pierre Menard</a> story.↩︎</p></li>
<li id="fn21"><p><em>The General Theory of Employment, Interest and Money</em>↩︎</p></li>
<li id="fn22"><p>This may seem circular but I think these two concepts may actually be <em>dual</em> in some sense. We will see if I ever formalize this idea.↩︎</p></li>
<li id="fn23"><p>And possibly group selection, although group selection is controversial inside biology.↩︎</p></li>
<li id="fn24"><p>Separately, as Searle argues in <em>The Construction of Social Reality</em>, collective acceptance can create <em>institutional facts</em>. For example, canons, credentials, etc. “I have five dollars” is a fact, even if the concepts of property and dollars are socially constructed.↩︎</p></li>
<li id="fn25"><p>See, for example Schmidhuber, <a href="https://arxiv.org/abs/0812.4360">Driven by Compression Progress</a>, or George D. Birkhoff, <em>Aesthetic Measure</em> (1933).↩︎</p></li>
<li id="fn26"><p>This is visible in domains where content can be instrumentally verified. Mathematical proofs are not themselves are art, as correctness can be checked mechanically in <a href="../../../reasoning/posts/calculus_of_constructions/index.html">formal theorem provers</a>. But the choice of proof is aesthetic. Mathematicians tend to prefer the elegant proof that reveals structure with minimal machinery. As is often attribute to Einstein: “Everything should be made as simple as possible, but not simpler.” The proof that compresses without losing truth is the one that gets taught and built upon. Compression is a side effect of selection for transmissibility.↩︎</p></li>
<li id="fn27"><p>See <a href="https://gwern.net/backstop">Gwern’s writing</a> on the relationship between learning and evolution as complementary search processes.↩︎</p></li>
<li id="fn28"><p>This framework differs from Girard’s mimetic desire. Here, agents seek differentiation rather than converging on the same objects. However, at the meta-level, agents still imitate what others point to, so in part the search for novelty is mimetic. Squaring this circle is outside the scope of this essay.↩︎</p></li>
<li id="fn29"><p>One question I have is which way to define “status”. Is the highest status person the person with the best taste (the best at predicting future coordination), or is the “best taste” simply want the leader does? A truly “high status” person doesn’t need to signal, because the group already knows they are in charge and will coordinate on their decisions. If the group coordinates on whatever they choose, then their choice becomes “correct” by definition. That is, you stop being a “price-taker” in the taste market and become a “price-setter”. You are the Schelling point. So both the high and low status don’t signal: high because they don’t have to, and low because they can’t afford to.↩︎</p></li>
<li id="fn30"><p>There are likely multiple paths to status. For example, in a “dominance” path, you accumulate resources/power until exile from you is more costly than exile from the group, and you become the new Schelling point (i.e.&nbsp;threat of direct punishment). In the foresight path (or “prestige” path), you predict coordination so well, so early, so consistently that people start looking to you as the oracle and you become the Schelling point. Both end at the same place: creating common knowledge. “Everyone knows everyone knows that X matters.”↩︎</p></li>
<li id="fn31"><p>It’s possible that status is fundamentally about demand. High-status things are things that are demanded; high-status people are people who are demanded. To display status is to display that there is demand for you. This explains why some strategies (NFTs, certain dating tactics) attempt to simulate overdemand by restricting supply, even though artificial scarcity isn’t the same as genuine demand. Brands work similarly. A brand identifies you with the group that demands that product. To wear the brand is to claim membership in that group, and to see someone wearing it is to classify them as a member.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Essays</category>
  <category>Research</category>
  <guid>https://demonstrandom.com/essays/posts/functional_theories_of_art/</guid>
  <pubDate>Sun, 18 Jan 2026 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/essays/posts/functional_theories_of_art/SantaCruz-CuevaManos.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Reduction</title>
  <link>https://demonstrandom.com/game_theory/posts/reduction/</link>
  <description><![CDATA[ 





<p><a href="https://en.wikipedia.org/wiki/Print_Gallery_(M._C._Escher)"><img src="https://demonstrandom.com/game_theory/posts/reduction/print_gallery_mc_escher_1956.jpg" class="img-fluid"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>In this post, I introduce gauge equivalence, and also investigate a few different types of reduction under symmetry (to build out a taxonomy).</p>
<p>If you haven’t followed along, in the last few posts we introduced the Lagrangian in the context of <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html">geometric controls</a>. We then proved <a href="../../../game_theory/posts/noether_geometric_controls/index.html">Noether’s theorem</a> with <a href="../../../game_theory/posts/noether_time/index.html">time</a> and applied it to <a href="../../../game_theory/posts/dynamical_similarity/index.html">similar systems</a>.</p>
<p>Epistemic status: This post is still a bit rough: these are my informal notes navigating this subject. I’m more interested (ultimately) in computation so I’m not necessarily aiming for maximal rigour, and in fact I probably need to introduce more geometric machinery (bundles, connections, differential forms, symplectic geometry) to make the exposition more clean and rigorous. See the read more section for more rigorous sources.</p>
<!-- 
Here's a diagram ChatGPT drew putting everything in this post together:

```{.tikz}
%%| lib: cd
%%| format: svg
\begin{tikzcd}[row sep=large, column sep=large]

\mathcal{L}(Q)
  \arrow[d, "\text{gauge equivalence}"']
\\
\mathcal{L}(Q)/{\sim}
  \arrow[d, "\text{symmetry / equivariance}"']
\\
\text{Families of trajectories}
  \arrow[dl, "\text{configuration reduction}"']
  \arrow[dr, "\text{fix } J=\mu"]
\\
Q/G
  \arrow[dr]
&&
J^{-1}(\mu)/G_\mu
  \arrow[dl]
\\
& \text{Reduced dynamics}
  \arrow[d, "\text{Routh correction}"']
\\
& \text{Reduced variational system}

\end{tikzcd}
```

Vertical arrows identify equivalent descriptions of the same dynamics, while horizontal arrows perform genuine reductions by symmetry. -->
</section>
<section id="equivalence" class="level1">
<h1>Equivalence</h1>
<p>Before reducing anything, let’s introduce a new notion of “equivalence” (and recall one we saw before).</p>
<section id="gauge-equivalence" class="level2">
<h2 class="anchored" data-anchor-id="gauge-equivalence">Gauge Equivalence</h2>
<p>We once again consider a manifold <img src="https://latex.codecogs.com/png.latex?Q"> and a system with start and end configurations <img src="https://latex.codecogs.com/png.latex?q_0,%20q_N%20%5Cin%20Q">.</p>
<p>The Lagrangian is <img src="https://latex.codecogs.com/png.latex?L(t,%20q,%20%5Cdot%20q)">, and the action is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L(t,%20q,%20%5Cdot%20q)%20%5C%20dt%0A"></p>
<p>Let’s consider the adjusted action</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L(t,%20q,%20%5Cdot%20q)%20%5C%20dt%20+%20C%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?C%20%5Cin%20%5Cmathbb%7BR%7D"> is some constant. Clearly, <img src="https://latex.codecogs.com/png.latex?C"> does not affect the minimizing path for <img src="https://latex.codecogs.com/png.latex?S%5Bq%5D">.</p>
<p>Next, consider some function <img src="https://latex.codecogs.com/png.latex?F:%20%5Cmathbb%7BR%7D%20%5Ctimes%20Q%20%5Cto%20%5Cmathbb%7BR%7D">. Let <img src="https://latex.codecogs.com/png.latex?C%20=%20F(t_N,%20q(t_N))%20-%20F(t_0,%20q(t_0))">, so the action becomes:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L(t,%20q,%20%5Cdot%20q)%20%5C%20dt%20+%20F(t_N,%20q(t_N))%20-%20F(t_0,%20q(t_0))%0A"></p>
<p>but this is equal to</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L(t,%20q,%20%5Cdot%20q)%20+%20%5Cfrac%7Bd%7D%7Bdt%7D%5BF(t,%20q)%5D%20%5C%20dt%0A"></p>
<p>Therefore, given some Lagrangian <img src="https://latex.codecogs.com/png.latex?L(t,%20q,%20%5Cdot%20q)">, we can add an arbitrary <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7Bd%7D%7Bdt%7D%5BF(t,%20q)%5D"> without changing the underlying mechanics. That is, two Lagrangians <img src="https://latex.codecogs.com/png.latex?L"> and <img src="https://latex.codecogs.com/png.latex?L'%20=%20L%20+%20%5Cfrac%7BdF%7D%7Bdt%7D"> produce the same Euler-Lagrange equations.</p>
<p>By analogy with our previous posts, if for some group <img src="https://latex.codecogs.com/png.latex?G"> and some <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">, we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%20T%5CPhi_g(%5Cdot%7Bq%7D))%20=%20L(t,%20q,%5Cdot%7Bq%7D)%20+%20%5Cfrac%7Bd%7D%7Bdt%7DF_g(t,%20q)%0A"></p>
<p>we say that that <img src="https://latex.codecogs.com/png.latex?%5CPhi_g"> is a quasi-symmetry of <img src="https://latex.codecogs.com/png.latex?L">, and that the Lagrangians <img src="https://latex.codecogs.com/png.latex?L"> and <img src="https://latex.codecogs.com/png.latex?L'"> are “gauge-equivalent”<sup>1</sup>.</p>
<p>If we combine this with our view of <a href="../../../game_theory/posts/dynamical_similarity/index.html">equivariance</a>, we get:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%20T%5CPhi_g(%5Cdot%7Bq%7D))%20=%20%5Cchi(g)%5Ccdot%20L(t,%20q,%5Cdot%7Bq%7D)%20+%20%5Cfrac%7Bd%7D%7Bdt%7DF_g(t,q)%0A"></p>
<p>We can consider two Lagrangians <img src="https://latex.codecogs.com/png.latex?L"> and <img src="https://latex.codecogs.com/png.latex?L'"> to be “equivalent” if <img src="https://latex.codecogs.com/png.latex?L'%20%5Csim%20%5Cchi(g)%20%5Ccdot%20L%20+%20%5Cfrac%7BdF%7D%7Bdt%7D"> for some <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">.</p>
<p>In discrete coordinates, this becomes</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL'_d(t_k,%20q_k,%20q_%7Bk+1%7D)%20=%20L_d(t_k,%20q_k,%20q_%7Bk+1%7D)%20+%20F_%7Bk+1%7D(q_%7Bk+1%7D)%20-%20F_%7Bk%7D(q_k)%0A"></p>
<p>The <img src="https://latex.codecogs.com/png.latex?F_k"> telescope away, leaving the actual dynamics the same.</p>
<p>The situation should be unchanged if the Lagrangian depends or does not depend on <img src="https://latex.codecogs.com/png.latex?t">.</p>
<p>The Noether charge is slightly modified in this case. We have an extra term:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AK(t,%20q):%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5BF_%7B%5Cepsilon%7D(t,%20q)%5D%5Cbigg%7C_%7B%5Cepsilon=0%7D%0A"></p>
<p>And the Noether’s charge is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20p%5Ccdot%5Comega_Q(q)%20-%20K%0A"></p>
<p>The proof (sketched in a later section) differs from the original in that action doesn’t equal <img src="https://latex.codecogs.com/png.latex?0"> under variation, but instead equals the variation in the total derivative.</p>
</section>
<section id="equivalence-under-equivariance" class="level2">
<h2 class="anchored" data-anchor-id="equivalence-under-equivariance">Equivalence under Equivariance</h2>
<p>Here we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%20T%5CPhi_g(%5Cdot%20q))%20=%20%5Cchi(g)L(q,%20%5Cdot%20q)%0A"></p>
<p>If we have some <img src="https://latex.codecogs.com/png.latex?%5Cchi(g)%20%5Cin%20%5Cmathbb%7BR%7D_%7B%3E0%7D">, really we want to work in quotient space</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL%20%5Csim%20cL%0A"></p>
<p>for <img src="https://latex.codecogs.com/png.latex?c%20%5Cin%20%5Cmathbb%7BR%7D_%7B%3E0%7D">.</p>
<p>We’ve already covered this in the <a href="../../../game_theory/posts/dynamical_similarity/index.html">dynamical similarity</a> post so I won’t belabor it. Essentially we end up with</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20:=%20p%20%5Ccdot%20%5Comega_Q(q)%20-%20k%5Cint_%7Bt_0%7D%5Et%20L(q(t'),%20%5Cdot%20q(t'))dt'%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?k%20:=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Clog%5Cchi(g(%5Cepsilon))%5Cbigg%7C_%7B%5Cepsilon=0%7D">. The <img src="https://latex.codecogs.com/png.latex?%5Clog"> shows up due to maps between <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D_%7B%3E0%7D">.</p>
<p>Can we look at this with respect to “general representations”? I.e. more complex characters? It seems not really, we would need to have generalized Lagrangians (i.e.&nbsp;not just a scalar), which is out of scope of this post.</p>
<section id="equivariant-reduction" class="level3">
<h3 class="anchored" data-anchor-id="equivariant-reduction">Equivariant “Reduction”</h3>
<p>Equivariance won’t help us lower dimension the way quotienting by <img src="https://latex.codecogs.com/png.latex?G"> does, since it only tells us when different-looking Lagrangians describe the same trajectories up to scaling. But, it did allow use to produce new coordinates that index entire families of solutions. This isn’t “reduction” per se, but reparametrization</p>
<p>What should this look like? (Presented without proof, we saw the simplified version in the last Kepler proof).</p>
<p>There’s some representation <img src="https://latex.codecogs.com/png.latex?%5Crho(g):%20G%20%5Cto%20GL_n(V)"> and character (homomorphism) <img src="https://latex.codecogs.com/png.latex?a(g):%20G%20%5Cto%20%5Cmathbb%7BR%7D_%7B%3E0%7D"> <img src="https://latex.codecogs.com/png.latex?%0Aq'%20=%20%5Crho(g)q%0A"> <img src="https://latex.codecogs.com/png.latex?%0At'%20=%20a(g)t%0A"></p>
<p>Such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(a(g)t,%20%5Crho(g)q,%20%5Cfrac%7B%5Crho(g)%7D%7Ba(g)%7D%5Cdot%20q)%20=%20%5Cchi(g)L(t,%20q,%20%5Cdot%20q)%0A"></p>
<p>Even more generally, with <img src="https://latex.codecogs.com/png.latex?%5CPhi_g"> and <img src="https://latex.codecogs.com/png.latex?%5Ctau_g"> diffeomorphisms (not necessarily linear), hand-waving</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL%5C!%5Cleft(%0A%5Ctau_g(t,q),%5C;%0A%5CPhi_g(t,q),%5C;%0A%5Cfrac%7BD%5CPhi_g(q)%5C,%5Cdot%20q%20+%20%5Cpartial_t%20%5CPhi_g(t,q)%7D%7B%5Cpartial_t%20%5Ctau_g(t,q)%20+%20%5Cpartial_q%20%5Ctau_g(t,q)%5C,%5Cdot%20q%7D%0A%5Cright)%20=%20%5Cchi(g)L(t,%20q,%20%5Cdot%20q)%0A"></p>
<p>This would be cool if we wanted to “transport” our solutions around between frames.</p>
<!-- This is probably the morphological allometry picture -->
</section>
</section>
<section id="summary" class="level2">
<h2 class="anchored" data-anchor-id="summary">Summary</h2>
<p>Putting it together:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%20T%5CPhi_g(%5Cdot%7Bq%7D))%20=%20%5Cchi(g)L(t,%20q,%5Cdot%7Bq%7D)%20+%20%5Cfrac%7Bd%7D%7Bdt%7DF_g(t,%20q)%0A"></p>
<p>And we can define some equivalence relations in terms of gauge equivalence and equivariance.</p>
<p>As an aside:</p>
<p>It is interesting to consider if we wanted to consider conditions such that the group actions composed. That is,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_%7Bgh%7D(q),%20T%5CPhi_%7Bgh%7D(%5Cdot%7Bq%7D))%20=%20L(%5CPhi_g(%5CPhi_h(q)),%20T%5CPhi_%7Bg%7D(T%5CPhi_h(%5Cdot%20q)))%0A"></p>
<p>so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cchi(gh)L(t,%20q,%5Cdot%7Bq%7D)%20+%20%5Cfrac%7Bd%7D%7Bdt%7DF_%7Bgh%7D(t,%20q)%20=%20%5Cchi(g)%5Cchi(h)L(t,%20q,%20%5Cdot%20q)%20+%20%5Cchi(g)%5Cfrac%7Bd%7D%7Bdt%7DF_h(t,%20q)+%20%5Cfrac%7Bd%7D%7Bdt%7D%20F_g(t,%20%5CPhi_h(q))%0A"></p>
<p>We know <img src="https://latex.codecogs.com/png.latex?%5Cchi(gh)%20=%20%5Cchi(g)%5Cchi(h)">.</p>
<p>So we would need</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7DF_%7Bgh%7D(t,%20q)%20=%20%5Cchi(g)%5Cfrac%7Bd%7D%7Bdt%7DF_h(t,%20q)%20+%20%5Cfrac%7Bd%7D%7Bdt%7D%20F_g(t,%20%5CPhi_h(q))%0A"></p>
<p>This may be interesting if we ever want to classify Lagrangians.</p>
</section>
</section>
<section id="reduction" class="level1">
<h1>Reduction</h1>
<p>Now that we have some equivalence relations on <img src="https://latex.codecogs.com/png.latex?L">, it makes sense to work in “reduced” space of Lagrangians, modulo symmetry. We’ll look at the equivalence relations above, plus others. Let’s go through each type of reduction one-at-a-time.</p>
<section id="gauge-reduction" class="level2">
<h2 class="anchored" data-anchor-id="gauge-reduction">1. Gauge “Reduction”</h2>
<p>As established, if <img src="https://latex.codecogs.com/png.latex?%5Cexists%20F:%20%5Cmathbb%7BR%7D%20%5Ctimes%20Q%20%5Cto%20%5Cmathbb%7BR%7D"> such that <img src="https://latex.codecogs.com/png.latex?L'(t,%20q,%20%5Cdot%20q)%20=%20L(t,%20q,%20%5Cdot%20q)%20+%20%5Cfrac%7BdF%7D%7Bdt%7D">, we write <img src="https://latex.codecogs.com/png.latex?L'%20%5Csim%20L">.</p>
<p>What’s the point of this adding extra <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7BdF%7D%7Bdt%7D"> term? Why care about it?</p>
<p>For some systems, we may not have “symmetries”, but by adding an extra term we can enforce a quasi-symmetry on the system.</p>
<section id="example" class="level3">
<h3 class="anchored" data-anchor-id="example">Example</h3>
<p>Consider the following Lagrangian on <img src="https://latex.codecogs.com/png.latex?Q%20=%20%5Cmathbb%7BR%7D%5E2">, where <img src="https://latex.codecogs.com/png.latex?A(q)%20:%20%5Cmathbb%7BR%7D%5E2%20%5Cto%20%5Cmathbb%7BR%7D%5E2">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(t,%20q,%20%5Cdot%20q)%20=%5Cfrac%7B1%7D%7B2%7Dm(%5Cdot%20q%20%5Ccdot%20%5Cdot%20q)%20+%20A(q)%20%5Ccdot%20%5Cdot%20q%0A"></p>
<p>As written, the system is not invariant to rotation by <img src="https://latex.codecogs.com/png.latex?%5Ctheta">.</p>
<p>Let</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AR_%7B%5Ctheta%7D%20=%0A%5Cbegin%7Bbmatrix%7D%0A%5Ccos(%5Ctheta)%20&amp;%20-%5Csin(%5Ctheta)%20%5C%5C%0A%5Csin(%5Ctheta)%20&amp;%20%5Ccos(%5Ctheta)%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>And consider new coordinates <img src="https://latex.codecogs.com/png.latex?q'%20=%20R_%7B%5Ctheta%7Dq">, <img src="https://latex.codecogs.com/png.latex?%5Cdot%20q'%20=%20R_%7B%5Ctheta%7D%5Cdot%20q"></p>
<p>We know the first term is invariant to rotation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B1%7D%7B2%7Dm%7C%7C%5Cdot%20q'%7C%7C%5E2%20=%20%5Cfrac%7B1%7D%7B2%7Dm%7C%7CR_%7B%5Ctheta%7D%5Cdot%20q%7C%7C%5E2%20=%20%5Cfrac%7B1%7D%7B2%7Dm%7C%7C%5Cdot%20q%7C%7C%5E2%0A"></p>
<p>The second term transforms as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA(q')%20%5Ccdot%20%5Cdot%20q'%20=%20A(R_%7B%5Ctheta%7Dq)%20%5Ccdot%20R_%7B%5Ctheta%7D%5Cdot%20q%0A"></p>
<p>If there exists a function <img src="https://latex.codecogs.com/png.latex?F_%5Ctheta%20:%20Q%20%5Cto%20%5Cmathbb%7BR%7D"> such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA(R_%7B%5Ctheta%7Dq)%20%5Ccdot%20R_%7B%5Ctheta%7D%5Cdot%20q%0A=%0AA(q)%20%5Ccdot%20%5Cdot%20q%0A+%0A%5Cfrac%7Bd%7D%7Bdt%7D%5BF_%7B%5Ctheta%7D(q)%5D%0A"></p>
<p>then it would be quasi-invariant. When is this true?</p>
<p>Rearranging, we would have to have <img src="https://latex.codecogs.com/png.latex?%0A(R_%7B%5Ctheta%7D%5E%7B%5Ctop%7DA(R_%7B%5Ctheta%7Dq)%20-%20A(q))%5Cdot%20q%20=%20%5Cfrac%7Bd%7D%7Bdt%7D%20%5BF_%7B%5Ctheta%7D(q)%5D%0A"></p>
<p>Since it’s true that <img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%20%5BF_%7B%5Ctheta%7D(q)%5D%20=%20%5Cnabla%20F_%7B%5Ctheta%7D%20%5Ccdot%20%5Cdot%20q%0A"></p>
<p>So we conclude it is quasi-invariant iff <img src="https://latex.codecogs.com/png.latex?%0AR_%7B%5Ctheta%7D%5E%7B%5Ctop%7DA(R_%7B%5Ctheta%7Dq)%20-%20A(q)%20=%20%5Cnabla%20F_%7B%5Ctheta%7D%0A"></p>
<p>A function is only a gradient if the mixed partials are the same. So (skipping one or two steps) we end up needing</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(%5Cpartial_xA_y%20-%20%5Cpartial_yA_x)(q)%20=%20(%5Cpartial_xA_y%20-%20%5Cpartial_yA_x)(R_%7B%5Ctheta%7Dq)%0A"></p>
<p>Call <img src="https://latex.codecogs.com/png.latex?B%20:=%20(%5Cpartial_xA_y%20-%20%5Cpartial_yA_x)(q)">. So this is true if <img src="https://latex.codecogs.com/png.latex?B"> is invariant under rotation.</p>
<p>We will need <img src="https://latex.codecogs.com/png.latex?B"> in the next section.</p>
<section id="noether-charge" class="level4">
<h4 class="anchored" data-anchor-id="noether-charge">Noether Charge</h4>
<p>What is the Noether charge?<sup>2</sup> Let’s compute it. The transformation is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(t,%20q)%20%5Cmapsto%20(t,%20R_%7B%5Cepsilon%7Dq)%0A"></p>
<p>We need <img src="https://latex.codecogs.com/png.latex?%5Comega_Q(q)">, which is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5BR_%7B%5Cepsilon%7Dq%5D%7C_%7B%5Cepsilon=0%7D%20=%0A%5Cbegin%7Bbmatrix%7D%0A-%5Csin(%5Cepsilon)%20&amp;%20-%5Ccos(%5Cepsilon)%20%5C%5C%0A%5Ccos(%5Cepsilon)%20&amp;%20-%5Csin(%5Cepsilon)%0A%5Cend%7Bbmatrix%7D_%7B%5Cepsilon=0%7D%20q=%20%5Cbegin%7Bbmatrix%7D%0A0%20&amp;%20-1%20%5C%5C%0A1%20&amp;%200%0A%5Cend%7Bbmatrix%7Dq%20=%20(-y,%20x)%0A"></p>
<p>Then we need <img src="https://latex.codecogs.com/png.latex?K%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5BF_%7B%5Cepsilon%7D%5D%5Cbigg%7C_%7B%5Cepsilon=0%7D">.</p>
<p>We can rearrange the terms of the Lagrangian with gauge term to get an expression for <img src="https://latex.codecogs.com/png.latex?K">. Differentiating the quasi-invariance condition at <img src="https://latex.codecogs.com/png.latex?%5Cepsilon=0"> gives</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7DL(t,q_%5Cepsilon,%5Cdot%20q_%5Cepsilon)%5CBig%7C_%7B%5Cepsilon=0%7D%0A=%0A%5Cfrac%7Bd%7D%7Bdt%7DK(q)%0A"></p>
<p>Since we know the typical Noether charge formula along solutions, the left side can be replaced by the Noether charge:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Bp%5Ccdot%20%5Comega_Q(q)%5D%20=%20%5Cfrac%7Bd%7D%7Bdt%7DK(q)%0A"></p>
<p>Thus the adapted charge for the gauge:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Bp%5Ccdot%20%5Comega_Q(q)%20-%20K%5D%20=%200%20%5Cimplies%20J%20:=%20p%5Ccdot%20%5Comega_Q(q)%20-%20K%0A"></p>
<p>In the example, the conserved quantity is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20p%20%5Ccdot%20(-y,%20x)%20-%20K%0A"></p>
<p>We just need to compute this particular <img src="https://latex.codecogs.com/png.latex?K"> (this will be relatively difficult since we don’t have an functional expression for <img src="https://latex.codecogs.com/png.latex?A">, just some abstract criteria).</p>
<p>First, expanding <img src="https://latex.codecogs.com/png.latex?J"> in coordinates and plugging in the components of <img src="https://latex.codecogs.com/png.latex?p%20=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20m(x%5Cdot%20y%20-%20y%20%5Cdot%20x)%20+%20xA_y%20-%20yA_x%20-%20K%0A"></p>
<p>Call <img src="https://latex.codecogs.com/png.latex?%0AM%20:=%20xA_y%20-%20yA_x%20-%20K%0A"></p>
<p>Now, return to our definition of <img src="https://latex.codecogs.com/png.latex?K">:</p>
<p><img src="https://latex.codecogs.com/png.latex?K%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5BF_%7B%5Cepsilon%7D%5D%5Cbigg%7C_%7B%5Cepsilon=0%7D"></p>
<p>Since</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AA(R_%7B%5Ctheta%7Dq)%20%5Ccdot%20R_%7B%5Ctheta%7D%5Cdot%20q%0A=%0AA(q)%20%5Ccdot%20%5Cdot%20q%0A+%0A%5Cfrac%7Bd%7D%7Bdt%7D%5BF_%7B%5Ctheta%7D(q)%5D%0A"></p>
<p>We get</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla%20K(q)%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5BR_%7B%5Cepsilon%7D%5E%7B%5Ctop%7DAR_%7B%5Cepsilon%7Dq%5D%5Cbigg%7C_%7B%5Cepsilon=0%7D%0A"></p>
<p>We can expand the <img src="https://latex.codecogs.com/png.latex?R_%7B%5Cepsilon%7D"> using their Taylor approximations</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AR_%7B%5Cepsilon%7D%20%5Capprox%20I%20+%0A%5Cepsilon%0A%5Cbegin%7Bbmatrix%7D%0A0%20&amp;%20-1%20%5C%5C%0A1%20&amp;%200%20%5Cend%7Bbmatrix%7D%0A+%20O(%5Cepsilon%5E2)%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AR_%7B%5Cepsilon%7D%5E%7B%5Ctop%7D%20%5Capprox%20I%20-%0A%5Cepsilon%0A%5Cbegin%7Bbmatrix%7D%0A0%20&amp;%20-1%20%5C%5C%0A1%20&amp;%200%20%5Cend%7Bbmatrix%7D%0A+%20O(%5Cepsilon%5E2)%0A"></p>
<p>If we split on the coordinates and rearrange we get</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpartial_x%20K%20=%20-y%20%5Cpartial_x%20A_x+%20x%5Cpartial_y%20A_x%20+%20A_y%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cpartial_y%20K%20=%20-y%5Cpartial_x%20A_y%20+%20x%20%5Cpartial_y%20A_y%20-%20A_x%0A"></p>
<p>Also, the mixed-partials <img src="https://latex.codecogs.com/png.latex?K"> must agree</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpartial_y%20%5Cpartial_x%20K%20=%20%5Cpartial_x%20%5Cpartial_y%20K%0A"></p>
<p>If you do a bunch of algebra, and substitute the two equations we have for the partials of <img src="https://latex.codecogs.com/png.latex?K">, you can get</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ay%5Cpartial_x%20B%20-%20x%5Cpartial_y%20B%20=%200%0A"></p>
<p>Notice that, for <img src="https://latex.codecogs.com/png.latex?M">, we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpartial_x%20M%20=%20xB%20%20%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cpartial_y%20M%20=%20yB%0A"></p>
<p>So we get <img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla%20M%20=%20B(q)%5Ccdot(x,%20y)%0A"></p>
<p>And if <img src="https://latex.codecogs.com/png.latex?B"> is constant, then we integrate the partials and put them together to get <img src="https://latex.codecogs.com/png.latex?M%20=%20%5Cfrac%7B1%7D%7B2%7DB(x%5E2%20+%20y%5E2)">.</p>
<p>But also</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AM%20=%20xA_y%20-%20yA_x%20-%20K%0A"></p>
<p>So the Noether charge reduces ultimately to</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20mx%5Cdot%20y%20-%20my%5Cdot%20x%20+%20%5Cfrac%7B1%7D%7B2%7DB(x%5E2%20+%20y%5E2)%0A"></p>
<p>Assuming B is constant. This is the angular momentum (the mass is included in the <img src="https://latex.codecogs.com/png.latex?p"> term).</p>
<p>If <img src="https://latex.codecogs.com/png.latex?B"> is some other rotation-invariant function, we can integrate <img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla%20M%20=%20B(q)%5Ccdot(x,%20y)%0A"></p>
<p>to find the Noether charge.</p>
</section>
<section id="thoughts-on-example" class="level4">
<h4 class="anchored" data-anchor-id="thoughts-on-example">Thoughts on Example</h4>
<p>In summary:</p>
<ol type="1">
<li>given A term</li>
<li>compute <img src="https://latex.codecogs.com/png.latex?B%20:=%20(%5Cpartial_xA_y%20-%20%5Cpartial_yA_x)(q)">, rotation-invariant</li>
<li>find <img src="https://latex.codecogs.com/png.latex?M"> based on the components of <img src="https://latex.codecogs.com/png.latex?B"></li>
<li>this could give <img src="https://latex.codecogs.com/png.latex?K"> based on the difference between <img src="https://latex.codecogs.com/png.latex?p%20%5Ccdot%20%5Comega_Q(q)"> and <img src="https://latex.codecogs.com/png.latex?M">, or just use it to compute <img src="https://latex.codecogs.com/png.latex?J"></li>
</ol>
<p>If there’s an easier way to do this, I don’t know what it is. This is a bit ugly because <img src="https://latex.codecogs.com/png.latex?K"> is gauge-dependent. What we really want is a symbolic way to automatically get <img src="https://latex.codecogs.com/png.latex?K"> given the gauge. In the code we can compute <img src="https://latex.codecogs.com/png.latex?K"> numerically (or using autodiff).</p>
<p>You’d have to</p>
<ol type="1">
<li>decide if a quasi-symmetry exists</li>
<li>construct <img src="https://latex.codecogs.com/png.latex?F_%7B%5Cepsilon%7D"></li>
<li>differentiate it</li>
<li>Return <img src="https://latex.codecogs.com/png.latex?K"></li>
</ol>
<p>ChatGPT 5.2 says step 1 isn’t solvable. So we’d have to supply the symmetry ahead of time (like we already do with regular symmetries), then integrate the gradient of <img src="https://latex.codecogs.com/png.latex?K"> (which we can get becausse we can compute <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7DL(%5CPhi(q),%20T%5CPhi(%5Cdot%20q))%5Cbig%7C_%7B%5Cepsilon=0%7D">)</p>
<p>But none of this really matters, we don’t even have to compute <img src="https://latex.codecogs.com/png.latex?K"> because <img src="https://latex.codecogs.com/png.latex?K"> is just a boundary term, the gauge telescopes away in the actual dynamics.</p>
<p>Also note that this isn’t a true reduction, as it doesn’t really reduce dimension.</p>
</section>
</section>
</section>
<section id="configuration-space-reduction" class="level2">
<h2 class="anchored" data-anchor-id="configuration-space-reduction">2. Configuration Space Reduction</h2>
<p>Here we will reduce <img src="https://latex.codecogs.com/png.latex?Q"> into a simpler space, by action <img src="https://latex.codecogs.com/png.latex?G">.</p>
<p>We have a map <img src="https://latex.codecogs.com/png.latex?%5CPhi%20:%20G%20%5Ctimes%20Q%20%5Cto%20Q">.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?%5Cpi%20:%20Q%20%5Cto%20%5Cbar%20Q">, where <img src="https://latex.codecogs.com/png.latex?%5Cbar%20Q%20:=%20Q/G">. The projection map <img src="https://latex.codecogs.com/png.latex?%5Cpi"> sends each element <img src="https://latex.codecogs.com/png.latex?q%20%5Cin%20Q"> to its corresponding orbit in <img src="https://latex.codecogs.com/png.latex?%5Cbar%20Q">.</p>
<p>How does the associated tangent bundle change under quotient by <img src="https://latex.codecogs.com/png.latex?G">?</p>
<p>Before, for a Lie group <img src="https://latex.codecogs.com/png.latex?G">, we had the pair</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(g,%20%5Comega)%20%5Cin%20G%20%5Ctimes%20TG%0A"></p>
<p>If <img src="https://latex.codecogs.com/png.latex?g"> acts on itself (call the acting element <img src="https://latex.codecogs.com/png.latex?h%20%5Cin%20G">), we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ah%20%5Ccdot%20(g,%20%5Cdot%20g)%20=%20(hg,%20h%20%5Cdot%20g)%0A"></p>
<p>If we trivialize this</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ah%5Ccdot(g,%20%5Comega)%20=%20h%5Ccdot(g,%20g%5E%7B-1%7D%5Cdot%20g)%20=%20(hg,%20(hg)%5E%7B-1%7D(h%5Cdot%20g))%20=%20(hg,%20%5Comega)%0A"></p>
<p>So <img src="https://latex.codecogs.com/png.latex?%5Comega"> is unaffected by the quotient.</p>
<section id="subcase-1-euler-poincare-reduction" class="level3">
<h3 class="anchored" data-anchor-id="subcase-1-euler-poincare-reduction">Subcase 1: Euler-Poincare Reduction</h3>
<p>Let’s consider <img src="https://latex.codecogs.com/png.latex?Q%20=%20G">. Then <img src="https://latex.codecogs.com/png.latex?%5Cbar%20Q%20=%20G/G">, which is a single point. This is the same as our original <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html#euler-poincare-equation">reduced lagrangian</a>, which was <img src="https://latex.codecogs.com/png.latex?%5Cell(%5Ctext%7Bid%7D_G,%20%5Comega)">.</p>
<p>If you <a href="../../../game_theory/posts/noether_time/index.html#lie-groups">recall</a>, we completely got rid of any dependence on the actual manifold and worked completely in the Lie algebra. So we’ve already solved this case.</p>
</section>
<section id="subcase-2-lagrange-poincare-reduction" class="level3">
<h3 class="anchored" data-anchor-id="subcase-2-lagrange-poincare-reduction">Subcase 2: Lagrange-Poincare Reduction</h3>
<p>What if <img src="https://latex.codecogs.com/png.latex?Q"> is just some arbitrary manifold? What does it even mean to take <img src="https://latex.codecogs.com/png.latex?Q/G">, in general? We need to define an equivalence relation.</p>
<p>Consider the orbit of <img src="https://latex.codecogs.com/png.latex?q"> with respect to <img src="https://latex.codecogs.com/png.latex?G">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7BOrb%7D_G(q)%20:=%20%5C%7Bg%20%5Ccdot%20q%20%5C%20%7C%20%5C%20g%20%5Cin%20G%20%5C%7D%0A"></p>
<p>We say <img src="https://latex.codecogs.com/png.latex?q_1%20%5Csim%20q_2"> if there exists some <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G"> such that <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20q_1%20=%20q_2"> (they are in the same orbit).</p>
<p>So we are talking about <img src="https://latex.codecogs.com/png.latex?Q/G%20:=%20%5C%7B%5Ctext%7BOrb%7D_G(q)%20%7C%20%5C%20q%20%5Cin%20Q%5C%7D">, the set of orbits.</p>
<p>The problem is the induced equivalence relation of <img src="https://latex.codecogs.com/png.latex?TQ">. We need the velocities to transform:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(q,%20v)%20%5Cmapsto%20(g%20%5Ccdot%20q,%20T_q(g)v)%0A"></p>
<p>We can define another equivalence relation in this way. Two elements <img src="https://latex.codecogs.com/png.latex?(q_1,%20v_1)"> and <img src="https://latex.codecogs.com/png.latex?(q_2,%20v_2)"> are equivalent if there exists a <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G"> such that <img src="https://latex.codecogs.com/png.latex?(q_2,%20v_2)%20=%20(g%20%5Ccdot%20q_1,%20T_%7Bq_1%7D(g)v_1)">. This constructs <img src="https://latex.codecogs.com/png.latex?TQ/G">.</p>
<p>We also know there’s a map <img src="https://latex.codecogs.com/png.latex?%5Crho"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Crho:%20TQ/G%20%5Cto%20Q/G%20=%20%5Cbar%20Q%0A"></p>
<p>that just takes the equivalence classes on <img src="https://latex.codecogs.com/png.latex?TQ"> (which are among pairs <img src="https://latex.codecogs.com/png.latex?(q,%20v)">) to their corresponding equivalence classes in <img src="https://latex.codecogs.com/png.latex?Q"> (whice are among <img src="https://latex.codecogs.com/png.latex?q">). Basically, it forgets the velocity.</p>
<p>If we have some <img src="https://latex.codecogs.com/png.latex?%5Cbar%20q"> we can take the fiber <img src="https://latex.codecogs.com/png.latex?%5Crho%5E%7B-1%7D(%5Cbar%20q)">. This points back to the entire orbit of <img src="https://latex.codecogs.com/png.latex?q%20=%20%5Cbar%20q"> and associated velocities. The question becomes: how do we resolve the ambiguity of which <img src="https://latex.codecogs.com/png.latex?q"> to use as representative?</p>
<p>Pick an arbitrary <img src="https://latex.codecogs.com/png.latex?q_0%20%5Cin%20%5Cbar%20q"> as representative; all other <img src="https://latex.codecogs.com/png.latex?q"> in the orbit equal <img src="https://latex.codecogs.com/png.latex?g%20%5Ccdot%20q_0"> for some <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">. The equivalence classes over velocities of the vertical part can be represented as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(q_0,%20%5C%20%5Comega_Q(q_0))%0A"></p>
<p>for some <img src="https://latex.codecogs.com/png.latex?%5Comega%20%5Cin%20%5Cmathfrak%7Bg%7D"><sup>3</sup>.</p>
<p>Once a representative <img src="https://latex.codecogs.com/png.latex?q_0%20%5Cin%20%5Cbar%20q"> is fixed, the velocity component along the group orbit is determined by an element <img src="https://latex.codecogs.com/png.latex?%5Comega%20%5Cin%20%5Cmathfrak%7Bg%7D">. What remains is the component of the velocity transverse to the orbit. So we can decompose</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%20q%20=%20%5Cdot%20q_s%20+%20%5Comega_Q(q)%0A"></p>
<p>Where <img src="https://latex.codecogs.com/png.latex?%5Comega_Q(q)"> is along the orbit and <img src="https://latex.codecogs.com/png.latex?q_s"> is the projection onto <img src="https://latex.codecogs.com/png.latex?%5Cdot%20%7B%5Cbar%20q%7D%20%5Cin%20T_%7B%5Cbar%20q%7D(Q/G)">.</p>
<p>(This split isn’t canonical, it depends on a choice of connection on <img src="https://latex.codecogs.com/png.latex?Q%20%5Cto%20Q/G">.)</p>
</section>
</section>
<section id="phase-space-reduction" class="level2">
<h2 class="anchored" data-anchor-id="phase-space-reduction">3. Phase Space Reduction</h2>
<section id="subcase-1-marsden-weinstein-reduction" class="level3">
<h3 class="anchored" data-anchor-id="subcase-1-marsden-weinstein-reduction">Subcase 1: Marsden-Weinstein Reduction</h3>
<p>Note: I believe <a href="https://www.cds.caltech.edu/~marsden/bib/1974/01-MaWe1974/MaWe1974.pdf">this</a> is the original paper. I haven’t introduced symplectic geometry so I am omitting that language and keeping things informal.</p>
<p>Let’s say we have a system with Noether charges <img src="https://latex.codecogs.com/png.latex?J_1,%20J_2,%20...,%20J_n">. We can reduce this system by picking corresponding values for each charge <img src="https://latex.codecogs.com/png.latex?J_1%20=%20%5Cmu_1,%20J_2%20=%20%5Cmu_2">, etc., then setting <img src="https://latex.codecogs.com/png.latex?J_i(q,%20p)%20=%20%5Cmu_i">. Let’s look in more detail.</p>
<p>We have the space of pairs <img src="https://latex.codecogs.com/png.latex?(q,p)"> (the phase space aka cotangent bundle):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AT%5E*Q%20:=%20%5C%7B(q,p):%20q%5Cin%20Q,%5C;%20p%5Cin%20T_q%5E*Q%5C%7D%0A"></p>
<p>Suppose a Lie group <img src="https://latex.codecogs.com/png.latex?G"> acts on configurations:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CPhi:%20G%20%5Ctimes%20Q%20%5Cto%20Q%0A"> <img src="https://latex.codecogs.com/png.latex?%0A(g,q)%20%5Cmapsto%20%5CPhi_g(q)%0A"></p>
<p>with tangent map <img src="https://latex.codecogs.com/png.latex?%0AT%5CPhi_g:%20T_qQ%20%5Cto%20T_%7B%5CPhi_g(q)%7DQ%0A"></p>
<p>We have:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag%20%5Ccdot%20(q,p)%20:=%20(%5CPhi_g(q),%5C%20p')%0A"></p>
<p>We want our momentum functional to work as so:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap'(T%5CPhi_g(v))%20=%20p(v)%0A"></p>
<p>Conveniently, for <img src="https://latex.codecogs.com/png.latex?%5Comega%20%5Cin%20%5Cmathfrak%20g"> with induced vector field <img src="https://latex.codecogs.com/png.latex?%5Comega_Q"> on <img src="https://latex.codecogs.com/png.latex?Q"> (so <img src="https://latex.codecogs.com/png.latex?%5Comega_Q(q)%5Cin%20T_qQ">):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ(q,%20p)(%5Comega)%20=%20p(%5Comega_Q(q))%0A"></p>
<p>We’ve contructed the “momentum map” <img src="https://latex.codecogs.com/png.latex?J:%20T%5E*Q%20%5Cto%20%5Cmathfrak%7Bg%7D%5E*">. This is essentially the definition of a Noether charge <img src="https://latex.codecogs.com/png.latex?p%20%5Ccdot%20%5Comega_Q(q)"> but written in functional form (<img src="https://latex.codecogs.com/png.latex?p(q)%20=%20p%20%5Ccdot%20q">).</p>
<p>We can assign a value to the corresponding Noether charge for that symmetry<sup>4</sup>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ(q,p)%20=%20%5Cmu%0A"></p>
<p>Consider the <img src="https://latex.codecogs.com/png.latex?%5Cmu">-preserving symmetries (the <img src="https://latex.codecogs.com/png.latex?%5Cmu">’s are preserved under action by <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AG_%7B%5Cmu%7D%20:=%20%5C%7Bg%20%5Cin%20G%20%7C%20%5Ctext%7BAd%7D_g%5E*%5Cmu%20=%20%5Cmu%20%20%5C%7D%0A"></p>
<p>So we can think of the reduced state space as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(T%5E*Q)_%7B%5Cmu%7D%20=%20J%5E%7B-1%7D(%5Cmu)/G_%7B%5Cmu%7D%0A"></p>
<p>So we’ve restricted the phase space to a specific value of a conserved quantity and then quotiented out the symmetry corresponding to that quantity. This generalizes to multiple conserved quantities when they arise as components of a momentum map (for a product symmetry group) or via staged reduction (multiple commuting symmetries).</p>
<section id="example---keplers-third-law---marsden-weinstein-reduction" class="level4">
<h4 class="anchored" data-anchor-id="example---keplers-third-law---marsden-weinstein-reduction">Example - Kepler’s Third Law - Marsden-Weinstein Reduction</h4>
<p>Let’s return to <a href="../../../game_theory/posts/dynamical_similarity/index.html#keplers-third-law">Kepler’s third law</a> in 2-dimensions.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(q,%20%5Cdot%20q)%20=%20%5Cfrac%7Bm%7D%7B2%7D%7C%7C%5Cdot%20q%7C%7C%5E2%20-%20V(q)%0A"></p>
<p>Let <img src="https://latex.codecogs.com/png.latex?V(q)%20=%20-%5Cfrac%7Bk%7D%7B%7C%7Cq%7C%7C%7D"></p>
<p>Convert to polar coordinates, <img src="https://latex.codecogs.com/png.latex?q%20=%20(r%20%5Ccos%20%5Ctheta,%20r%20%5Csin%20%5Ctheta)">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL((r,%20%5Ctheta),%20(%5Cdot%20r,%20%5Cdot%20%5Ctheta))%20=%20%5Cfrac%7Bm%7D%7B2%7D(%5Cdot%20r%5E2%20+%20r%5E2%20%5Cdot%20%5Ctheta%5E2)%20+%20%5Cfrac%7Bk%7D%7Br%7D%0A"></p>
<p>Now let’s consider</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(r,%20%5Ctheta)%20%5Cmapsto%20(r,%20%5Ctheta%20+%20%5Cepsilon)%0A"></p>
<p>(Symmetry under <img src="https://latex.codecogs.com/png.latex?SO_2">)</p>
<p>The conserved quantity is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20mr%5E2%5Cdot%5Ctheta%0A"></p>
<p>Let’s fix this</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmu%20=%20mr%5E2%5Cdot%5Ctheta%0A"></p>
<p>This determines <img src="https://latex.codecogs.com/png.latex?%5Cdot%20%5Ctheta"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%20%5Ctheta%20=%20%5Cfrac%7B%5Cmu%7D%7Bmr%5E2%7D%0A"></p>
<p>(this is angular momentum, the same as the <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html#example-pendulum">free rotor</a>)</p>
<p>So</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL((r,%20%5Ctheta),%20(%5Cdot%20r,%20%5Cdot%20%5Ctheta))%20=%20%5Cfrac%7Bm%7D%7B2%7D(%5Cdot%20r%5E2%20+%20r%5E2%20(%5Cfrac%7B%5Cmu%7D%7Bmr%5E2%7D)%5E2)%20+%20%5Cfrac%7Bk%7D%7Br%7D%0A"></p>
<p>So we’ve reduced the Lagrangian to one dimension (<img src="https://latex.codecogs.com/png.latex?r">).</p>
<p>However, there’s a problem. The dynamics are correct, but this is no longer necessarily a Lagrangian. We’ve introduced a constraint (we haven’t looked at constraints yet). We’ll need a way to correct that (in the next section).</p>
<!-- Note that while this preserves the dynamics along this trajectory, we need the Routhian correction (derived above) to turn this back into a variational problem and make the actual numerical integration easier. -->
<p>The last step is to handle the phase space.</p>
<p>Start with: <img src="https://latex.codecogs.com/png.latex?%0AL(r,%5Ctheta,%5Cdot%20r,%5Cdot%5Ctheta)=%5Cfrac%7Bm%7D%7B2%7D%5Cleft(%5Cdot%20r%5E2+r%5E2%5Cdot%5Ctheta%5E2%5Cright)+%5Cfrac%7Bk%7D%7Br%7D.%0A"></p>
<p>Compute the canonical momenta:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_r:=%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20r%7D=m%5Cdot%20r,%5Cqquad%0Ap_%5Ctheta:=%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%5Ctheta%7D=mr%5E2%5Cdot%5Ctheta.%0A"></p>
<p>Invert: <img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%20r=%5Cfrac%7Bp_r%7D%7Bm%7D,%5Cqquad%20%5Cdot%5Ctheta=%5Cfrac%7Bp_%5Ctheta%7D%7Bmr%5E2%7D.%0A"></p>
<p>Then the Hamiltonian is the Legendre transform</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH%20=%20p_r%5Cdot%20r%20+%20p_%5Ctheta%5Cdot%5Ctheta%20-%20L%0A=%20%5Cfrac%7Bp_r%5E2%7D%7B2m%7D+%5Cfrac%7Bp_%5Ctheta%5E2%7D%7B2mr%5E2%7D-%5Cfrac%7Bk%7D%7Br%7D.%0A"></p>
<p>So restricting to <img src="https://latex.codecogs.com/png.latex?p_%5Ctheta=%5Cmu"> gives <img src="https://latex.codecogs.com/png.latex?%0AH_%5Cmu(r,p_r)=%5Cfrac%7Bp_r%5E2%7D%7B2m%7D+%5Cfrac%7B%5Cmu%5E2%7D%7B2mr%5E2%7D-%5Cfrac%7Bk%7D%7Br%7D.%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(r,%5Ctheta,p_r,p_%5Ctheta)%20=%20%5Cfrac%7Bp_r%5E2%7D%7B2m%7D%20+%20%5Cfrac%7Bp_%5Ctheta%5E2%7D%7B2mr%5E2%7D%20-%20%5Cfrac%7Bk%7D%7Br%7D%0A"></p>
<p>then on <img src="https://latex.codecogs.com/png.latex?p_%5Ctheta=%5Cmu"> it becomes</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH_%5Cmu(r,p_r)%20=%20%5Cfrac%7Bp_r%5E2%7D%7B2m%7D%20+%20%5Cfrac%7B%5Cmu%5E2%7D%7B2mr%5E2%7D%20-%20%5Cfrac%7Bk%7D%7Br%7D%0A"></p>
</section>
</section>
<section id="subcase-2-routh-reduction" class="level3">
<h3 class="anchored" data-anchor-id="subcase-2-routh-reduction">Subcase 2: Routh Reduction</h3>
<p>Note: Typically Routh reduction is for cyclic coordinates specifically. I’m looking at it a bit more generally.</p>
<p>We know, from the last section, that we have <img src="https://latex.codecogs.com/png.latex?p%20%5Ccdot%20%5Comega_Q(q)%20=%20%5Cmu">. We are looking to modify our variational problem to consider this constraint.</p>
<p>By Lagrange multipliers, we can augment the action with the constraint:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20(L(t,%20q,%20%5Cdot%20q)%20+%20%5Clambda(t)(J(t,%20q,%20%5Cdot%20q)%20-%20%5Cmu))dt%0A"></p>
<p>Since <img src="https://latex.codecogs.com/png.latex?%5Cmu"> is constant along the orbits of the symmetry, we just need to ensure the solutions move along that submanifold to ensure the new equation is variational. From the earlier section on Lagrange-Poincare, we can decompose <img src="https://latex.codecogs.com/png.latex?%5Cdot%20q"> as <img src="https://latex.codecogs.com/png.latex?v%20+%20u%5Comega_Q(q)">, where <img src="https://latex.codecogs.com/png.latex?u(t)"> is along the “symmetry direction” (with conserved quantity <img src="https://latex.codecogs.com/png.latex?%5Cmu">) and <img src="https://latex.codecogs.com/png.latex?v(t)"> is “transverse” to the symmetry direction.</p>
<p>Define:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BL%7D(t,%20q,%20v,%20u)%20:=%20%20L(t,%20q,%20v%20+%20u%5Ccdot%5Comega_Q(q))%0A"></p>
<p>This is the same as the original Lagrangian, just reparametrized.</p>
<p>Thus,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20%5Cmathcal%7BL%7D%7D%7B%5Cpartial%20u%7D%20=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D%20%5Cfrac%7B%5Cpartial%20%5Cdot%20q%7D%7B%5Cpartial%20u%7D%20=%20p%20%5Ccdot%20%5Comega_Q(q)%20=%20J%0A"></p>
<p>And we enforce <img src="https://latex.codecogs.com/png.latex?J%20=%20%5Cmu">.</p>
<p>Plugging back in to the action formula</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq,%20v,%20u,%20%5Clambda%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20(%5Cmathcal%7BL%7D(t,%20q,%20v,%20u)%20+%20%5Clambda(t)(%5Cfrac%7B%5Cpartial%20%5Cmathcal%7BL%7D%7D%7B%5Cpartial%20u%7D%20-%20%5Cmu))dt%0A"></p>
<p>Here the only subtlety is that <img src="https://latex.codecogs.com/png.latex?u"> is the symmetry-direction velocity (e.g.&nbsp;<img src="https://latex.codecogs.com/png.latex?u=%5Cdot%5Ctheta">), so the variation that produces the symmetry equation is really a variation of the symmetry coordinate <img src="https://latex.codecogs.com/png.latex?%5Ctheta">.</p>
<p>That means <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20u=%5Cdelta%5Cdot%5Ctheta=%5Cfrac%7Bd%7D%7Bdt%7D%5Cdelta%5Ctheta">, so after integrating by parts the stationarity condition is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%5CBig(%5Cpartial_u%5Cmathcal%20L%20+%20%5Clambda%5C,%5Cpartial%5E2_%7Buu%7D%5Cmathcal%20L%5CBig)%20=%200%0A"></p>
<p>Now that we have this, let’s try to modify <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BL%7D"> to cancel the <img src="https://latex.codecogs.com/png.latex?u">-chain rule term, without affect <img src="https://latex.codecogs.com/png.latex?q"> or <img src="https://latex.codecogs.com/png.latex?v">.</p>
<p>Consider</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BF%7D(t,%20q,%20v,%20u)%20=%20%5Cmathcal%7BL%7D(t,%20q,%20v,%20u)%20+%20g(u)%0A"></p>
<p>So (chain rule in shorthand) <img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20%5Cmathcal%7BF%7D%20=%20%5Cpartial_q%20%5Cmathcal%7BL%7D%5Cdelta%20q%20+%20%5Cpartial_v%20%5Cmathcal%7BL%7D%5Cdelta%20v%20+%20%5Cpartial_u%5Cmathcal%7BL%7D%5Cdelta%20u%20+%20g'(u)%5Cdelta%20u%0A"></p>
<p>So</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cpartial_u%20%5Cmathcal%7BL%7D%20+%20g'(u)%0A"></p>
<p>is the <img src="https://latex.codecogs.com/png.latex?u"> coefficient. Since the <img src="https://latex.codecogs.com/png.latex?u">-coefficient is <img src="https://latex.codecogs.com/png.latex?0"> along the desired solutions, we set <img src="https://latex.codecogs.com/png.latex?%5Cmu%20+%20g'(u)%20=%200">, and thus <img src="https://latex.codecogs.com/png.latex?g'(u)%20=%20-%5Cmu">, and <img src="https://latex.codecogs.com/png.latex?g(u)%20=%20-%5Cmu%20u%20+%20C">.</p>
<p>Thus, the corrected Lagrangian is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathcal%7BL%7D(t,%20q,%20v)%20=%20%5Cmathcal%7BL%7D(t,%20q,%20v,%20u_%7B%5Cmu%7D(t,%20q,%20v))%20-%20%5Cmu%20u_%7B%5Cmu%7D(t,%20q,%20v)%0A"></p>
<p>Which is the form of the Routhian.</p>
<section id="example---keplers-third-law---routh-reduction" class="level4">
<h4 class="anchored" data-anchor-id="example---keplers-third-law---routh-reduction">Example - Kepler’s Third Law - Routh Reduction</h4>
<p>Take our solution from the end of the last example:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL((r,%20%5Ctheta),%20(%5Cdot%20r,%20%5Cdot%20%5Ctheta))%20=%20%5Cfrac%7Bm%7D%7B2%7D(%5Cdot%20r%5E2%20+%20r%5E2%20(%5Cfrac%7B%5Cmu%7D%7Bmr%5E2%7D)%5E2)%20+%20%5Cfrac%7Bk%7D%7Br%7D%0A"></p>
<p>Subtract <img src="https://latex.codecogs.com/png.latex?%5Cmu%20u">. In this case motion is split into <img src="https://latex.codecogs.com/png.latex?r"> and <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> components,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL((r,%20%5Ctheta),%20(%5Cdot%20r,%20%5Cdot%20%5Ctheta))%20=%20%5Cfrac%7Bm%7D%7B2%7D(%5Cdot%20r%5E2%20+%20r%5E2%20(%5Cfrac%7B%5Cmu%7D%7Bmr%5E2%7D)%5E2)%20+%20%5Cfrac%7Bk%7D%7Br%7D%20-%20%5Cmu%20%5Cdot%20%5Ctheta%0A"> <img src="https://latex.codecogs.com/png.latex?%0AL((r,%20%5Ctheta),%20(%5Cdot%20r,%20%5Cdot%20%5Ctheta))%20=%20%5Cfrac%7Bm%7D%7B2%7D%5Cdot%20r%5E2%20+%20%5Cfrac%7Bm%7D%7B2%7D%20r%5E2%20%5Cfrac%7B%5Cmu%5E2%7D%7Bm%5E2r%5E4%7D%20+%20%5Cfrac%7Bk%7D%7Br%7D%20-%20%5Cfrac%7B%5Cmu%5E2%7D%7Bmr%5E2%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0AL((r,%20%5Ctheta),%20(%5Cdot%20r,%20%5Cdot%20%5Ctheta))%20=%20%5Cfrac%7Bm%7D%7B2%7D%5Cdot%20r%5E2%20%20+%20%5Cfrac%7Bk%7D%7Br%7D%20-%20%5Cfrac%7B%5Cmu%5E2%7D%7B2mr%5E2%7D%0A"></p>
<p>So the new <img src="https://latex.codecogs.com/png.latex?L"> is the variational formula that gives the dynamics that obeys the constraint.</p>
</section>
</section>
</section>
</section>
<section id="code" class="level1">
<h1>Code</h1>
<p>No code this time. There’s probably already enough here to implement Routh reduction, but I’m going to leave that for later, once I’ve thought more carefully about how it composes with the other notions of equivalence and reduction above.</p>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>We now have the start of a picture of how the Lagrangian works and reduces under symmetry. So far, we are still recapitulating well-known results, but I have a much better grasp on the subject than before. In subsequent posts, we will look at this picture an algebraic viewpoint and look at extensions. I also plan to look at applications of these principles to controls, games, and agents.</p>
</section>
<section id="read-more" class="level1">
<h1>Read More</h1>
<ul>
<li><p>Marsden &amp; Ratiu, Introduction to Mechanics and Symmetry</p></li>
<li><p>Marsden &amp; Weinstein (1974), “Reduction of symplectic manifolds with symmetry.”</p></li>
<li><p>Marsden, Ratiu &amp; Scheurle (2000), “Reduction theory and the Lagrange–Routh equations.”</p></li>
<li><p>Cendra, Marsden &amp; Ratiu (2001), “Lagrangian Reduction by Stages”</p></li>
<li><p>Strongly suspect ChatGPT has memorized <a href="https://www.michael-kraus.org/notes/euler-poincare-reduction/">this blog post</a> by Michael Kraus.</p></li>
</ul>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>It seems the full notion from physics of “gauge symmetry” or “gauge theory” from physics implies a fair amount of structure I have not yet introduced, so I avoid it. Really this is “variational equivalence or Lagrangian equivalence modulo exact 1-forms on path space.”↩︎</p></li>
<li id="fn2"><p>I used ChatGPT to assist with some of the algebra here, though I checked in thoroughly and I think it works. Even with ChatGPT and significant effort, I think the proof is inelegant. It’s not important to the overall throughline I’m trying to build so skip it if it seems confusing. I think in most cases you’d have some formula for <img src="https://latex.codecogs.com/png.latex?K"> and you’d just compute the derivative.↩︎</p></li>
<li id="fn3"><p>The <img src="https://latex.codecogs.com/png.latex?G">-action <img src="https://latex.codecogs.com/png.latex?%5CPhi"> needs to technically be “free and proper” for all of this to work out nicely such that <img src="https://latex.codecogs.com/png.latex?Q/G"> is a smooth manifold. Freeness: if we form the matrix whose columns are the velocity directions generated by each symmetry at the current state, that matrix has full column rank (no non-trivial nullspace). Properness: basically we require “large symmetry actions” to produce “different enough” parameters; we cannot send the symmetry parameter to infinity while the Jacobian and the transformed state both stay small. From the reduction point of view: 1. there is only one symmetry motion corresponding to a given “along-orbit” velocity, and 2. states that are “the same up to symmetry” stay close when you evolve or project them. <!-- 
ChatGPT: Reduction works cleanly only when a symmetry acts uniquely, stably, and everywhere. If the action is non-free, some symmetry directions do nothing at certain states, producing isotropy and gauge redundancy, so reduction requires constraints or gauge fixing rather than simple quotienting. If the action is non-proper, large symmetry transformations can act almost trivially, causing the reduced space to break into stratified regions with different effective dimensions, so reduction becomes piecewise and regime-dependent. If a symmetry is broken (explicitly, spontaneously, or by constraints), conserved quantities disappear or split, leading to bifurcations, branching solutions, and model switching, meaning reduction is only valid locally or conditionally. Together, gauge redundancy, stratification, and symmetry breaking explain why real systems yield not one reduced model, but a hierarchy of reduced descriptions glued together by constraints and transitions. 
In physics, non-free symmetries occur when some transformations leave a state unchanged (isotropy), creating redundant variables that require constraints or gauge fixing (e.g., rotations at the origin, electromagnetic potentials). In games, the same structure appears when multiple strategy labels represent the same effective outcome (e.g., interchangeable players at symmetric profiles), producing degenerate directions where best responses or learning dynamics are not uniquely defined. In both cases, reduction cannot simply quotient the symmetry; it must instead handle redundancy explicitly, or the reduced dynamics become ambiguous.
In physics, non-proper actions lead to reduced spaces that split into strata with different effective dimensions (e.g., generic motion versus collision states, orbits collapsing at special configurations), so the reduced dynamics change qualitatively across regimes. In games, the analogue is when symmetry collapses at special strategy profiles (e.g., identical strategies or equilibrium manifolds), causing the “reduced strategy space” to behave differently near symmetric points than in generic play. In both settings, reduction becomes piecewise, and algorithms must detect regime changes rather than rely on a single smooth reduced model.
In physics, broken symmetry—explicit, spontaneous, or constraint-induced—destroys conserved quantities and produces bifurcations, where continuous families of symmetric solutions split into distinct branches (e.g., buckling, phase transitions, contact constraints). In games, symmetry breaking appears when incentives, information, or constraints differentiate players, turning symmetric equilibria into unstable points and creating role differentiation or equilibrium selection. In both cases, reduction is only valid locally or conditionally, and the correct reduced description depends on which branch or regime the system occupies.
-->↩︎</p></li>
<li id="fn4"><p>I suppress the individual J_i and p_i from here out, but this can be done for each Noether charge.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Geometric Controls</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/game_theory/posts/reduction/</guid>
  <pubDate>Sat, 20 Dec 2025 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/game_theory/posts/reduction/print_gallery_mc_escher_1956.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Inspection Bias</title>
  <link>https://demonstrandom.com/ml/posts/inspection_bias/</link>
  <description><![CDATA[ 





<!-- Need L(S) where S a subset -->
<!-- A "persistent" subset is one with low turnover. -->
<!-- Doob h transform -->
<p><a href="https://en.wikipedia.org/wiki/Ad_Parnassum"><img src="https://demonstrandom.com/ml/posts/inspection_bias/Paul_Klee_Ad_Parnassum_1932.jpg" class="img-fluid"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>Suppose we have a population of objects of different lifespans (starting at different times). Given a sample from a specific time point, we should expect “most” of the objects we see to be drawn from objects of longer life spans. Let’s briefly look into this phenomenon.</p>
<p>All these results all well-known in the literature, under “renewal theory”, “Palm theory”, “length-based sampling”.</p>
</section>
<section id="lifespans" class="level1">
<h1>Lifespans</h1>
<p>Assume objects are created over time with a constant-rate process.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?A_%7B%5Cvarepsilon%7D%20:=%20%5Ctext%7BObject%20alive%20anywhere%20in%7D%20%5C%20%5Bt,%20t%20+%20%5Cvarepsilon%5D"> and call “lifespan” of an object <img src="https://latex.codecogs.com/png.latex?L"><sup>1</sup>.</p>
<p>We are interested in the following distribution:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%7BL%7CA_%7B%5Cepsilon%7D%7D(%5Cell)%0A"></p>
<p>by Bayes’ rule</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%7BL%7CA_%7B%5Cepsilon%7D%7D(%5Cell)%20=%20%5Cfrac%7BP(A_%7B%5Cvarepsilon%7D%20%7C%20%5C%20L%20=%20%5Cell%20)%20p_%7BL%7D(%5Cell)%7D%7BP(A_%7B%5Cepsilon%7D)%7D%0A"></p>
<p>If the objects lifespan is <img src="https://latex.codecogs.com/png.latex?%5BS,%20S%20+%20L%5D">, then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(A_%7B%5Cvarepsilon%7D%20%7C%20L%20=%20%5Cell)%20=%20P(t_0%20-%20%5Cell%20%5Cleq%20S%20%5Cleq%20t_0%20+%20%5Cvarepsilon)%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?S"> is the birth of the object. Let’s assume <img src="https://latex.codecogs.com/png.latex?S"> is roughly constant density. Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(A_%7B%5Cvarepsilon%7D%20%7C%20L%20=%20%5Cell)%20%5Cpropto%20%5Cell%20+%20%5Cvarepsilon%0A"></p>
<p>Rename <img src="https://latex.codecogs.com/png.latex?P(L%20=%20%5Cell)"> to <img src="https://latex.codecogs.com/png.latex?p_L(%5Cell)">. We know:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(A_%7B%5Cvarepsilon%7D)%20=%20%5Cint%20P(A_%7B%5Cvarepsilon%7D%20%7C%20L%20=%20%5Cell)p_L(%5Cell)d%5Cell%0A"></p>
<p>so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(A_%7B%5Cvarepsilon%7D)%20=%20%5Cint%20c(%5Cell%20+%20%5Cvarepsilon)p_%7BL%7D(%5Cell)d%5Cell%0A"></p>
<p>for some constant <img src="https://latex.codecogs.com/png.latex?c">, and splitting this up we get</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(A_%7B%5Cvarepsilon%7D)%20=%20c(E%5BL%5D%20+%20%5Cvarepsilon)%0A"></p>
<p>Plugging this in, we get</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%7BL%7CA_%7B%5Cepsilon%7D%7D(%5Cell)%20=%20%5Cfrac%7B(%5Cell%20+%20%5Cvarepsilon)p_L(%5Cell)%7D%7B%5Cmathbb%7BE%7D%5BL%5D%20+%20%5Cvarepsilon%7D%0A"></p>
<p>For <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon%20=%200">, we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%7BL%7CA%7D(%5Cell%20)=%20%5Cfrac%7B%5Cell%20p_L(%5Cell)%7D%7B%5Cmathbb%7BE%7D%5BL%5D%7D%0A"></p>
<p>This is ultimately dependent on a choice of distribution over <img src="https://latex.codecogs.com/png.latex?L">, and the constant birth rate.</p>
<p>This also implies that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%7BE%7D%5BL%20%7C%20A%5D%20=%20%5Cfrac%7B%5Cmathbb%7BE%7D%5BL%5E2%5D%7D%7B%5Cmathbb%7B%5Cmathbb%7BE%7D%7D%5BL%5D%7D%0A"></p>
<p>(which is strictly larger than <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5BL%5D"> for non-degenerate <img src="https://latex.codecogs.com/png.latex?L">).</p>
<p>Also</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%7BE%7D%5BL%20%7C%20A%5D%20-%20%5Cmathbb%7BE%7D%5BL%5D%20=%20%5Cfrac%7B%5Ctext%7BVar%7D(L)%7D%7B%5Cmathbb%7BE%7D%5BL%5D%7D%0A"></p>
<section id="exponential" class="level2">
<h2 class="anchored" data-anchor-id="exponential">Exponential</h2>
<p>Let’s try exponential (which corresponds to “random death”/constant hazard rate)</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL%20%5Csim%20%5Ctext%7BExp%7D(%5Clambda)%0A"> <img src="https://latex.codecogs.com/png.latex?%0Ap_%7BL%7D(%5Cell)%20=%20%5Clambda%20e%5E%7B-%5Clambda%20%5Cell%7D%0A"></p>
<p>Then we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%7BE%7D%5BL%5D%20=%20%5Cfrac%7B1%7D%7B%5Clambda%7D%0A"></p>
<p>The conditional density is thus</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%7BL%20%7C%20A%7D(%5Cell)%20=%20%5Clambda%5E2%20%5Cell%20e%5E%7B-%5Clambda%20%5Cell%7D%0A"></p>
<p>Which is a gamma distribution.</p>
<p>Even though exponential lifetimes are “memoryless”, the population is not. Memorylessness is not preserved under selection-by-survival.</p>
<p>Also: if the variation in the population is large, the bias can be large.</p>
</section>
<section id="gamma" class="level2">
<h2 class="anchored" data-anchor-id="gamma">Gamma</h2>
<p>Let’s do the gamma distribution.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL%20%5Csim%20%5Ctext%7BGamma%7D(k,%20%5Clambda)%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_L(%5Cell)%20=%20%5Cfrac%7B%5Clambda%5Ek%7D%7B%5CGamma(k)%7D%5Cell%5E%7Bk-1%7De%5E%7B-%5Clambda%20%5Cell%7D%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%7BE%7D%5BL%5D%20=%20%5Cfrac%7Bk%7D%7B%5Clambda%7D%0A"></p>
<p>By length bias formula:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_%7BL%20%7C%20A%7D(%5Cell)%20=%20%5Cfrac%7B%5Clambda%5E%7Bk%20+%201%7D%7D%7B%5CGamma(k%20+%201)%7D%5Cell%5Eke%5E%7B-%5Clambda%20%5Cell%7D%0A"></p>
<p>which is also a gamma</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL%20%5Csim%20%5Ctext%7BGamma%7D(k%20+%201,%20%5Clambda)%0A"></p>
<p>with expected value <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5BL%20%7C%20A%5D%20=%20%5Cfrac%7Bk%20+%201%7D%7B%5Clambda%7D%20=%20%5Cmathbb%7BE%7D%5BL%5D%20+%20%5Cfrac%7B1%7D%7B%5Clambda%7D">.</p>
<p>The Gamma shape parameter measures how many “chances to die” have already been survived. Observing an object at a random time guarantees at least one “survival”. So we increase the shape by one.</p>
</section>
<section id="log-uniform" class="level2">
<h2 class="anchored" data-anchor-id="log-uniform">Log Uniform</h2>
<p>This example is just to show how strong the effect can be.</p>
<p>Let’s say we have a log-uniform distribution over 10 orders of magnitude.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_L(%5Cell)%20=%20%5Cfrac%7B1%7D%7B%5Cell%20%5Cln(b/a)%7D%0A"></p>
<p>Multiplying by <img src="https://latex.codecogs.com/png.latex?%5Cell"> gives a constant. So the new distribution is uniform!</p>
<p>Let’s look at the top decile</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(L%20%5Cin%20%5B10%5E9,%2010%5E10%5D)%20=%200.1%0A"> <img src="https://latex.codecogs.com/png.latex?%0AP(L%20%5Cin%20%5B10%5E9,%2010%5E10%5D%20%7C%20A)%20=%20%5Cfrac%7B10%5E10%20-%2010%5E9%7D%7B10%5E10%20-%201%7D%20%5Capprox%200.9%0A"></p>
<p>So now most of the mass lives in the top decile!</p>
</section>
</section>
<section id="population-traits" class="level1">
<h1>Population Traits</h1>
<p>Let’s now connect the lifespan to a set of “traits”. So <img src="https://latex.codecogs.com/png.latex?L%20=%20f(%5Ctheta_1,%20%5Ctheta_2,%20...,%20%5Ctheta_n)"></p>
<p>To simplify further, assume <img src="https://latex.codecogs.com/png.latex?f(%5Ctheta)%20=%20a%20+%20%5Csum_i%20b_i%20%5Ctheta_i%20=%20a%20+%20b%5E%7B%5Ctop%7D%5Ctheta">. So a</p>
<p>What happens to traits in our sample? We should expect the positively correlated <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> to be overrepresented in the sample, and vice-versa.</p>
<p>In fact</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%7BE%7D%5BL%20%7C%20A%5D%20-%20%5Cmathbb%7BE%7D%5BL%5D%20=%20%5Cfrac%7B%5Ctext%7BVar%7D(L)%7D%7B%5Cmathbb%7BE%7D%5BL%5D%7D%20=%20%5Cfrac%7B%5Ctext%7BVar%7D(a%20+%20b%5E%7B%5Ctop%7D%5Ctheta)%7D%7Ba%20+%20b%5E%7B%5Ctop%7D%5Cmu_%7B%5Ctheta%7D%7D%20=%20%5Cfrac%7Bb%5E%7B%5Ctop%7D%5Ctext%7BVar%7D(%5Ctheta)b%7D%7Ba%20+%20b%5E%7B%5Ctop%7D%5Cmu_%7B%5Ctheta%7D%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmu_%7B%5Ctheta%7D"> is <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5B%5Ctheta%5D">. Since <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> is a vector, <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BVar%7D(%5Ctheta)"> is actual a matrix: the covariance of <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> with itself.</p>
<p>This entire expression shows that the component of variability <em>aligned with <img src="https://latex.codecogs.com/png.latex?b"></em> is what drives the sample bias. So if all the bias is “orthogonal” to <img src="https://latex.codecogs.com/png.latex?b">, there will be little selection bias, but if the bias is “in the direction of” <img src="https://latex.codecogs.com/png.latex?b">, there will be substantial selection bias. This is interesting as it grants us a “direction” based purely on the persistence of objects, which we can tie to geometry.</p>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>In the typical statistical story, we are interested in information about the population, and we observe a sample obtained through a random process to infer the relevant information. I’m interested in two related statistical concepts. That is:</p>
<ol type="1">
<li><p>We know the population and the sampling process, and we are interested in the properties of the sample (this example).</p></li>
<li><p>We know the population and the sample, and we are interested in what process was used to obtain the sample.</p></li>
</ol>
<p>This example is interesting because we managed to derive a “direction” purely from conditioning on persistence.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>The <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon"> window avoids any issues with measure zero that I’m too lazy to think through.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Statistics</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/ml/posts/inspection_bias/</guid>
  <pubDate>Tue, 16 Dec 2025 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/ml/posts/inspection_bias/Paul_Klee_Ad_Parnassum_1932.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Dynamical Similarity and Equivariant Symmetry</title>
  <link>https://demonstrandom.com/game_theory/posts/dynamical_similarity/</link>
  <description><![CDATA[ 





<p><img src="https://demonstrandom.com/game_theory/posts/dynamical_similarity/Newton-Kepler.png" class="img-fluid"></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>We have continued our investigation of <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html">geometric controls</a> by investigating conserved quantities derived from <a href="../../../game_theory/posts/noether_geometric_controls/index.html">Noether’s (first) theorem</a>. Here we look at a slight extension, where the Noether charge is not conserved for the system itself, but across classes of systems.</p>
<p>Here, we will look at a particular case of this phenomenon: dynamical similarity.</p>
<p>Note: Much of this material was briefly included in the <a href="../../../game_theory/posts/noether_time/index.html">last post</a> prior to a refactor (for cleaner conceptual organization).</p>
<p>AI disclosure: I had ChatGPT draft the one bridge section required to complete the refactor (the “Modified Noether” section).</p>
</section>
<section id="background" class="level1">
<h1>Background</h1>
<p>Given <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G"> for some group <img src="https://latex.codecogs.com/png.latex?G">, there is a group action <img src="https://latex.codecogs.com/png.latex?q%20%5Cmapsto%20%5CPhi_g(q)%20=%20g%20%5Ccdot%20q"> (and associated tangent maps) that the Lagrangian is invariant to:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%20T%5CPhi_g(%5Cdot%20q))%20=%20L(q,%20%5Cdot%20q)%0A"></p>
<p>But the Euler-Lagrange equations are homogeneous in <img src="https://latex.codecogs.com/png.latex?L">. That is, multiplying <img src="https://latex.codecogs.com/png.latex?L"> by a nonzero constant <img src="https://latex.codecogs.com/png.latex?%5Calpha"> doesn’t change the equations of motion<sup>1</sup>.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%20%5Cleft(%20%5Cfrac%7B%5Cpartial%20(%5Calpha%20L)%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Cright)%0A-%20%5Cfrac%7B%5Cpartial%20(%5Calpha%20L)%7D%7B%5Cpartial%20q%7D%20=%20%5Calpha%5Cbigg(%5Cfrac%7Bd%7D%7Bdt%7D%20%5Cleft(%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Cright)%0A-%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5Cbigg)%0A"></p>
<p>This suggests a looser restraint on the Lagrangian:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%20T%5CPhi_g(%5Cdot%20q))%20=%20%5Cchi(g)%20%5Ccdot%20L(q,%20%5Cdot%20q)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cchi:%20G%20%5Cto%20%5Cmathbb%7BR%7D_%7B%3E0%7D"> is a group homomorphism<sup>2</sup>, called the “character”. Note that if <img src="https://latex.codecogs.com/png.latex?%5Cforall%20g%5Cin%20G">, <img src="https://latex.codecogs.com/png.latex?%5Cchi(g)%20=%201">, we have the original invariance condition.</p>
<p>In this case, the Lagrangian is not invariant but is instead “equivariant”.</p>
<p>When the symmetry is only equivariant, the usual Noether quantity is no longer conserved. Instead, it drifts predictably, as determined by the scaling factor. The combination (with the integral correction) is the piece that remains constant across similar systems.</p>
<section id="modified-noether" class="level2">
<h2 class="anchored" data-anchor-id="modified-noether">Modified Noether</h2>
<p>Given this, how is the Noether charge modified?</p>
<p>Assume we have a transformation depending on a small parameter <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> and the Lagrangian transforms by a scalar factor</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(q_%5Cepsilon,%20%5Cdot%20q_%5Cepsilon)%0A=%0A%5Cchi(%5Cepsilon)%5C,%20L(q,%5Cdot%20q)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cchi(0)=1"> and <img src="https://latex.codecogs.com/png.latex?%5Cchi"> is smooth.</p>
<p>For the symmetries we care about, we can write</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cchi(%5Cepsilon)=e%5E%7Bk%5Cepsilon%7D%0A"></p>
<p>for some constant <img src="https://latex.codecogs.com/png.latex?k">.</p>
<p>Differentiate both sides at <img src="https://latex.codecogs.com/png.latex?%5Cepsilon%20=%200">.</p>
<p>Left-hand side:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%0A%5BL(q_%5Cepsilon,%20%5Cdot%20q_%5Cepsilon)%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Cbig(%20p%20%5Ccdot%20%5Comega_Q(q)%20%5Cbig)%0A"></p>
<p>(plus any <a href="../../../game_theory/posts/noether_time/index.html#g-invariance-and-noether-charge">time-related terms</a>. I omitted those here but they pass through as you’d expect.)</p>
<p>Right-hand side:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%0A%5Cbig(%5Cchi(%5Cepsilon)%20L(q,%5Cdot%20q)%5Cbig)%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%0A%5Cchi'(0)%5C,L(q,%5Cdot%20q)%0A=%0Ak%5C,L(q,%5Cdot%20q)%0A"></p>
<p>Equating both expressions gives the equivariant Noether identity:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7B%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Cbig(%20p%20%5Ccdot%20%5Comega_Q(q)%20%5Cbig)%0A=%0Ak%5C,L(q,%5Cdot%20q)%0A%7D%0A"></p>
<p>This replaces the conservation law of the invariant case.</p>
<p>Integrating in time, the combination</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7B%0AJ(t)%0A=%0Ap%20%5Ccdot%20%5Comega_Q(q)%0A%5C;-%5C;%0Ak%20%5C!%5Cint_%7Bt_0%7D%5E%7Bt%7D%20L(q(t'),%5Cdot%20q(t'))%5C,%20dt'%0A%7D%0A"></p>
<p>is constant across all trajectories related by the symmetry.</p>
<p>For <img src="https://latex.codecogs.com/png.latex?k=0"> (i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Cchi(%5Cepsilon)=1">), we recover the usual Noether charge.</p>
</section>
<section id="reintroducing-time" class="level2">
<h2 class="anchored" data-anchor-id="reintroducing-time">Reintroducing Time</h2>
<p>When we impose an <em>equivariant</em> symmetry on the Lagrangian,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%5C,T%5CPhi_g(%5Cdot%20q))%20=%20%5Cchi(g)%5C,L(q,%5Cdot%20q),%0A"></p>
<p>the usual continuous Noether statement produces the modified identity</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D(p%5Ccdot%5Cdelta%20q)%20=%20k%5C,L%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cchi(e%5E%5Cepsilon)=e%5E%7Bk%5Cepsilon%7D%0A"></p>
<p>and the conserved quantity</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20p%5Ccdot%5Cdelta%20q%20%5C;-%5C;%20k%5C!%5Cint_%7Bt_0%7D%5Et%20L%5C,dt'.%0A"></p>
<p>At first glance, this seems to require an additional term in the computation of the Noether charge.</p>
<p>What happens if we reintroduce time?</p>
<p>We have extended Lagrangian:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20L(%5Ctilde%20q,%5Cdot%7B%5Ctilde%20q%7D)%0A=%0A%5Cdot%20t%5C,L%5C!%5Cleft(q,%5Cfrac%7B%5Cdot%20q%7D%7B%5Cdot%20t%7D%5Cright).%0A"></p>
<p>Recall</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20J%0A=%0A%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20t%7D%0A%5Cleft.%5Cfrac%7Bd%20t_%5Cepsilon%7D%7Bd%5Cepsilon%7D%5Cright%7C_%7B%5Cepsilon=0%7D%0A%5C;+%5C;%0Ap%5Ccdot%0A%5Cleft.%5Cfrac%7Bd%20q_%5Cepsilon%7D%7Bd%5Cepsilon%7D%5Cright%7C_%7B%5Cepsilon=0%7D%0A"></p>
<p>with no additional terms.</p>
<p>For the scaling symmetry</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(t,q)%20%5Cmapsto%20(%5Clambda%5E%7B%5Calpha%7D%20t,%5C;%20%5Clambda%20q)%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Clambda%20=%20e%5E%7B%5Cepsilon%7D%0A"></p>
<p>we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%20t_%5Cepsilon%7D%7Bd%5Cepsilon%7D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%0A%5Calpha%20t%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%20q_%5Cepsilon%7D%7Bd%5Cepsilon%7D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%0Aq%0A"></p>
<p>so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%0A=%0A-%5C,%5Calpha%20H%20t%20+%20p%20q,%0A"></p>
<p>which is exactly the form we derived in the example section.</p>
<p>Since we know</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D(p%5Ccdot%5Cdelta%20q)%20=%20k%5C,L%0A"></p>
<p>and the extended-time identity</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D(-%5Calpha%20H%20t%20+%20p%20q)%20=%200%0A"></p>
<p>hold at the same time, we can subtract:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Calpha%5C,%5Cfrac%7Bd%7D%7Bdt%7D(H%20t)%0A=%0Ak%5C,L%0A"></p>
<p>Integrating from <img src="https://latex.codecogs.com/png.latex?t_0"> to <img src="https://latex.codecogs.com/png.latex?t">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Calpha%20H%20t%20-%20%5Calpha%20H(t_0)t_0%0A=%0Ak%5C!%5Cint_%7Bt_0%7D%5E%7Bt%7D%20L%5C,dt'.%0A"></p>
<p>So (up to a constant)</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A-%5C,k%5C!%5Cint%20L%5C,dt%0A=%0A-%5C,%5Calpha%20H%20t%0A"></p>
<p>So (assuming <img src="https://latex.codecogs.com/png.latex?L"> is homogeneous, has a Hamiltonian and the symmetry rescales time) we actually don’t have to compute that integral! This entire formulation is equivalent to our <a href="../../../game_theory/posts/noether_time/index.html">extended Noether</a> framework!</p>
</section>
</section>
<section id="examples" class="level1">
<h1>Examples</h1>
<section id="homogeneous-potentials" class="level2">
<h2 class="anchored" data-anchor-id="homogeneous-potentials">Homogeneous Potentials</h2>
<p>Let’s look at dynamical similarity.</p>
<p>Suppose we have:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(q,%20%5Cdot%20q)%20=%20%5Cfrac%7B1%7D%7B2%7Dm%7C%5Cdot%20q%7C%5E2%20-%20V(q)%0A"></p>
<p>i.e.&nbsp;movement under some potential <img src="https://latex.codecogs.com/png.latex?V(q)">. Let’s assume the potential <img src="https://latex.codecogs.com/png.latex?V(q)"> is homogeneous of degree <img src="https://latex.codecogs.com/png.latex?k">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(%5Clambda%20q)%20=%20%5Clambda%5E%7Bk%7DV(q)%0A"></p>
<p>and we have symmetry of form:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(t,%20q)%20%5Cmapsto%20(%5Clambda%5E%7B%5Calpha%7Dt,%20%5Clambda%20q)%0A"></p>
<p>With <img src="https://latex.codecogs.com/png.latex?%5Clambda%20=%20e%5E%7B%5Cepsilon%7D"> (so it’s infinitesimal) and <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%201-k/2"> (which causes all of <img src="https://latex.codecogs.com/png.latex?q,%20%5Cdot%20q,%20V,%20K"> to scale homogeneously: <img src="https://latex.codecogs.com/png.latex?q"> scales as <img src="https://latex.codecogs.com/png.latex?%5Clambda">, <img src="https://latex.codecogs.com/png.latex?%5Cdot%20q"> as <img src="https://latex.codecogs.com/png.latex?%5Clambda%5E%7B1-%5Calpha%7D">, <img src="https://latex.codecogs.com/png.latex?K"> as <img src="https://latex.codecogs.com/png.latex?%5Clambda%5E%7B2(1-%5Calpha)%7D">, <img src="https://latex.codecogs.com/png.latex?V"> as <img src="https://latex.codecogs.com/png.latex?%5Clambda%5Ek">).</p>
<p>That is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5Clambda%20q,%20%5Clambda%5E%7B1-%5Calpha%7D%20%5Cdot%20q)%20=%20%5Cfrac%7B1%7D%7B2%7Dm%7C%5Clambda%5E%7B1-%5Calpha%7D%5Cdot%20q%7C%5E2%20-%20V(%5Clambda%20q)%20=%20%5Clambda%5Ek(%5Cfrac%7B1%7D%7B2%7Dm%7C%5Cdot%20q%7C%5E2%20-%20V(q))%20=%20%5Clambda%5E%7Bk%7DL(q,%20%5Cdot%20q)%0A"></p>
<p>(since <img src="https://latex.codecogs.com/png.latex?2-2%5Calpha%20=%20k">).</p>
<p>Let’s compute the Noether charge:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20t%7D%5Cfrac%7B%5Cpartial%20t%7D%7B%5Cpartial%20%5Cepsilon%7D%20%5Cbiggr%7C_%7B%5Cepsilon=0%7D%20+%20%5C%20p%5Ccdot%20%5Comega_Q(q)%0A"> <img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20t%7D%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Be%5E%7B%5Calpha%5Cepsilon%7Dt%5D%20%5Cbiggr%7C_%7B%5Cepsilon=0%7D%20+%20%5C%20p%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Be%5E%7B%5Cepsilon%7Dq%5D%7C_%7B%5Cepsilon=0%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20t%7D%5Calpha%20t%20+%20%5C%20p%20q%0A"></p>
<p>And from a previous example we know</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20t%7D%20=%20-H%0A"></p>
<p>so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20-%5Calpha%20Ht%20+%20pq%0A"></p>
<p>This specializes to some known cases:</p>
<section id="free-fall" class="level3">
<h3 class="anchored" data-anchor-id="free-fall">Free Fall</h3>
<p><img src="https://latex.codecogs.com/png.latex?q=h"> in this case. <img src="https://latex.codecogs.com/png.latex?p%20=%20m%7C%5Cdot%20q%7C">. If we double the initial height, we need to “stretch time” by dividing <img src="https://latex.codecogs.com/png.latex?t"> by <img src="https://latex.codecogs.com/png.latex?%5Csqrt%202"> to remain on a valid solution.</p>
<p>Here potential scales with height <img src="https://latex.codecogs.com/png.latex?V(h)%20=%20mgh">, so <img src="https://latex.codecogs.com/png.latex?V(q)%20=%20%5Clambda%20q"> and <img src="https://latex.codecogs.com/png.latex?k=1">. <img src="https://latex.codecogs.com/png.latex?%5Calpha=1/2">.</p>
<p><img src="https://latex.codecogs.com/png.latex?J%20=%20-%5Cfrac%7B1%7D%7B2%7DHt%20+%20m%7C%5Cdot%20q%7Ch"></p>
</section>
<section id="keplers-third-law" class="level3">
<h3 class="anchored" data-anchor-id="keplers-third-law">Kepler’s Third Law</h3>
<p><img src="https://latex.codecogs.com/png.latex?q=r"> in this case. <img src="https://latex.codecogs.com/png.latex?p=m%7C%5Cdot%20q%7C">. So doubling the radius <img src="https://latex.codecogs.com/png.latex?r"> impies you must “stretch time” (like the time to complete one orbit) by dividing by <img src="https://latex.codecogs.com/png.latex?2%5E%7B3/2%7D"> to stay on a valid solution.</p>
<p>Here, <img src="https://latex.codecogs.com/png.latex?V%20%5Cpropto%20%5Cfrac%7B1%7D%7Bq%7D">, so <img src="https://latex.codecogs.com/png.latex?k%20=%20-1">. <img src="https://latex.codecogs.com/png.latex?%5Calpha%20=%203/2">, as in <a href="https://en.wikipedia.org/wiki/Kepler%27s_laws_of_planetary_motion">Kepler’s third law</a>.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20-%5Cfrac%7B3%7D%7B2%7DHt%20+%20m%7C%5Cdot%20q%7Cr%0A"></p>
</section>
</section>
</section>
<section id="code" class="level1">
<h1>Code</h1>
<p>We don’t need to adjust the code at all. It should already work!</p>
<section id="examples-1" class="level2">
<h2 class="anchored" data-anchor-id="examples-1">Examples</h2>
<section id="keplers-third-law-1" class="level3">
<h3 class="anchored" data-anchor-id="keplers-third-law-1">Kepler’s Third Law</h3>
<p>We define</p>
<div id="d31683c8" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Kepler(VariationalSystem):</span>
<span id="cb1-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> control_plane(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb1-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb1-4">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>: Rn(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb1-5">        }</span>
<span id="cb1-6">        </span>
<span id="cb1-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> params(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb1-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mu"</span>]</span>
<span id="cb1-9"></span>
<span id="cb1-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lagrangian(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ctrl, dctrl):</span>
<span id="cb1-11">        r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ctrl.r</span>
<span id="cb1-12">        rdot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dctrl.r</span>
<span id="cb1-13">        m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.mass</span>
<span id="cb1-14">        mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.mu</span>
<span id="cb1-15">            </span>
<span id="cb1-16">        r_norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.sqrt((r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> r).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-10</span>)</span>
<span id="cb1-17">        T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (rdot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> rdot).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb1-18">        V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> r_norm</span>
<span id="cb1-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> V</span></code></pre></div>
</div>
<p>We run it with</p>
<div id="73c3e7c3" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb2-2">    kepler <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Kepler({</span>
<span id="cb2-3">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>,</span>
<span id="cb2-4">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mu"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb2-5">    })</span>
<span id="cb2-6"></span>
<span id="cb2-7">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span></span>
<span id="cb2-8">    recorder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StepRecorder()</span>
<span id="cb2-9">    integrator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> VariationalIntegrator(kepler, step_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>h, on_step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>recorder.on_step)</span>
<span id="cb2-10"></span>
<span id="cb2-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Energy (time translation): (eps, t) -&gt; t + eps</span></span>
<span id="cb2-12">    energy_sym <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Symmetry(</span>
<span id="cb2-13">        space_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> eps, t, q: q,</span>
<span id="cb2-14">        time_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> eps, t, q: t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> eps</span>
<span id="cb2-15">    )</span>
<span id="cb2-16">    integrator.register_noether_charge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"energy"</span>, energy_sym)</span>
<span id="cb2-17"></span>
<span id="cb2-18">    r_slice <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kepler.model.layout[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb2-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> rotate_r(eps, t, q, sl<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>r_slice):</span>
<span id="cb2-20">        qn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q.clone()</span>
<span id="cb2-21">        x, y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q[sl]</span>
<span id="cb2-22">        c, s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> math.cos(eps), math.sin(eps)</span>
<span id="cb2-23">        qn[sl] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>q.dtype)</span>
<span id="cb2-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> qn</span>
<span id="cb2-25"></span>
<span id="cb2-26">    angular_sym <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Symmetry(space_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rotate_r)</span>
<span id="cb2-27">    integrator.register_noether_charge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"angular_momentum"</span>, angular_sym)</span>
<span id="cb2-28"></span>
<span id="cb2-29">    alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span></span>
<span id="cb2-30"></span>
<span id="cb2-31">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> scale_r(eps, t, q, sl<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>r_slice):</span>
<span id="cb2-32">        qn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q.clone()</span>
<span id="cb2-33">        qn[sl] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> math.exp(eps) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> q[sl]</span>
<span id="cb2-34">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> qn</span>
<span id="cb2-35"></span>
<span id="cb2-36">    dyn_sim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Symmetry(</span>
<span id="cb2-37">        space_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>scale_r,</span>
<span id="cb2-38">        time_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> eps, t, q: math.exp(alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> eps) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t</span>
<span id="cb2-39">    )</span>
<span id="cb2-40"></span>
<span id="cb2-41">    integrator.register_noether_charge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dynamical_similarity"</span>, dyn_sim)</span>
<span id="cb2-42"></span>
<span id="cb2-43">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initial conditions for elliptical orbit</span></span>
<span id="cb2-44">    r0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64)</span>
<span id="cb2-45">    v0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64)</span>
<span id="cb2-46">    t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64)</span>
<span id="cb2-47"></span>
<span id="cb2-48">    ctrl0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>: r0, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t"</span>: t0})</span>
<span id="cb2-49">    q0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kepler.model.pack(ctrl0)</span>
<span id="cb2-50"></span>
<span id="cb2-51">    steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span></span>
<span id="cb2-52"></span>
<span id="cb2-53">    ctrl1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>: r0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v0, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t"</span>: t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h})</span>
<span id="cb2-54">    q1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kepler.model.pack(ctrl1)</span>
<span id="cb2-55"></span>
<span id="cb2-56">    qs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [q0.clone(), q1.clone()]</span>
<span id="cb2-57">    q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q0, q1</span>
<span id="cb2-58"></span>
<span id="cb2-59">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tqdm.tqdm(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)):</span>
<span id="cb2-60">        q_next, ok <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> integrator.step(q_prev, q_curr)</span>
<span id="cb2-61">        qs.append(q_next.clone())</span>
<span id="cb2-62">        q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr, q_next</span>
<span id="cb2-63"></span>
<span id="cb2-64">    qs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.stack(qs, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb2-65"></span>
<span id="cb2-66">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Kepler Problem:"</span>)</span>
<span id="cb2-67">    energies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"energy"</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> recorder.records]</span>
<span id="cb2-68">    angular <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"angular_momentum"</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> recorder.records]</span>
<span id="cb2-69">    similarity <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dynamical_similarity"</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> recorder.records]</span>
<span id="cb2-70">    </span>
<span id="cb2-71">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Energy (should be constant):"</span>)</span>
<span id="cb2-72">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"  min:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(energies), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(energies), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"drift:"</span>, energies[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> energies[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb2-73">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Angular momentum (should be constant):"</span>)</span>
<span id="cb2-74">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"  min:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(angular), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(angular), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"drift:"</span>, angular[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> angular[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb2-75">    </span>
<span id="cb2-76">    J_values <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dynamical_similarity"</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> recorder.records]</span>
<span id="cb2-77"></span>
<span id="cb2-78">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Second Kepler run, scaled initial conditions</span></span>
<span id="cb2-79">    lam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span></span>
<span id="cb2-80">    kepler2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Kepler({</span>
<span id="cb2-81">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>,</span>
<span id="cb2-82">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mu"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb2-83">    })</span>
<span id="cb2-84">    recorder2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StepRecorder()</span>
<span id="cb2-85">    h2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>(lam<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>alpha)</span>
<span id="cb2-86">    integrator2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> VariationalIntegrator(kepler2, step_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>h2, on_step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>recorder2.on_step)</span>
<span id="cb2-87"></span>
<span id="cb2-88">    r0_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> r0</span>
<span id="cb2-89">    v0_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (lam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v0</span>
<span id="cb2-90">    t0_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64)</span>
<span id="cb2-91"></span>
<span id="cb2-92">    ctrl0_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>: r0_sc, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t"</span>: t0_sc})</span>
<span id="cb2-93">    q0_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kepler2.model.pack(ctrl0_sc)</span>
<span id="cb2-94"></span>
<span id="cb2-95">    ctrl1_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>: r0_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v0_sc, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t"</span>: t0_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h2})</span>
<span id="cb2-96">    q1_sc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kepler2.model.pack(ctrl1_sc)</span>
<span id="cb2-97"></span>
<span id="cb2-98">    qs2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [q0_sc.clone(), q1_sc.clone()]</span>
<span id="cb2-99">    q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q0_sc, q1_sc</span>
<span id="cb2-100"></span>
<span id="cb2-101">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tqdm.tqdm(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)):</span>
<span id="cb2-102">        q_next, ok <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> integrator2.step(q_prev, q_curr)</span>
<span id="cb2-103">        qs2.append(q_next.clone())</span>
<span id="cb2-104">        q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr, q_next</span>
<span id="cb2-105"></span>
<span id="cb2-106">    qs2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.stack(qs2, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb2-107"></span>
<span id="cb2-108">    base_t, base_J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_J_from_records(kepler, recorder, alpha)</span>
<span id="cb2-109">    sc_t, sc_J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_J_from_records(kepler2, recorder2, alpha)</span>
<span id="cb2-110"></span>
<span id="cb2-111">    lam_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> alpha</span>
<span id="cb2-112">    lam_J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lam <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> alpha)</span>
<span id="cb2-113"></span>
<span id="cb2-114">    errors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-115">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> tb, Jb <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(base_t, base_J):</span>
<span id="cb2-116">        target_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lam_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> tb</span>
<span id="cb2-117">        idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(sc_t)), key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> k: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(sc_t[k] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> target_t))</span>
<span id="cb2-118">        J_scaled_rescaled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sc_J[idx] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> lam_J</span>
<span id="cb2-119">        errors.append(J_scaled_rescaled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> Jb)</span>
<span id="cb2-120"></span>
<span id="cb2-121">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Dynamical similarity comparison (lambda=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lam<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">):"</span>)</span>
<span id="cb2-122">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"  J_scaled/lam^{2-alpha} - J_base stats:"</span>)</span>
<span id="cb2-123">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    min:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(errors))</span>
<span id="cb2-124">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    max:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(errors))</span>
<span id="cb2-125">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    mean:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(errors) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(errors))</span></code></pre></div>
</div>
<p>This is the previous example, but we run it twice, at two different scales.</p>
<p>We get:</p>
<pre><code>Kepler Problem:
Energy (should be constant):
  min: 0.6735297151115243 max: 0.6868012832352087 drift: -0.0026780225061522334
Angular momentum (should be constant):
  min: 0.8000193606114198 max: 0.8000206253911845 drift: -1.9376809246018922e-07
100%|████████████████████████████████████████████████████████████████████████████████| 498/498 [00:01&lt;00:00, 361.06it/s]
Dynamical similarity comparison (lambda=2.0):
  J_scaled/lam^{2-alpha} - J_base stats:
    min: -3.547062643605159e-11
    max: 4.892536153988658e-09
    mean: 9.990145743197486e-10</code></pre>
<p>Which looks good. So we have the same <img src="https://latex.codecogs.com/png.latex?J"> for similar curves.</p>
</section>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>We showed how equivariance results in Noether charges across similar systems, rather than within a single system. In the next post in this series, I plan to dig into some more interesting examples.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>There’s also another way (gauges) to transform the Lagrangian while preserving the physics that I’ll explore in a later post.↩︎</p></li>
<li id="fn2"><p>Ignoring the cases where <img src="https://latex.codecogs.com/png.latex?%5Cchi(g)"> is less than zero, as that flips the minima and maxima.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Geometric Controls</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/game_theory/posts/dynamical_similarity/</guid>
  <pubDate>Mon, 08 Dec 2025 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/game_theory/posts/dynamical_similarity/Newton-Kepler.png" medium="image" type="image/png" height="92" width="144"/>
</item>
<item>
  <title>Are We Approaching Cultural Saturation?</title>
  <link>https://demonstrandom.com/essays/posts/cultural_saturation/</link>
  <description><![CDATA[ 





<p><a href="https://en.wikipedia.org/wiki/Paul_Klee"><img src="https://demonstrandom.com/essays/posts/cultural_saturation/static-dynamic-gradation-klee-1923.jpg" class="img-fluid" style="width:35.0%" alt="Paul Klee, Static-Dynamic Gradation, 1923"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>In <a href="../../../essays/posts/preference_oracles/index.html">the Paradox of Taste</a>, I looked at novels as information-theoretic objects. One question I asked was: what if all novels that could ever exist were enumerated and indexed in the Library of Babel?</p>
<p>The Library of Babel is gigantic. A back-of-the envelope calculation shows that there are roughly <img src="https://latex.codecogs.com/png.latex?10%5E%7B140000%7D"> to <img src="https://latex.codecogs.com/png.latex?10%5E%7B220000%7D"> grammatical-ish English strings of novel length<sup>1</sup>. Even <img src="https://latex.codecogs.com/png.latex?10%5E%7B140000%7D"> is a superastronomical number. There are only an estimated <img src="https://latex.codecogs.com/png.latex?10%5E%7B80%7D"> particles in the observable universe.</p>
<p>But despite the vast number of possible stories, we seem to see the same stories over and over. Even if we just look at novels, we see clusters around a handful of templates. An orphaned farm boy is destined to defeat an ancient evil. A socially awkward young woman circles a slow-burn romance in a polite society. A brooding detective unravels a plot in a corrupt organization.</p>
<p>This seems to extend beyond the novel. In another essay, I considered images in terms of the number of <a href="../../../essays/posts/picture_worth_thousand_words/index.html#semantic-images">semantic bits</a> they encode. While there are many possible pictures, humans only care about a few semantic bits worth of knowledge, and so we see many similar forms over and over again.</p>
<p>Other art forms also seem to gravitate towards a few recurring forms. Consider movies. We often see the same Marvel origin stories or the same Disney film remade over-and-over.</p>
<p>Every artistic medium shows the same pattern. Early on, discoveries feel abundant. New genres, new forms, new conventions. As the medium matures, novelty becomes harder, and there are endless sequels and reboots. Or, genres fragment and microstyles proliferate, with innovation occurring along narrower and narrower dimensions.</p>
<p>If there is such an abundance of possible art, why do we seem to see the same cultural objects over and over? Is cultural novelty a finite resource? And if so, are we approaching some sort of equilibrium?</p>
<p>In the sciences, there is even some concern that an exponential amount of energy input could lead to a mere linear payoff, or worse<sup>2</sup>. Could something similar be true in the cultural fields?</p>
<p>In this post we will briefly consider these questions.</p>
<p><strong>Caveat Lector</strong>: All arguments are back-of-the-envelope. As with all of my writings, please view it as semi-experimental.</p>
</section>
<section id="abstraction" class="level1">
<h1>Abstraction</h1>
<p>One obvious answer to this conundrum is that humans don’t remember or analyze stories in their entirety, but only consider abstractions of stories. Various theorists have tried to build models of specific stories, or classes of stories. The most well-known of these is Campbell’s “Hero’s Journey”, from <em>The Hero with a Thousand Faces</em> (1949).</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/Heroesjourney.png" class="img-fluid" style="width:65.0%"></p>
<p>There is an entire corpus of scholarly work attempting to build narratological models of stories.</p>
<p>Most of the academic work seems aimed at classifying or characterizing existing stories. These range from role-based models (Greimas models stories as interactions among a small set of roles) to grammars (Vladimir Propp’s <a href="https://web.mit.edu/allanmc/www/propp.pdf">Morphology of the Folktale</a>, which treats Russian folktales as sequences of standardized “functions” that behave roughly like the states of a finite-state automaton).</p>
<p>In folklore, academics have even attempted to index the space of known plots. The <a href="https://edition.fi/kalevalaseura/catalog/book/763">Aarne–Thompson–Uther</a> (ATU) folktale type index assigns each traditional tale a numeric “type” (ATU 510A for “Cinderella”, 300–749 for various hero tales, and so on), while Stith Thompson’s <a href="https://ia600301.us.archive.org/18/items/Thompson2016MotifIndex/Thompson_2016_Motif-Index.pdf">Motif-Index of Folk-Literature</a> catalogues recurring “mythemes”, story motifs like “cruel stepmother,” “magic helper,” “journey to the underworld”. In effect, these systems treat the corpus of folktales as a finite catalogue of plot skeletons and motifs that can be recombined.</p>
<p>There’s also a small cottage industry of “implementable” story frameworks aimed at aspiring screenwriters (Syd Field’s three-act paradigm, Snyder’s “Save The Cat”, John Truby’s <em>Anatomy of Story</em>) which break all stories down into a “templated” set of steps (to be implemented by a writer)<sup>3</sup>.</p>
<p>Finally, there are attempts to embed stories (and indeed, all of language) into low-dimensional spaces using machine learning. These are outside the scope of this post.</p>
<p>Long story short: long stories can be made short.</p>
<section id="counting-abstract-stories" class="level2">
<h2 class="anchored" data-anchor-id="counting-abstract-stories">Counting Abstract Stories</h2>
<p>Now that we’ve concluded stories can be abstracted, let’s investigate the “crowdedness” of the space of stories. Let’s suppose a story’s “semantic type” can be encoded in only <img src="https://latex.codecogs.com/png.latex?k"> “semantic” bits<sup>4</sup>, similar to our analysis of <a href="../../../essays/posts/picture_worth_thousand_words/index.html#semantic-images">pictures</a>. If there are <img src="https://latex.codecogs.com/png.latex?N"> “evenly distributed” stories, how close are they to each other?</p>
<p>To be clear, we are modeling each “abstract story” in our model as a string of 1s and 0s. Each bit represents some abstract story element (for example, “comedy or tragedy”). We can view this as an “index” into the set of stories (like in Borges’ library). If a story differs from another story in <img src="https://latex.codecogs.com/png.latex?r"> bit places, we say the two stories are “Hamming distance <img src="https://latex.codecogs.com/png.latex?r">” away from each other.</p>
<p>We can also similarly assume each story “claims” the nearby <img src="https://latex.codecogs.com/png.latex?r"> radius. Then the “volume” of the Hamming neighborhood is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(k,%20r)%20=%20%5Csum_%7Bi=0%7D%5E%7Br%7D%20%7Bk%20%5Cchoose%20i%7D%0A"></p>
<p>Therefore, we say there are <img src="https://latex.codecogs.com/png.latex?V(k,%20r)"> stories within radius <img src="https://latex.codecogs.com/png.latex?r"> of the original story.</p>
<p>Since there are <img src="https://latex.codecogs.com/png.latex?2%5Ek"> stories total, if we assume the stories are evenly distributed<sup>5</sup>, we get (crudely)</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AN%5Ccdot%20V(k,r)%20%5Capprox%202%5E%7Bk%7D%0A"></p>
<p>Suppose we want to create a new story in this space. What’s the minimal overlap it will have with an existing story?</p>
<p>If we know <img src="https://latex.codecogs.com/png.latex?N"> and <img src="https://latex.codecogs.com/png.latex?k">, we can find this by solving the following equation for <img src="https://latex.codecogs.com/png.latex?r">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV(k,%20r)%20%5Capprox%20%5Cfrac%7B2%5E%7Bk%7D%7D%7BN%7D%0A"></p>
<p>and then computing “bits in common”</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ad_0%20=%20k%20-%20r%0A"></p>
<p>in fractional form:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ad_%7Bf%7D%20=%20%5Cfrac%7Bd_%7B0%7D%7D%7Bk%7D%20=%201%20-%20%5Cfrac%7Br%7D%7Bk%7D%0A"></p>
<p>How large are <img src="https://latex.codecogs.com/png.latex?N"> and <img src="https://latex.codecogs.com/png.latex?k"> in reality?</p>
<p>Let’s stick with English. For novels, according to <a href="https://litlab.stanford.edu/how-many-novels-have-been-published-in-english-an-attempt/">Fredner</a>, <img src="https://latex.codecogs.com/png.latex?N%20%5Capprox%205%5Ctimes%2010%5E6">. Let’s use this estimate for now (although in reality the number of novels is dwarfed by the number of short stories, and to consider all stories we might also want to consider narrative poems, film scripts, etc.).</p>
<p>Reusing our semantic bit estimate of <img src="https://latex.codecogs.com/png.latex?k=50">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B2%5E%7B50%7D%7D%7B5000000%7D%20=%20%5Csum_%7Bi=0%7D%5E%7Br%7D%20%7B50%20%5Cchoose%20i%7D%0A"></p>
<p>Solving this, we get somewhere between <img src="https://latex.codecogs.com/png.latex?r=7"> and <img src="https://latex.codecogs.com/png.latex?r=8">. So <img src="https://latex.codecogs.com/png.latex?d_f"> is bounded by <img src="https://latex.codecogs.com/png.latex?1%20-%20%5Cfrac%7B8%7D%7B50%7D%20=%200.84">.</p>
<p>That is, under this model, a new novel would semantically overlap with an existing story by 84% or more.</p>
<p>Let’s look at how varying <img src="https://latex.codecogs.com/png.latex?k"> and <img src="https://latex.codecogs.com/png.latex?N"> affect this result.</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/Overlap_Fraction.png" class="img-fluid"></p>
<p>This first figure is a heatmap. We vary <img src="https://latex.codecogs.com/png.latex?%5Clog_%7B10%7DN"> on the x-axis and <img src="https://latex.codecogs.com/png.latex?k"> on the y-axis. The color of the grid depends on <img src="https://latex.codecogs.com/png.latex?d_f">.</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/Overlap_vs_N.png" class="img-fluid"></p>
<p>In the second plot we instead compare <img src="https://latex.codecogs.com/png.latex?N"> to <img src="https://latex.codecogs.com/png.latex?d_f"> for each value of <img src="https://latex.codecogs.com/png.latex?k">.</p>
<p><img src="https://latex.codecogs.com/png.latex?d_f"> rises sharply at low <img src="https://latex.codecogs.com/png.latex?N">, then approaches 1 as <img src="https://latex.codecogs.com/png.latex?N"> grows larger.</p>
<p>So even at relatively high <img src="https://latex.codecogs.com/png.latex?k">, the space of stories is somewhat crowded at <img src="https://latex.codecogs.com/png.latex?10%5E6">. What’s more, most of the crowding occurs early, then slows down, with “steeper crowding” occurring at lower <img src="https://latex.codecogs.com/png.latex?k">.</p>
<p>This matches what we expect. Stories are getting crowded. Even before we bring in energy or dynamics, a simple packing argument already suggests that most new stories must live semantically close to ones we’ve already told.</p>
</section>
<section id="not-all-bits-are-equal" class="level2">
<h2 class="anchored" data-anchor-id="not-all-bits-are-equal">Not All Bits Are Equal</h2>
<p>By using “semantic bits” we have implicitly assumed that the bits are (mostly) orthogonal and ordered by importance. In reality, the first bit (e.g.&nbsp;“tragedy vs.&nbsp;comedy”, or more likely some division of two common story structure types) probably matters more to human perception than the 47th bit. So our original model is a bit naive. Let’s try to improve it by adding weighting for perceptual importance.</p>
<p>If there is a recursive hierarchical decomposition of meaning (i.e.&nbsp;at the top level is comedy vs.&nbsp;tragedy, comedy splits into romantic comedy vs.&nbsp;dark comedy, etc.), and this is “scale-free”, we should wind up with a <a href="https://arxiv.org/abs/cond-mat/0412004">power law</a>. Many similar natural phenomena follow power laws, such as <a href="https://en.wikipedia.org/wiki/Zipf%27s_law">Zipf’s Law</a><sup>6</sup>, which comes up in the relative frequences of words in natural corpora of texts.</p>
<p>So let’s assume the importance <img src="https://latex.codecogs.com/png.latex?w_i"> of the <img src="https://latex.codecogs.com/png.latex?i">-th bit roughly follows a power law:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aw_i%20%5Cpropto%20i%5E%7B-%5Cbeta%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> is some weight.</p>
<p>So we can compute the weighted distance between two works (note the shift by one to keep <img src="https://latex.codecogs.com/png.latex?0"> indexing, consistent with the last example):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ad(x,y)%20=%20%5Csum_%7Bi=0%7D%5E%7Bk-1%7D%20%5Cfrac%7B1%7D%7B(i%20+%201)%5E%7B%5Cbeta%7D%7D%7Cx_i%20-%20y_i%7C%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?x"> and <img src="https://latex.codecogs.com/png.latex?y"> are strings in this case, and the sum is over their characters. <img src="https://latex.codecogs.com/png.latex?%7Cx_i%20-%20y_i%7C"> returns 0 if the characters are equal, and 1 if they are different.</p>
<p>So the complete diameter of the space (the distance between the furthest two objects, for example the string of <img src="https://latex.codecogs.com/png.latex?k"> “1”s and the string of <img src="https://latex.codecogs.com/png.latex?k"> “0”s) is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ad(11111..1,00000..0)%20=%20%5Csum_%7Bi=0%7D%5E%7Bk-1%7D%20%5Cfrac%7B1%7D%7B(i%20+%201)%5E%7B%5Cbeta%7D%7D%0A"></p>
<p>This is the partial sum of the <img src="https://latex.codecogs.com/png.latex?p">-series<sup>7</sup>. In the limit, the <img src="https://latex.codecogs.com/png.latex?p">-series only converges for <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3E%201">. Call the partial sum <img src="https://latex.codecogs.com/png.latex?S_%7Bk%7D(%5Cbeta)"> for a given <img src="https://latex.codecogs.com/png.latex?%5Cbeta">.</p>
<p>Now let <img src="https://latex.codecogs.com/png.latex?R"> be the typical nearest-neighbor distance between works in this weighted metric. We’ll also assume there’s a perceptual threshold distance <img src="https://latex.codecogs.com/png.latex?R_c"> below which differences between works become imperceptible. This threshold probably varies by person: more casual consumers have a higher <img src="https://latex.codecogs.com/png.latex?R_c">, whereas experts have a lower <img src="https://latex.codecogs.com/png.latex?R_c">.</p>
<p>For a given <img src="https://latex.codecogs.com/png.latex?k"> and <img src="https://latex.codecogs.com/png.latex?%5Cbeta">, <img src="https://latex.codecogs.com/png.latex?S_k(%5Cbeta)"> is the maximum possible distance. If the typical nearest-neighbor distance <img src="https://latex.codecogs.com/png.latex?R"> falls well below a perceptual threshold <img src="https://latex.codecogs.com/png.latex?R_c">, then most works within that ball of radius <img src="https://latex.codecogs.com/png.latex?R_c"> will feel indistinguishable to a given observer. When <img src="https://latex.codecogs.com/png.latex?R"> is still a substantial fraction of <img src="https://latex.codecogs.com/png.latex?S_k(%5Cbeta)">, there is still plenty of perceptual room for works to feel distinct.</p>
<p>We can now repeat our earlier analysis.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?V_%5Cbeta(k,R)"> be the number of strings within weighted distance <img src="https://latex.codecogs.com/png.latex?R"> of a given string. Then by our crude packing argument we get a volume:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AV_%7B%5Cbeta%7D(k,%20R)%20=%20%5Cfrac%7B2%5E%7Bk%7D%7D%7BN%7D%0A"></p>
<p>and a new weighted <img src="https://latex.codecogs.com/png.latex?d%5Ew_f"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ad%5Ew_f%20=%201%20-%20%5Cfrac%7BR%7D%7BS_k(%5Cbeta)%7D%0A"></p>
<p>Given <img src="https://latex.codecogs.com/png.latex?S_k(%5Cbeta)">, we can solve implicitly for <img src="https://latex.codecogs.com/png.latex?R">.</p>
<p>There’s three different cases for what the approximation of <img src="https://latex.codecogs.com/png.latex?S_k(%5Cbeta)"> looks like, depending on <img src="https://latex.codecogs.com/png.latex?%5Cbeta">.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS_k(%5Cbeta)%0A%5Capprox%0A%5Cbegin%7Bcases%7D%0A%5Cdfrac%7Bk%5E%7B1-%5Cbeta%7D%7D%7B1-%5Cbeta%7D,%20&amp;%200%20%3C%20%5Cbeta%20%3C%201,%20%5C%5C%5B6pt%5D%0A%5Clog%20k%20+%20%5Cgamma,%20&amp;%20%5Cbeta%20=%201,%20%5C%5C%5B6pt%5D%0A%5Czeta(%5Cbeta)%20-%20%5Cdfrac%7B1%7D%7B(%5Cbeta-1)%5C,k%5E%7B%5Cbeta-1%7D%7D,%20&amp;%20%5Cbeta%20%3E%201,%0A%5Cend%7Bcases%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Czeta(%5Cbeta)"> is the zeta function, and <img src="https://latex.codecogs.com/png.latex?%5Cgamma"> is the <a href="https://en.wikipedia.org/wiki/Euler%27s_constant">Euler-Mascheroni constant</a>. Note that the partial sum only actually converges in the limit if <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Cgt%201">.</p>
<p>Let’s think about each case.</p>
<section id="case-1-0-lt-beta-lt-1" class="level3">
<h3 class="anchored" data-anchor-id="case-1-0-lt-beta-lt-1">Case 1: <img src="https://latex.codecogs.com/png.latex?0%20%5Clt%20%5Cbeta%20%5Clt%201"></h3>
<p>In this case, the partial sum diverges like a polynomial in <img src="https://latex.codecogs.com/png.latex?k">. <img src="https://latex.codecogs.com/png.latex?S_k(%7B%5Cbeta%7D)%20%5Capprox%20%5Cfrac%7Bk%5E%7B1-%5Cbeta%7D%7D%7B1%20-%20%5Cbeta%7D"></p>
<p>The intuition is that the weights fall off very slowly as we progress to increasingly low-order bits, so “low-order” bits still contribute meaningfully to perception (though less than higher-order bits).</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/Overlap_vs_N_0_5.png" class="img-fluid"></p>
<p>Plot above at <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20=%200.5">.</p>
</section>
<section id="case-2-beta-1" class="level3">
<h3 class="anchored" data-anchor-id="case-2-beta-1">Case 2: <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20=%201"></h3>
<p>In this case, we have a harmonic series, and the partial sum is roughly a logarithm in <img src="https://latex.codecogs.com/png.latex?k">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS_k(1)%20%5Capprox%20%5Clog(k)%20+%20%5Cgamma%0A"></p>
<p>In this case, each bit is worth roughly the same. This is the “Zipfian” case.</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/Overlap_vs_N_1.png" class="img-fluid"></p>
<p>Plot above at <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20=%201">.</p>
</section>
<section id="case-3-beta-gt-1" class="level3">
<h3 class="anchored" data-anchor-id="case-3-beta-gt-1">Case 3: <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Cgt%201"></h3>
<p>Finally, if <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%5Cgt%201"> we have a convergent series. The partial sum <img src="https://latex.codecogs.com/png.latex?S_k"> converges to the zeta function <img src="https://latex.codecogs.com/png.latex?%5Czeta(%5Cbeta)"> in the limit, plus an additional term (<img src="https://latex.codecogs.com/png.latex?S_k(%5Cbeta)%20%5Capprox%20%5Czeta(%5Cbeta)%20-%20%5Cfrac%7B1%7D%7B(%5Cbeta-1)%5C,k%5E%7B%5Cbeta-1%7D%7D">).</p>
<p>In this regime, marginal bits are worth much less than earlier bits: most of the perceptual “distance budget” is carried by the first few coordinates. Geometrically, the metric has a finite diameter (bounded by <img src="https://latex.codecogs.com/png.latex?%5Czeta(%5Cbeta)">) even as the number of distinct works <img src="https://latex.codecogs.com/png.latex?2%5Ek"> grows exponentially. Beyond some point, adding more bits creates many more states that are all crammed into almost the same finite perceptual space.</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/Overlap_vs_N_2.png" class="img-fluid"></p>
<p>Plot above at <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20=%202">. See how it is noticeably more “squashed” than the last two?</p>
</section>
</section>
</section>
<section id="dynamics" class="level1">
<h1>Dynamics</h1>
<p>Now that we have a method to measure the “crowdedness” of the space is terms of the total number of possible artifacts, let’s look at production in terms of total spent energy, and how that relates to the amount of novelty remaining. If we can link energy spent to the number of remaining microstates, we can use methods from statistical mechanics to model the state of the culture, and how it will change over time.</p>
<section id="empirical-energy-estimates" class="level2">
<h2 class="anchored" data-anchor-id="empirical-energy-estimates">Empirical Energy Estimates</h2>
<p>Let’s do some Fermi estimates of the total energy expenditure spent over time to reach the current cultural stock.</p>
<p>Let’s break this down.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AN_%7B%5Ctext%7Bnovels%7D%7D%5Ccdot%5Cfrac%7B%5Ctext%7Btime%7D%7D%7B%5Ctext%7Bnovel%7D%7D%20%5Ccdot%20%5Cfrac%7B%5Ctext%7Benergy%7D%7D%7B%5Ctext%7Btime%7D%7D%20%20=%20%5Ctext%7Benergy%7D%0A"></p>
<p>We already estimated <img src="https://latex.codecogs.com/png.latex?5%5Ctimes%2010%5E%7B6%7D"> total novels. If a novel takes roughly 800 hours<sup>8</sup>, and a human burns 135 watts while writing<sup>9</sup>, we get:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A5%5Ctimes%2010%5E6%20%5Ccdot%20800%20%5Ccdot%2060%20%5Ccdot%2060%20%5Ccdot%20135%20%5Capprox%2010%5E%7B15%7D%20%5Ctext%7BJ%7D%0A"></p>
<p>or <img src="https://latex.codecogs.com/png.latex?10%5E%7B15%7D"> total joules (roughly 10 Hiroshima-class atomic bombs).</p>
<p>That’s the metabolic energy to actually produce the works. We’ll pretend that all works are magically placed in the library instantly, but note that there is also some additional analysis possible regarding distribution<sup>10</sup>.</p>
</section>
<section id="statistical-mechanics-of-semantic-space" class="level2">
<h2 class="anchored" data-anchor-id="statistical-mechanics-of-semantic-space">Statistical Mechanics of Semantic Space</h2>
<p>Can we now connect the energy input to our model of cultural generation?</p>
<p>Let’s arbitrarily choose a reference frame, the <img src="https://latex.codecogs.com/png.latex?k">-length string <img src="https://latex.codecogs.com/png.latex?0"> = “000..0”<sup>11</sup>.</p>
<p>We can then compute the “internal energy” <img src="https://latex.codecogs.com/png.latex?%0AE%5E%5Cast(x)%20=%20d(x,%200)%20=%20%5Csum_%7Bi=0%7D%5E%7Bk-1%7D%20%5Cfrac%7Bx_i%7D%7B(i%20+%201)%5E%7B%5Cbeta%7D%7D%0A"></p>
<p>The number of microstates <img src="https://latex.codecogs.com/png.latex?%5COmega(E)"> for a given energy level <img src="https://latex.codecogs.com/png.latex?E"> is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5COmega(E)=%5C#%5C%7Bx%5Cin%5C%7B0,1%5C%7D%5Ek%20:%20E%5E%5Cast(x)%5Cle%20E%5C%7D%0A"></p>
<p>That is, for a given <img src="https://latex.codecogs.com/png.latex?E"> and <img src="https://latex.codecogs.com/png.latex?x">, we count up the strings where <img src="https://latex.codecogs.com/png.latex?d(x,%200)%20%5Clt%20E">. We can define the “entropy” now:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%20=%20%5Clog%20%5COmega(E)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5COmega"> is the number of microstates within distance <img src="https://latex.codecogs.com/png.latex?E"> of the reference.</p>
<p>We are actually seeking the “novelty” per unit energy added to the system. This looks like</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20S%7D%7B%5Cpartial%20E%7D%0A"></p>
<p>It just so happens that this is related to the temperature.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B1%7D%7BT%7D%20=%20%5Cfrac%7B%5Cpartial%20S%7D%7B%5Cpartial%20E%7D%0A"></p>
<p>which we can approximate as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B1%7D%7BT%7D%20=%20%5Cfrac%7B%5Cpartial%20S%7D%7B%5Cpartial%20E%7D%20%5Capprox%20%5Cfrac%7B%5CDelta%20S%7D%7B%5CDelta%20E%7D%20=%20%5Cfrac%7B%5Clog(%5COmega(E%20+%20(k%20+%201)%5E%7B-%5Cbeta%7D))%20-%20%5Clog(%5COmega(E))%7D%7B(k%20+%201)%5E%7B-%5Cbeta%7D%7D%0A"></p>
<p>Solving for <img src="https://latex.codecogs.com/png.latex?T"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AT%20%5Capprox%20%5Cfrac%7B(k%20+%201)%5E%7B-%5Cbeta%7D%7D%7B%5Clog(%5COmega(E%20+%20(k%20+%201)%5E%7B-%5Cbeta%7D))%20-%20%5Clog(%5COmega(E))%7D%0A"></p>
<p>So (by definition) as <img src="https://latex.codecogs.com/png.latex?T"> increase, the marginal entropy (the amount of space for new stories) per unit of marginal energy declines.</p>
<p>Using the <img src="https://latex.codecogs.com/png.latex?k=50"> and <img src="https://latex.codecogs.com/png.latex?N=5%5Ctimes%2010%5E6">, we can plot <img src="https://latex.codecogs.com/png.latex?T"> against the total cumulative energy added to the system <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BE%7D">:</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/Total_Energy_vs_T.png" class="img-fluid"></p>
<p>The above plot is at <img src="https://latex.codecogs.com/png.latex?%5Cbeta=1.0">. Basically, the temperature doesn’t change much at first, but past a certain amount of energy <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7BdS%7D%7BdE%5E%5Cast%7D"> drops rapidly as we “run out of room” and temperature increases rapidly.</p>
</section>
<section id="heat-capacity" class="level2">
<h2 class="anchored" data-anchor-id="heat-capacity">Heat Capacity</h2>
<p>Let’s now look at the marginal gain compression (changes in <img src="https://latex.codecogs.com/png.latex?T">) as energy increases.</p>
<p>Under the <a href="https://en.wikipedia.org/wiki/Microcanonical_ensemble">microcanonical ensemble</a>, we can define the heat capacity as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AC(E)%20=%20%5Cleft(%20%5Cfrac%7B%5Cpartial%20T%7D%7B%5Cpartial%20E%7D%20%5Cright)%5E%7B-1%7D%0A"></p>
<p>which we can approximate as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AC(E)%20%5Capprox%0A%5Cfrac%7B%5CDelta%20E%7D%7BT(E+%5CDelta%20E)%20-%20T(E)%7D%0A"></p>
<p>Alternately this can be written in terms of <img src="https://latex.codecogs.com/png.latex?S">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AC(E)%20%5Capprox%0A-%5C,%5Cfrac%7BT(E)%5E2%7D%7B%5Cdfrac%7BS(E+%5CDelta%20E)%20-%202S(E)%20+%20S(E-%5CDelta%20E)%7D%7B(%5CDelta%20E)%5E2%7D%7D%0A"></p>
<p>With the heat capacity, we can now connect the effect of the marginal energy input into the system with the change in the remaining capacity.</p>
<p>Intuitively, <img src="https://latex.codecogs.com/png.latex?C(E)"> measures how the energy we pump into the system relates to increases in the effective temperature <img src="https://latex.codecogs.com/png.latex?T"> (scarcity of novelty per unit energy).</p>
<p>When <img src="https://latex.codecogs.com/png.latex?C(E)"> is large, adding energy changes <img src="https://latex.codecogs.com/png.latex?T"> slowly. This corresponds to a regime where there is still a lot of unexplored semantic volume, and we can keep investing in new works without running out of “cheap” novelty.</p>
<p>When <img src="https://latex.codecogs.com/png.latex?C(E)"> is small, adding a little energy produces a big jump in <img src="https://latex.codecogs.com/png.latex?T">. In this regime, most of the low-hanging novelty has already been harvested, so further investment mostly churns inside already-occupied regions of semantic space.</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/Total_Energy_vs_C.png" class="img-fluid"></p>
<p>If we plot the heat capacity against total energy added to the system, we see the heat capacity is pretty constant until it suddenly declines.</p>
</section>
<section id="synthesis" class="level2">
<h2 class="anchored" data-anchor-id="synthesis">Synthesis</h2>
<p>Let’s put the argument together and examine the dynamics of this situation from a society-level perspective. Human society expends energy (in the form of food and fuel) to find cultural objects. How much energy does it take to find “novel” cultural objects?</p>
<p>We can connect the entropy change over time to the energy input <img src="https://latex.codecogs.com/png.latex?%5Cdot%20E"> and the temperature <img src="https://latex.codecogs.com/png.latex?T">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BdS%7D%7Bdt%7D%0A=%20%5Cfrac%7B%5Cpartial%20S%7D%7B%5Cpartial%20E%7D%5C,%5Cfrac%7BdE%7D%7Bdt%7D%0A=%20%5Cfrac%7B%5Cdot%20E(t)%7D%7BT(E)%7D%0A"></p>
<p>For now, let’s assume a constant energy input rate. Therefore</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%20E(t)%20=%20%5Ckappa%0A"></p>
<p>and so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AE(t)%20=%20E_0%20+%20%5Ckappa%20t%0A"></p>
<p>Let’s recall our three cases and go case-by-case</p>
<section id="case-1-0-lt-beta-lt-1-1" class="level3">
<h3 class="anchored" data-anchor-id="case-1-0-lt-beta-lt-1-1">Case 1: <img src="https://latex.codecogs.com/png.latex?0%20%5Clt%20%5Cbeta%20%5Clt%201"></h3>
<p>This is our “slow drop off” case. In the <img src="https://latex.codecogs.com/png.latex?0%20%3C%20%5Cbeta%20%3C%201"> regime, the earlier bit-weight analysis gave a polynomial growth of the effective “diameter” with <img src="https://latex.codecogs.com/png.latex?k">,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS_k(%5Cbeta)%20%5Capprox%20%5Cfrac%7Bk%5E%7B1-%5Cbeta%7D%7D%7B1-%5Cbeta%7D%0A"></p>
<p>and if we take energy to be proportional to <img src="https://latex.codecogs.com/png.latex?k"> we can write entropy <img src="https://latex.codecogs.com/png.latex?S"> as a power law in <img src="https://latex.codecogs.com/png.latex?E">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS(E)%20%5Capprox%20A%20E%5E%7B1-%5Cbeta%7D%20+%20B%0A"></p>
<p>for some constants <img src="https://latex.codecogs.com/png.latex?A%20%3E%200">, <img src="https://latex.codecogs.com/png.latex?B">.</p>
<p>Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BdS%7D%7BdE%7D%0A=%20A%20(1-%5Cbeta)%20E%5E%7B-%5Cbeta%7D%0A"></p>
<p>Using <img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B1%7D%7BT(E)%7D%20=%20%5Cfrac%7BdS%7D%7BdE%7D%0A"></p>
<p>we obtain</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B1%7D%7BT(E)%7D%20=%20A%20(1-%5Cbeta)%20E%5E%7B-%5Cbeta%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0AT(E)%20=%20%5Cfrac%7B1%7D%7BA(1-%5Cbeta)%7D%5C,E%5E%7B%5Cbeta%7D%0A"></p>
<p>The entropy production rate under constant energy input is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BdS%7D%7Bdt%7D%0A=%20%5Cfrac%7BdS%7D%7BdE%7D%5C,%5Cdot%20E%0A=%20A%20(1-%5Cbeta)%20E%5E%7B-%5Cbeta%7D%20%5Ckappa%0A=%20%5Cfrac%7BA%20(1-%5Cbeta)%20%5Ckappa%7D%7B%5Cbigl%0A(E_0%20+%20%5Ckappa%20t%5Cbigr)%5E%7B%5Cbeta%7D%7D%0A"></p>
<p>So, given constant energy input <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7BdS%7D%7Bdt%7D"> falls off as <img src="https://latex.codecogs.com/png.latex?O(t%5E%7B-%5Cbeta%7D)">. Alternatively, each doubling of our energy input should yield <img src="https://latex.codecogs.com/png.latex?2%5E%7B-%5Cbeta%7D"> amount of “novelty”.</p>
</section>
<section id="case-2-beta-1-1" class="level3">
<h3 class="anchored" data-anchor-id="case-2-beta-1-1">Case 2: <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20=%201"></h3>
<p>This is our Zipfian case. Before, we had</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS_k(1)%20%5Capprox%20%5Clog%20k%20+%20%5Cgamma%0A"></p>
<p>so suppose</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS(E)%20%5Capprox%20a%20%5Clog%20E%20+%20b%0A"></p>
<p>with <img src="https://latex.codecogs.com/png.latex?a"> and <img src="https://latex.codecogs.com/png.latex?b"> constants. Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BdS%7D%7BdE%7D%0A=%20%5Cfrac%7Bd%7D%7BdE%7D%5Cbigl(a%20%5Clog%20E%20+%20b%5Cbigr)%0A=%20%5Cfrac%7Ba%7D%7BE%7D%0A"></p>
<p>Using <img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B1%7D%7BT(E)%7D%20=%20%5Cfrac%7BdS%7D%7BdE%7D%0A"></p>
<p>we get</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B1%7D%7BT(E)%7D%20=%20%5Cfrac%7Ba%7D%7BE%7D%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AT(E)%20=%20%5Cfrac%7BE%7D%7Ba%7D%0A"></p>
<p>With constant energy input, <img src="https://latex.codecogs.com/png.latex?%0AE(t)%20=%20E_0%20+%20%5Ckappa%20t%0A"></p>
<p>therefore</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7BdS%7D%7Bdt%7D%0A=%20%5Cfrac%7BdS%7D%7BdE%7D%5C,%5Cdot%20E%0A=%20%5Cfrac%7Ba%7D%7BE(t)%7D%5C,%5Ckappa%0A=%20%5Cfrac%7Ba%5Ckappa%7D%7BE_0%20+%20%5Ckappa%20t%7D%0A"></p>
<p>Integrating in time:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS(t)%20%5Capprox%20a%20%5Clog%5Cbigl(E_0%20+%20%5Ckappa%20t%5Cbigr)%20+%20b%0A"></p>
<p>So in the Zipfian regime, even with constant energy input, entropy (the number of distinguishable cultural microstates) grows logarithmically in time. Equivalently, <em>exponential energy input leads to linear growth output</em>.</p>
<p>Sound familiar? This is similar to the story we heard earlier, related to science.</p>
</section>
<section id="case-3-beta-1" class="level3">
<h3 class="anchored" data-anchor-id="case-3-beta-1">Case 3: <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3E%201"></h3>
<p>This is the bounded regime. Since the p-series converges to a finite limit for <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3E%201">, there is an effective maximal energy scale <img src="https://latex.codecogs.com/png.latex?E_%7B%5Cmax%7D"> and corresponding maximal entropy</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS_%7B%5Cmax%7D%20=%20%5Clog%20%5COmega(E_%7B%5Cmax%7D)%0A"></p>
<p>We can’t do the same analysis we did in the previous two cases because the number of bits is no longer proportional to the energy. That being said, we can reason intuitively that under constant energy input, <img src="https://latex.codecogs.com/png.latex?T(E)"> diverges as <img src="https://latex.codecogs.com/png.latex?E%20%5Cto%20E_%7B%5Cmax%7D">, while the entropy production rate <img src="https://latex.codecogs.com/png.latex?%5Cdfrac%7BdS%7D%7Bdt%7D"> collapses to zero.</p>
</section>
<section id="time-to-saturation" class="level3">
<h3 class="anchored" data-anchor-id="time-to-saturation">Time to Saturation</h3>
<p>How far are we from saturation? More specifically, given some energy input rate <img src="https://latex.codecogs.com/png.latex?%5Cdot%20E%20=%20%5Ckappa">, how long does it take before novelty per unit time falls below the perceptual threshold <img src="https://latex.codecogs.com/png.latex?R_c">, or before we’ve exhausted a fixed fraction of the available semantic space?</p>
<p>We can find this by computing:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0At_%7B%5Ctext%7Bsat%7D%7D%20=%20%5Cfrac%7BE_%7B%5Ctext%7Bsat%7D%7D%20-%20E_0%7D%7B%5Ckappa%7D%0A"></p>
<p>We have expressions for <img src="https://latex.codecogs.com/png.latex?E"> from the previous section, and we have estimates for <img src="https://latex.codecogs.com/png.latex?E_0"> and <img src="https://latex.codecogs.com/png.latex?%5Ckappa">. To find <img src="https://latex.codecogs.com/png.latex?E_%7B%5Ctext%7Bsat%7D%7D">, we compute <img src="https://latex.codecogs.com/png.latex?V(k,%20R_c)">, then compute <img src="https://latex.codecogs.com/png.latex?E_%7B%5Ctext%7Bsat%7D%7D%20=%20%5Cfrac%7B2%5Ek%7D%7BV(k,%20R_c)E_%7B%5Ctext%7Bwork%7D%7D%7D">.</p>
<p>I’ll omit the details. We can now plot out the dynamics using software.</p>
</section>
<section id="plots" class="level3">
<h3 class="anchored" data-anchor-id="plots">Plots</h3>
<p>AI Disclosure: Don’t take these plots too seriously. They’re mostly for illustrative effect. The parameter choices and saturation threshold are arbitrary; the only thing that really matters is the qualitative shape: initially flat, then a sharp rise once the space becomes crowded.</p>
<p>Now that we know how to compute everything, let’s take a look at historical and future trends. Instead of a universally constant energy input rate, let’s assume roughly exponential increase in energy input starting at the year 1700. There were also very few English language novels in the year 1700, so I used “100” as an approximation.</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/t_time_beta0_5.png" class="img-fluid"> <img src="https://demonstrandom.com/essays/posts/cultural_saturation/t_time_beta1_0.png" class="img-fluid"> <img src="https://demonstrandom.com/essays/posts/cultural_saturation/t_time_beta2_0.png" class="img-fluid"></p>
<p>These are toy graphs. The saturation level is arbitrary. In the above graphs I’ve just set it to 10x the current temperature. But we can see that the saturation point could be quite close, especially if we think <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> is high.</p>
<p>Regardless, under these assumptions, the qualitative picture is straightforward. At first, additional energy buys a lot of entropy. The system is “cold”, and new works carve out genuinely new regions of semantic space. As cumulative energy grows, the effective temperature <img src="https://latex.codecogs.com/png.latex?T(E)"> remains roughly flat for a while, then begins to rise rapidly as we enter the crowded regime. Beyond that point, most of the marginal energy goes into producing works that sit inside already-populated neighborhoods in story space, rather than opening up genuinely new directions.</p>
<p>Now, let’s imagine that AI lets us move past metabolic limits for cultural production sometime in the near future. What will that do to our saturation times? The computations are easy: time to saturation and energy required are proportional in this framework. For example, AI might let us instantly increase the energy rate used to mine culture by one or more orders of magnitude. How does the projection change?</p>
<p><img src="https://demonstrandom.com/essays/posts/cultural_saturation/t_ai_beta0_5.png" class="img-fluid"> <img src="https://demonstrandom.com/essays/posts/cultural_saturation/t_ai_beta1_0.png" class="img-fluid"> <img src="https://demonstrandom.com/essays/posts/cultural_saturation/t_ai_beta2_0.png" class="img-fluid"></p>
<p>Here I’ve let AI increase energy expenditures by 5x. This massively decreases the saturation timeline</p>
</section>
<section id="negative-temperature" class="level3">
<h3 class="anchored" data-anchor-id="negative-temperature">Negative Temperature</h3>
<p>The <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20%3E%201"> case (especially high <img src="https://latex.codecogs.com/png.latex?%5Cbeta">) has some especially interesting properties. In this case we can get “negative temperature”.</p>
<p>In ordinary thermodynamic systems, adding energy increases the number of accessible microstates, so <img src="https://latex.codecogs.com/png.latex?S(E)"> is increasing and <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20S%7D%7B%5Cpartial%20E%7D%20%3E%200">, which implies a positive temperature. In some long-range interacting systems, however, the density of states is not monotonic. Beyond a threshold, adding more energy actually <em>reduces</em> the number of accessible microstates, so <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20S%7D%7B%5Cpartial%20E%7D%20%3C%200"> and the effective temperature becomes <em>negative</em>.</p>
<p>Onsager’s classic example is a gas of point vortices in two dimensions. At low energy you get many small, disordered vortices but at very high energy the system prefers to concentrate that vorticity into a few large, coherent “supervortices.” These macroscopic structures are more “ordered,” but they correspond to the <em>highest</em> energies and thus to negative temperature states.</p>
<p>If we push the cultural analogy, a negative-temperature regime in semantic space would be one where driving the system to higher “energy” (more extreme, differentiated works) eventually <em>reduces</em> the number of distinct configurations, because the only way to pack that much structure into a bounded perceptual manifold is to form large-scale superstructures.</p>
<p>Maybe this is already happening? Mega-franchises, shared universes… all are examples of canonical templates that organize huge numbers of micro-variations. In such a regime, additional energy no longer produces fine-grained diversity. Instead, adding energy reinforces a few giant, highly ordered attractors that dominate the landscape.</p>
</section>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>We’ve built a simple model of the space of stories using methods inspired by statistical mechanics. The model shows that, over time, the space of stories becomes more crowded. If we increase the amount of energy we pour into constructing cultural objects, the space will “run out” more quickly. As we proceed, innovation happens in “lower-order” bits.</p>
<p>How seriously should we take this? Since there’s such a huge amount of stories, it seems outlandish that we could actually run out. Regardless, I think this exercise is useful as a first step in tying some of the intuition I’m developing around information bottlenecks to physical and social processes.</p>
</section>
<section id="additional-thoughts" class="level1">
<h1>Additional Thoughts</h1>
<p>In no particular order:</p>
<ul>
<li><p>It’s possible that <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> differs based on different segments of the population.</p></li>
<li><p>If there are different segments of the population at different <img src="https://latex.codecogs.com/png.latex?%5Cbeta">, do they proceed independently through this progression? Will “intellectual superfranchises” emerge?</p></li>
<li><p><img src="https://latex.codecogs.com/png.latex?%5Cbeta"> could vary along the “bit direction”. So early bits are at a different <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> than later bits.</p></li>
<li><p>Since human intelligence is bounded, the space of stories must ultimately be bounded.</p></li>
<li><p>Stories are not actually evenly distributed. However, this doesn’t necessarily weaken the argument. In fact, if there are a limited number of “story attractors”, then we would expect some regions of story space to actually grow more crowded more quickly.</p></li>
<li><p>There may be additional modeling that could be done here. For example, can this model predict punctuated equilibrium? Maybe there are regions that are separated by “high-energy barriers” or areas of extremely low density. Maybe the semantic manifold has disconnected or quasi-disconnected components.</p></li>
<li><p>If something like Propp’s model could be made into a full-out recursive structure (like a context-free grammar), does this change the analysis? Then we are not necessarily looking at “fixed strings”.</p></li>
<li><p>Stories can be forgotten, freeing up space for stories to reoccur.</p></li>
<li><p>Are there empirical ways to test this model? What concrete, falsifiable hypotheses does it make?</p></li>
</ul>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>Probably the most heavily I’ve used AI on a post. I used ChatGPT and Claude to make a bunch of the graphs (an extremely painful process, I ended up making several graphs myself), to format LaTeX, to find sources, and for general feedback and brainstorming.</p>
<!-- [^vinge]: The "singularity" metaphor for AI stems from Vernor Vinge's 1986 novel, *Marooned in Realtime*. -->


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>A grammatical English text of length <img src="https://latex.codecogs.com/png.latex?L%5Capprox%208%5Ctimes%2010%5E4"> words has about <img src="https://latex.codecogs.com/png.latex?C%20%5Capprox%206L"> characters. Using an entropy rate of <img src="https://latex.codecogs.com/png.latex?h%5Capprox%201">–<img src="https://latex.codecogs.com/png.latex?1.5"> bits/char (<a href="https://pit-claudel.fr/clement/blog/an-experimental-estimation-of-the-entropy-of-english-in-50-lines-of-python-code/">a conservative estimate</a>) gives total information <img src="https://latex.codecogs.com/png.latex?B%20%5Capprox%20hC%20%5Capprox%201%20%5Ctext%7B%20to%20%7D%201.5%20%5Cfrac%7B%5Ctext%7Bbits%7D%7D%7B%5Ctext%7Bchar%7D%7D%5Ctimes%206%20%5Cfrac%7B%5Ctext%7Bchars%7D%7D%7B%5Ctext%7Bword%7D%7D%20%5Ctimes%20L%20%5Ctext%7B%20words%7D%20%5Capprox%20(5%5Ctimes%2010%5E5%20%5Ctext%7B%20to%20%7D%207.5%5Ctimes%2010%5E5)%5C%20%5Ctext%7Bbits%7D."> Thus the number of grammatical English sequences is <img src="https://latex.codecogs.com/png.latex?N%20%5Capprox%202%5EB%20%5Capprox%2010%5E%7B0.301B%7D%20%5Csim%2010%5E%7B(1.4%5Ctimes%2010%5E5%20%5Ctext%7B%20to%20%7D%202.2%5Ctimes%2010%5E5)%7D."> If we assume the “empty word” is in our vocabulary, we can include shorter works in the same counting argument.↩︎</p></li>
<li id="fn2"><p>See <a href="https://www.aeaweb.org/articles">here</a>, <a href="https://www.nature.com/articles/s41586-022-05543-x">here</a> or <a href="https://en.wikipedia.org/wiki/Eroom%27s_law">here</a> for some claims about science. While this essay focuses on culture, it’s possible similar arguments apply to other intellectual pursuits. We may even see some similar input-output relationships. That being said, science, math, and code have inductive structures that render some of this analysis less pertinent. For example, in math, a theorem might be stated very simply but imply an extensive proof of unknown length. Perhaps more on this in a future post.↩︎</p></li>
<li id="fn3"><p>It would be interesting to see attempts to combine these resources with LLMs to construct new stories.↩︎</p></li>
<li id="fn4"><p>We will get into the value of <img src="https://latex.codecogs.com/png.latex?k"> later.↩︎</p></li>
<li id="fn5"><p>We will examine this assumption later.↩︎</p></li>
<li id="fn6"><p>Claude suggested that, under a Chinese Restaurant Process or stick-breaking construction, the resulting ranked piece sizes follow a <img src="https://latex.codecogs.com/png.latex?w_i%20%5Cpropto%20%5Cfrac%7B1%7D%7Bi%7D"> distribution due to a connection between Dirichlet processes and Zipf’s law. This is also potentially related to <a href="https://en.wikipedia.org/wiki/Pink_noise">pink noise</a>.↩︎</p></li>
<li id="fn7"><p>https://math.stackexchange.com/questions/2848784/general-p-series-rule↩︎</p></li>
<li id="fn8"><p>https://www.jenniferellis.ca/blog/2016/8/27/hourstowriteanovel. Seems reasonable AFAICT.↩︎</p></li>
<li id="fn9"><p>I assume 35% over a baseline 100 watt human.↩︎</p></li>
<li id="fn10"><p>This is a bit weird because you could have cases where some consumers have access to some works but not others. It seems outside the scope of this post.↩︎</p></li>
<li id="fn11"><p>A better reference frame would probably be somehow drawn from the <a href="https://en.wikipedia.org/wiki/Typical_set">typical set</a>, and then the “higher-energy” texts would be more ordered, but I couldn’t figure out how to do this properly. Possibly the metric should be engineered along these lines as well.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Essays</category>
  <category>Speculative</category>
  <guid>https://demonstrandom.com/essays/posts/cultural_saturation/</guid>
  <pubDate>Sat, 06 Dec 2025 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/essays/posts/cultural_saturation/static-dynamic-gradation-klee-1923.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Noether’s Theorem with Time</title>
  <link>https://demonstrandom.com/game_theory/posts/noether_time/</link>
  <description><![CDATA[ 





<p><a href="https://www.wassilykandinsky.net/work-49.php"><img src="https://demonstrandom.com/game_theory/posts/noether_time/kandinsky-several-circles-1926.jpg" class="img-fluid"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>Let’s extend the <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html">discrete controls</a> framework and <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html">Noether’s theorems</a> to include time-invariance. We’ll start with extending the continuous version, then discretize.</p>
<p>Edit: I refactored this post on December 8th 2025, moving the discussion of homogeneous potentials and dynamical similarity to the <a href="../../../game_theory/posts/dynamical_similarity/index.html">subsequent post</a>. Some symmetries (such as dynamical similarity) do not make the physical Lagrangian strictly invariant, but become invariant only after lifting to the extended space <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20Q"> and parametrizing by <img src="https://latex.codecogs.com/png.latex?s">. In such cases, the associated Noether quantity is conserved with respect to <img src="https://latex.codecogs.com/png.latex?s"> but not generally with respect to the physical time <img src="https://latex.codecogs.com/png.latex?t">.</p>
</section>
<section id="background" class="level1">
<h1>Background</h1>
<section id="lagrangian" class="level2">
<h2 class="anchored" data-anchor-id="lagrangian">Lagrangian</h2>
<p>Before we defined our Lagrangian as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL%20:%20TQ%20%5Cto%20%5Cmathbb%7BR%7D%0A"></p>
<p>where the action was</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L(q(t),%20%5Cdot%7Bq%7D(t))%20%5C,%20dt%0A"></p>
<p>Let us now alter our definitions to explicitly include time. We want:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL':%20%5Cmathbb%7BR%7D%20%5Ctimes%20TQ%20%5Cto%20%5Cmathbb%7BR%7D%0A"></p>
<p>where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L'(t,%20q(t),%20%5Cdot%7Bq%7D(t))%20%5C,%20dt%0A"></p>
<p>Let’s start by defining a new manifold:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctilde%20Q%20:=%20%5Cmathbb%7BR%7D%20%5Ctimes%20Q"></p>
<p>Where <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20q%20%5Cin%20%5Ctilde%20Q"> looks like <img src="https://latex.codecogs.com/png.latex?(t,%20q)">.</p>
<p>In terms of <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20Q">, the Lagrangian <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20L"> is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20L:%20T%5Ctilde%20Q%20%5Cto%20%5Cmathbb%7BR%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20L"> takes as data <img src="https://latex.codecogs.com/png.latex?(%5Ctilde%20q,%20%5Cdot%20%7B%5Ctilde%20q%7D)"> and returns a number.</p>
<p>However, there’s a problem. Since <img src="https://latex.codecogs.com/png.latex?t"> is now part of the state, we need to introduce a new variable to play to role of <img src="https://latex.codecogs.com/png.latex?t"> in the adjusted formulation. Thus, we introduce a dummy parameter, “virtual time”, denoted <img src="https://latex.codecogs.com/png.latex?s"><sup>1</sup>.</p>
<p>A curve through <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20Q"> is thus <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20q(s)%20:=%20(t(s),%20%5C%20q(t(s)))">, and the action is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Ctilde%20L(%5Ctilde%20q(s),%20%5Cdot%7B%5Ctilde%20q%7D(s))%20%5C,%20ds%0A"></p>
<p>Unpacking this further, the <img src="https://latex.codecogs.com/png.latex?q">’s don’t actually depend on <img src="https://latex.codecogs.com/png.latex?s"> directly. They only depend through <img src="https://latex.codecogs.com/png.latex?t(s)">, so our “dot” operator is now with respect to <img src="https://latex.codecogs.com/png.latex?s">. What does this mean for our derivation?</p>
<p>Let’s try casting this back into physical time.</p>
<p>By definitions of <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20q"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5B%5Ctilde%20q%5D%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%5Ctilde%20L((t,%20q(t(s))),%20(%5Cdot%20t,%20%5Cfrac%7Bd%7D%7Bds%7D%5Bq(t(s))%5D))%20%20%5C,%20ds%0A"></p>
<p>Consider: <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7Bd%7D%7Bds%7D%5Bq(t(s))%5D%20=%20%5Cfrac%7Bdq%7D%7Bdt%7D(t(s))%20%5Ccdot%20%5Cfrac%7Bdt%7D%7Bds%7D%20=%20%5Cfrac%7Bdq%7D%7Bdt%7D(t(s))%20%5Ccdot%20%5Cdot%20t"></p>
<p>So</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5B%5Ctilde%20q%5D%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Ctilde%20L((t,%20q(t(s))),%20(%5Cdot%20t,%20%5Cfrac%7Bdq%7D%7Bdt%7D(t(s))%20%5Ccdot%20%5Cdot%20t))%20%5C,%20ds%0A"></p>
<p>The action is preserved, so:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Ctilde%20L((t,%20q(t(s))),%20(%5Cdot%20t,%20%5Cfrac%7Bdq%7D%7Bdt%7D(t(s))%20%5Ccdot%20%5Cdot%20t))%20%5C,%20ds%20=%20%5Cint_%7Bt_0(s_0)%7D%5E%7Bt_N(s_N)%7D%20L'(t,%20q(t),%20%5Cdot%7Bq%7D(t))%20%5C,%20dt%0A"></p>
<p>Or, by adjusting the RHS</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Ctilde%20L((t,%20q(t(s))),%20(%5Cdot%20t,%20%5Cfrac%7Bdq%7D%7Bdt%7D(t(s))%20%5Ccdot%20%5Cdot%20t))%20%5C,%20ds%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20L'(t,%20q(t),%20%5Cfrac%7Bdq%7D%7Bdt%7D)%20%5C%20%5Cdot%20t%5C,%20ds%0A"></p>
<p>Let’s define some temp variables: <img src="https://latex.codecogs.com/png.latex?A%20=%20t,%5C%20B%20=%20q(t(s)),%5C%20C%20=%20%5Cdot%20t,%5C%20D%20=%20%5Cfrac%7Bdq%7D%7Bdt%7D(t(s))%20%5Ccdot%20%5Cdot%20t">.</p>
<p>By <img src="https://latex.codecogs.com/png.latex?C"> and <img src="https://latex.codecogs.com/png.latex?D">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cfrac%7BD%7D%7BC%7D%20=%20%5Cfrac%7Bdq%7D%7Bdt%7D(t(s))"></p>
<p>Therefore:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL((A,%20B),%20(C,%20D))%20=%20CL'(A,%20B,%20%5Cfrac%7BD%7D%7BC%7D)%0A"></p>
<p>Which implies</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Ctilde%20L((t,%20q(t(s))),%20(%5Cdot%20t,%20%5Cfrac%7Bdq%7D%7Bdt%7D(t(s))%20%5Ccdot%20%5Cdot%20t))%20%5C,%20ds%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Cdot%20t%20L'(t,%20q(t),%20%5Cfrac%7Bdq%7D%7Bdt%7D)%20ds%0A"></p>
<p>Seen another way, the curve we are integrating over in <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20Q"> is defined by <img src="https://latex.codecogs.com/png.latex?%5Cgamma(s):=(t(s),%20q(t(s)))">. Its derivative is <img src="https://latex.codecogs.com/png.latex?(%5Cdot%20t,%20%5Cfrac%7Bdq%7D%7Bdt%7D%20%5Ccdot%20%5Cdot%20t)">. If <img src="https://latex.codecogs.com/png.latex?%5Cdot%20t%20=%201">, then <img src="https://latex.codecogs.com/png.latex?t%20=%20s"> (up to a constant), and the curve is <img src="https://latex.codecogs.com/png.latex?%5Cgamma(t):=(t,%20q(t))"> with the derivative is <img src="https://latex.codecogs.com/png.latex?(1,%20%5Cfrac%7Bdq%7D%7Bdt%7D)">.</p>
<p>So the <img src="https://latex.codecogs.com/png.latex?%5Cdot%20t"> factor is just a linear reparametrization of our curve. Glossing over a few steps, we “normalize” by <img src="https://latex.codecogs.com/png.latex?%5Cdot%20t"> and match arguments to get the following:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20L'(t,%20q,%20%5Cfrac%7B%5Cdot%20q%7D%7B%5Cdot%20t%7D)%20%5C%20%5Cdot%20t%20%5C,%20ds%0A"></p>
<p>(Note <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7Bdq%7D%7Bdt%7D%20=%20%5Cfrac%7Bdq/ds%7D%7Bdt/ds%7D">)</p>
<p>The upshot is that if the symmetry affects <img src="https://latex.codecogs.com/png.latex?t">, then <img src="https://latex.codecogs.com/png.latex?%5Cdot%20q"> needs to be adjusted by “dividing out” the change in the time variable with respect to virtual time<sup>2</sup>.</p>
<p>Also notice, if <img src="https://latex.codecogs.com/png.latex?t%20=%20t(s)">, we have <img src="https://latex.codecogs.com/png.latex?dt%20=%20%5Cdot%20t%20ds">.</p>
<p>So</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bt(s_0)%7D%5E%7Bt(s_N)%7D%20L'(t,%20q,%20%5Cfrac%7B%5Cdot%20q%7D%7B%5Cdot%20t%7D)%20%5C,%20dt%0A"></p>
<p>In particular, if <img src="https://latex.codecogs.com/png.latex?t(s)%20=%20s">, we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L'(t,%20q,%20%5Cdot%20q)%20%5C,%20dt%0A"></p>
<p>This implies that any Lagrangian <img src="https://latex.codecogs.com/png.latex?L(q,%20%5Cdot%20q)"> can be extended “for free” into <img src="https://latex.codecogs.com/png.latex?L'(t,%20q,%20%5Cdot%20q)"> under the trivial reparametrization <img src="https://latex.codecogs.com/png.latex?t=s"><sup>3</sup>.</p>
</section>
<section id="g-invariance-and-noether-charge" class="level2">
<h2 class="anchored" data-anchor-id="g-invariance-and-noether-charge">G-Invariance and Noether Charge</h2>
<p>By analogy with the time-free case, if <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20q%20%5Cin%20%5Ctilde%20Q">, we define a map <img src="https://latex.codecogs.com/png.latex?%5CPhi'_g"> such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20%5CPhi_g%20:%20G%20%5Ctimes%20%5Ctilde%20Q%20%5Cto%20%5Ctilde%20Q%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20%5CPhi_g(%5Ctilde%20q)%20:=%20%5Ctilde%20%5CPhi_g(t,%20q)%20=%20(t',%20q')%0A"></p>
<p>Where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cquad%20t'%20:=%20%5Ctext%7Bproj%7D_1(%5Ctilde%5CPhi_g(t,q))%0A"> and <img src="https://latex.codecogs.com/png.latex?%0A%5Cquad%20q'%20:=%20%5Ctext%7Bproj%7D_2(%5Ctilde%5CPhi_g(t,q))%0A"></p>
<p>The <img src="https://latex.codecogs.com/png.latex?%5Cproj_i"> are just operators that “unpack” the tuple and return the <img src="https://latex.codecogs.com/png.latex?i">-th argument.</p>
<p>We also construct the map</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AT%5Ctilde%5CPhi_g:%20T%5Ctilde%20Q%20%5Cto%20T%5Ctilde%20Q%0A"> <img src="https://latex.codecogs.com/png.latex?%0AT%5Ctilde%5CPhi_g%20:%20T_q%5Ctilde%20Q%20%5Cto%20T_%7B%5Ctilde%5CPhi_g(%5Ctilde%20q)%7D%5Ctilde%20Q%0A"></p>
<p>Note that <img src="https://latex.codecogs.com/png.latex?T%20%5Ctilde%20Q%20%5Ccong%20%5Cmathbb%7BR%7D%20%5Ctimes%20Q%20%5Ctimes%20%5Cmathbb%7BR%7D%20%5Ctimes%20TQ">.</p>
<p>Once again, this matches our previous derivation, but on <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20Q"> instead of <img src="https://latex.codecogs.com/png.latex?Q">.</p>
<p>We can now state our updated <img src="https://latex.codecogs.com/png.latex?G">-invariance principle. We say that <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20L"> is <img src="https://latex.codecogs.com/png.latex?G">-invariant if:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cforall%20g%20%5Cin%20G,%20q%5Cin%20Q">, <img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20L(%5Ctilde%20%5CPhi_g(%5Ctilde%20q),%20T%5Ctilde%5CPhi_g(%5Cdot%20%7B%5Ctilde%20q%7D))%20=%20%5Ctilde%20L(%5Ctilde%20q,%20%5Cdot%20%7B%5Ctilde%20q%7D)%0A"></p>
<p>At this point, we have reduced the problem back to the original <a href="../../../game_theory/posts/noether_geometric_controls/index.html#noethers-theorem">Noether’s theorem proof</a>.</p>
<p>We need to make one change. In the original proof we started here</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5B%0AL%5Cbigl(q_%5Cepsilon(t),%5C,%20%5Cdot%20q_%5Cepsilon(t))%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5Ccdot%20%5Cfrac%7Bdq_%7B%5Cepsilon%7D%7D%7Bd%7B%5Cepsilon%7D%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A+%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D%0A%5Ccdot%0A%5Cfrac%7Bd%5Cdot%20q_%7B%5Cepsilon%7D%7D%7Bd%5Cepsilon%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctilde%20L"> has two tuples as arguments</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5B%0A%5Ctilde%20L%5Cbigl((t,%20q_%5Cepsilon(t)),%5C,(%5Cdot%20t,%20%5Cdot%20q_%5Cepsilon(t)))%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%0A%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20t%7D%20%5Cfrac%7B%5Cpartial%20t%7D%7B%5Cpartial%20%5Cepsilon%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A+%0A%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20q%7D%5Ccdot%20%5Cfrac%7Bdq_%7B%5Cepsilon%7D%7D%7Bd%7B%5Cepsilon%7D%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A+%20%5Cnewline%0A%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20t%7D%20%5Cfrac%7B%5Cpartial%20%5Cdot%20t%7D%7B%5Cpartial%20%5Cepsilon%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A+%0A%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D%0A%5Ccdot%0A%5Cfrac%7Bd%5Cdot%20q_%7B%5Cepsilon%7D%7D%7Bd%5Cepsilon%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A"></p>
<p>Our Noether charge ends up being:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20t%7D%5Cfrac%7B%5Cpartial%20t%7D%7B%5Cpartial%20%5Cepsilon%7D%20%5Cbiggr%7C_%7B%5Cepsilon=0%7D%20+%20%5C%20p%5Ccdot%20%5Comega_Q(q)%0A"></p>
</section>
<section id="lie-groups" class="level2">
<h2 class="anchored" data-anchor-id="lie-groups">Lie Groups</h2>
<p>What changes when <img src="https://latex.codecogs.com/png.latex?Q%20=%20G">, where <img src="https://latex.codecogs.com/png.latex?G"> is a Lie group<sup>4</sup>?</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D"> is a Lie group, and Lie groups are closed under product. So the product of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D"> and <img src="https://latex.codecogs.com/png.latex?G"> is also a Lie group.</p>
<p>We can then use the same reparametrization trick. Call <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20G%20=%20%5Cmathbb%7BR%7D%20%5Ctimes%20Q">, with <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20g%20%5Cin%20%5Ctilde%20G"> and <img src="https://latex.codecogs.com/png.latex?g%20%5Cin%20G">.</p>
<p>If you recall, for left-invariant Lie groups we actually are interested in the <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html#lie-groups">reduced lagrangian</a> for a Lie Group<sup>5</sup>:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5Ctext%7Bid%7D_G,%20%5Comega)%20=%20%5Cell(%5Comega)%0A"></p>
<p>where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Comega%20=%20g%5E%7B-1%7D(t)%5Cdot%20g(t)%20%5Cin%20%5Cmathfrak%7Bg%7D%0A"></p>
<p>(this is the tangent along some path <img src="https://latex.codecogs.com/png.latex?g(t)"> starting from the origin).</p>
<p>Now, we instead seek the reduced Lagrangian dependent on time</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cell(t,%20%5Comega)%0A"></p>
<p>with action</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5B%5Comega%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_n%7D%20%5Cell(t,%20%5Comega)dt%0A"></p>
<p>For our extended problem, we get “for free”</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5B%5Ctilde%20%5Comega%5D%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_n%7D%20%5Ctilde%20%5Cell(%5Ctilde%20%5Comega)ds%0A"></p>
<p>We want to put this in terms of <img src="https://latex.codecogs.com/png.latex?t">. First of all, since, we can unpack <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20%5Comega"> into constituent parts<sup>6</sup></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctilde%20%5Comega%20=%20(t%5E%7B-1%7D%5C%20%5Cdot%20t,%5C%20g%5E%7B-1%7D%5C%20%5Cdot%20g)%20%5Cin%20%5Cmathbb%7BR%7D%20%5Ctimes%20%5Cmathfrak%7Bg%7D%0A"></p>
<p>We need to work out <img src="https://latex.codecogs.com/png.latex?%5Cdot%20g">, since the dot operator is now with respect to <img src="https://latex.codecogs.com/png.latex?s">. By the chain rule:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bds%7D%5Bg(t(s))%5D%20=%20%5Cfrac%7Bdg%7D%7Bdt%7D%5Ccdot%20%5Cdot%20t%0A"></p>
<p>Thus<sup>7</sup></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5B%5Ctilde%20%5Comega%5D%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_n%7D%20%5Ctilde%20%5Cell((t%5E%7B-1%7D%20%5Ccdot%20%5Cdot%20t,%20g%5E%7B-1%7D%5Cfrac%7Bdg%7D%7Bdt%7D%5Ccdot%20%5Cdot%20t%20))ds%0A"></p>
<p>Call <img src="https://latex.codecogs.com/png.latex?%5Comega%20:=%20g%5E%7B-1%7D%5Cfrac%7Bdg%7D%7Bdt%7D">.</p>
<p>The action is preserved, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Ctilde%20%5Cell((t%5E%7B-1%7D%20%5Ccdot%20%5Cdot%20t,%20%5Comega%20%5Ccdot%20%5Cdot%20t))ds%20=%20%5Cint_%7Bt(s_0)%7D%5E%7Bt(s_N)%7D%20%5Cell'(t,%20%5Comega)%20dt%0A"></p>
<p>We need <img src="https://latex.codecogs.com/png.latex?%5Cell'"> (in terms of <img src="https://latex.codecogs.com/png.latex?%5Cell">) that makes this equation true.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Ctilde%20%5Cell((t%5E%7B-1%7D%20%5Ccdot%20%5Cdot%20t,%20%5Comega%20%5Ccdot%20%5Cdot%20t))ds%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Cell'(t,%20%5Comega)%20%5Cdot%20t%20%5C%20ds%0A"></p>
<p>Mapping back to the original reduced lagrangian via matching argument (same as in the original derivation; omitted), we therefore have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5B%5Comega%5D%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20t%20(t%5E%7B-1%7D%20%5Cdot%20t)%20%5C%20%5Cell(t,%20%5Cfrac%7B%5Comega%20%5Cdot%20t%7D%7Bt%20(t%5E%7B-1%7D%20%5Cdot%20t)%7D)%20%5C%20%20ds%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Cdot%20t%20%5C%20%5Cell(t,%20%5Comega)%20%5C%20%20ds%0A"></p>
<p>Interestingly, if you squint you can see a lot of “conjugation actions” that might pop up for a general extension by an arbitrary non-abelian “time group <img src="https://latex.codecogs.com/png.latex?T">”, rather than <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D"> specifically<sup>8</sup>.</p>
<p>As a last aside, notice if we change variables back to <img src="https://latex.codecogs.com/png.latex?t">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5B%5Comega%5D%20=%20%5Cint_%7Bs_0%7D%5E%7Bs_N%7D%20%5Cdot%20t%20%5C%20%5Cell(t,%20%5Comega)%20%5C%20%20ds%20=%20%20%5Cint_%7Bt(s_0)%7D%5E%7Bt(s_N)%7D%20%5Cell(t,%20%5Comega)%20%5C%20%20ds%0A"></p>
<p>So we successfully added a <img src="https://latex.codecogs.com/png.latex?t"> to <img src="https://latex.codecogs.com/png.latex?%5Cell">, and can in fact do this to any arbitrary <img src="https://latex.codecogs.com/png.latex?%5Cell"> “without penalty”. So our Lie groups are already normalized by time, “naturally” (it’s included in <img src="https://latex.codecogs.com/png.latex?%5Comega">).</p>
</section>
<section id="examples" class="level2">
<h2 class="anchored" data-anchor-id="examples">Examples</h2>
<p>Let’s look at some examples.</p>
<section id="energy" class="level3">
<h3 class="anchored" data-anchor-id="energy">Energy</h3>
<p>Let’s say we have <img src="https://latex.codecogs.com/png.latex?%0AL(t,%20q,%20%5Cdot%20q)%0A"></p>
<p>preserved under symmetry</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(t,%20q)%20%5Cmapsto%20(t_%7B%5Cepsilon%7D,%20%5C%20q_%7B%5Cepsilon%7D)%20=%20(t%20+%20%5Cepsilon,%20%5C%20q)%0A"></p>
<p>This implies that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Comega_%7BQ%7D(q)%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Bq_%7B%5Cepsilon%7D(t)%5D%5Cbigg%7C_%7B%5Cepsilon%20=%200%7D%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Bq(t%20+%20%5Cepsilon)%5D%5Cbigg%7C_%7B%5Cepsilon%20=%200%7D%20=%200%0A"></p>
<p>We have that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bdt%7D%7Bd%5Cepsilon%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%20=%201%0A"></p>
<p>So we need</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20%5Ctilde%20L%7D%7B%5Cpartial%20%5Cdot%20t%7D%20=%20%5Cfrac%7B%5Cpartial%20%7D%7B%5Cpartial%20%5Cdot%20t%7D%5B%5Cdot%20t%20L'(t,%20q,%20%5Cfrac%7B%5Cdot%20q%7D%7B%5Cdot%20t%7D)%5D%20=%20L'%20-%20%5Cfrac%7B%5Cpartial%20L'%7D%7B%5Cpartial%20%5Cdot%20q%7D%20%5Cfrac%7B%5Cdot%20q%7D%7B%5Cdot%20t%7D%0A"></p>
<p>In the usual parametrization (<img src="https://latex.codecogs.com/png.latex?%5Cdot%20t%20=%201">) the Noether charge is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL'%20-%20p%20%5Cdot%20q%0A"></p>
<p>In our <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html#hamiltonian">Hamiltonian exposition</a>, we defined</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t,%20v,%20p)%20=%20%5Csup_v%20(p(v)%20-%20L'(t,%20q,%20v))%0A"></p>
<p>If we evaluate this at the unique <img src="https://latex.codecogs.com/png.latex?v(q,p)"> such that <img src="https://latex.codecogs.com/png.latex?p%20=%20%5Cfrac%7B%5Cpartial%20L'%7D%7B%5Cpartial%20%5Cdot%20q%7D">, then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(t,%20q,%20p)%20=%20p%20%5Cdot%20q%20-%20L'(t,%20q,%20%5Cdot%20q)%0A"></p>
<p>So the (negative) Hamiltonian is conserved (the total energy).</p>
</section>
<section id="linear-momentum" class="level3">
<h3 class="anchored" data-anchor-id="linear-momentum">Linear Momentum</h3>
<p>Consider the following transformation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A(t,%20q)%20%5Cmapsto%20(t,%20q%20+%20c%5Cepsilon)%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?t"> is static so we only need to worry about <img src="https://latex.codecogs.com/png.latex?p%5Ccdot%20w_Q(q)">.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aw_Q(q)%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Bq_%7B%5Cepsilon%7D(t)%5D%5Cbigg%7C_%7B%5Cepsilon%20=%200%7D%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Bq%20+%20c%5Cepsilon%5D%5Cbigg%7C_%7B%5Cepsilon%20=%200%7D%20=%20c%0A"></p>
<p>So <img src="https://latex.codecogs.com/png.latex?pc"> is conserved. Setting <img src="https://latex.codecogs.com/png.latex?c%20=%201"> gives conservation of <img src="https://latex.codecogs.com/png.latex?p">. If we view <img src="https://latex.codecogs.com/png.latex?c"> as vector valued we can view this as conservation of momentum along each dimension.</p>
</section>
<section id="galilean-boost" class="level3">
<h3 class="anchored" data-anchor-id="galilean-boost">Galilean Boost</h3>
<p>Omitted.</p>
<p>ChatGPT suggested that you might be able to derive Kinetic energy via symmetry of the “free Lagrangian” under <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5En%20%5Crtimes%20%5Ctext%7BSO%7D_n">.</p>
</section>
</section>
</section>
<section id="discrete-noether-with-time" class="level1">
<h1>Discrete Noether with Time</h1>
<p>As we saw above, for Lie groups the reduced Lagrangian is unchanged for Lie groups. The only thing we really need to do is update the computation of the Noether charge (if time is included).</p>
<section id="code" class="level2">
<h2 class="anchored" data-anchor-id="code">Code</h2>
<p>Claude Opus 4.5 + ChatGPT (with some coaxing through several major issues) was able to modify the code.</p>
<p>In the last post, we had this function</p>
<div id="521c3925" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> register_noether_charge(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, name:<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, symmetry: Symmetry):</span>
<span id="cb1-2">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _new_charge(qk, qk1):</span>
<span id="cb1-3">            p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D2_Ld(qk, qk1)</span>
<span id="cb1-4"></span>
<span id="cb1-5">            qk1_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetry.log(symmetry.exp(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> symmetry.generator) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> symmetry.exp(qk1))</span>
<span id="cb1-6">            omega_qk1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (qk1_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> qk1)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol</span>
<span id="cb1-7"></span>
<span id="cb1-8">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>omega_qk1).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb1-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.noether_charges[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _new_charge </span></code></pre></div>
</div>
<p>For time, we need to add the time-dependent term:</p>
<p>To handle time-dependent symmetries, we update both <code>Symmetry</code> and <code>register_noether_charge</code> to work with an infinitesimal parameter <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> acting on both space and time:</p>
<div id="9baa44ae" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Symmetry:</span>
<span id="cb2-2"></span>
<span id="cb2-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(</span>
<span id="cb2-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb2-5">        space_transform: Callable[..., torch.Tensor],</span>
<span id="cb2-6">        time_transform: Optional[Callable[..., <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>,</span>
<span id="cb2-7">    ):</span>
<span id="cb2-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._space_transform <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> space_transform</span>
<span id="cb2-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._time_transform <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time_transform</span>
<span id="cb2-10"></span>
<span id="cb2-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> apply_space(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, eps: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, t: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, q: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb2-12"></span>
<span id="cb2-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb2-14">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._space_transform(eps, t, q)</span>
<span id="cb2-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>:</span>
<span id="cb2-16">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Allow simpler signatures like f(eps, q)</span></span>
<span id="cb2-17">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._space_transform(eps, q)</span>
<span id="cb2-18"></span>
<span id="cb2-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> apply_time(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, eps: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, t: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, q: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb2-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._time_transform <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb2-21">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> t</span>
<span id="cb2-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb2-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._time_transform(eps, t, q)</span>
<span id="cb2-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>:</span>
<span id="cb2-25">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Allow simpler signatures like f(eps, t)</span></span>
<span id="cb2-26">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._time_transform(eps, t)</span></code></pre></div>
</div>
<div id="53a33260" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> register_noether_charge(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, symmetry: Symmetry):</span>
<span id="cb3-2">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _charge(qk, qk1, t_k, t_k1):</span>
<span id="cb3-3">            p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D2_Ld(qk, qk1)</span>
<span id="cb3-4">        </span>
<span id="cb3-5">            eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol</span>
<span id="cb3-6">        </span>
<span id="cb3-7">            q_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetry.apply_space(eps, t_k1, qk1)</span>
<span id="cb3-8">            omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (q_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> qk1) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> eps</span>
<span id="cb3-9">        </span>
<span id="cb3-10">            t_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetry.apply_time(eps, t_k1, qk1)</span>
<span id="cb3-11">            tau <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (t_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> t_k1) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> eps</span>
<span id="cb3-12"></span>
<span id="cb3-13">            charge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> omega).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb3-14">        </span>
<span id="cb3-15">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> tau <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>:</span>
<span id="cb3-16">                E <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.discrete_energy(qk, qk1)</span>
<span id="cb3-17">                charge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> charge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> E <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> tau</span>
<span id="cb3-18">        </span>
<span id="cb3-19">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> charge</span>
<span id="cb3-20">    </span>
<span id="cb3-21">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.noether_charges[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _charge</span></code></pre></div>
</div>
<section id="example" class="level3">
<h3 class="anchored" data-anchor-id="example">Example</h3>
<p>We define</p>
<div id="c758cc20" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Kepler(VariationalSystem):</span>
<span id="cb4-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> control_plane(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb4-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb4-4">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>: Rn(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb4-5">        }</span>
<span id="cb4-6">        </span>
<span id="cb4-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> params(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb4-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mu"</span>]</span>
<span id="cb4-9"></span>
<span id="cb4-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lagrangian(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ctrl, dctrl):</span>
<span id="cb4-11">        r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ctrl.r</span>
<span id="cb4-12">        rdot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dctrl.r</span>
<span id="cb4-13">        m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.mass</span>
<span id="cb4-14">        mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.mu</span>
<span id="cb4-15">            </span>
<span id="cb4-16">        r_norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.sqrt((r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> r).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-10</span>)</span>
<span id="cb4-17">        T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (rdot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> rdot).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb4-18">        V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> r_norm</span>
<span id="cb4-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> V</span></code></pre></div>
</div>
<p>We run it with</p>
<div id="f068c906" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb5-2">    kepler <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Kepler({</span>
<span id="cb5-3">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>,</span>
<span id="cb5-4">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mu"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb5-5">    })</span>
<span id="cb5-6"></span>
<span id="cb5-7">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span></span>
<span id="cb5-8">    recorder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StepRecorder()</span>
<span id="cb5-9">    integrator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> VariationalIntegrator(kepler, step_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>h, on_step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>recorder.on_step)</span>
<span id="cb5-10"></span>
<span id="cb5-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Energy (time translation): (eps, t) -&gt; t + eps</span></span>
<span id="cb5-12">    energy_sym <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Symmetry(</span>
<span id="cb5-13">        space_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> eps, t, q: q,</span>
<span id="cb5-14">        time_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> eps, t, q: t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> eps</span>
<span id="cb5-15">    )</span>
<span id="cb5-16">    integrator.register_noether_charge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"energy"</span>, energy_sym)</span>
<span id="cb5-17"></span>
<span id="cb5-18">    r_slice <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kepler.model.layout[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb5-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> rotate_r(eps, t, q, sl<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>r_slice):</span>
<span id="cb5-20">        qn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q.clone()</span>
<span id="cb5-21">        x, y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q[sl]</span>
<span id="cb5-22">        c, s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> math.cos(eps), math.sin(eps)</span>
<span id="cb5-23">        qn[sl] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y, s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>q.dtype)</span>
<span id="cb5-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> qn</span>
<span id="cb5-25"></span>
<span id="cb5-26">    angular_sym <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Symmetry(space_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rotate_r)</span>
<span id="cb5-27">    integrator.register_noether_charge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"angular_momentum"</span>, angular_sym)</span>
<span id="cb5-28"></span>
<span id="cb5-29">    alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span></span>
<span id="cb5-30"></span>
<span id="cb5-31">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> scale_r(eps, t, q, sl<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>r_slice):</span>
<span id="cb5-32">        qn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q.clone()</span>
<span id="cb5-33">        qn[sl] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> math.exp(eps) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> q[sl]</span>
<span id="cb5-34">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> qn</span>
<span id="cb5-35"></span>
<span id="cb5-36">    dyn_sim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Symmetry(</span>
<span id="cb5-37">        space_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>scale_r,</span>
<span id="cb5-38">        time_transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> eps, t, q: math.exp(alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> eps) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t</span>
<span id="cb5-39">    )</span>
<span id="cb5-40"></span>
<span id="cb5-41">    integrator.register_noether_charge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dynamical_similarity"</span>, dyn_sim)</span>
<span id="cb5-42"></span>
<span id="cb5-43">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initial conditions for elliptical orbit</span></span>
<span id="cb5-44">    r0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64)</span>
<span id="cb5-45">    v0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64)</span>
<span id="cb5-46">    t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64)</span>
<span id="cb5-47"></span>
<span id="cb5-48">    ctrl0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>: r0, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t"</span>: t0})</span>
<span id="cb5-49">    q0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kepler.model.pack(ctrl0)</span>
<span id="cb5-50"></span>
<span id="cb5-51">    steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span></span>
<span id="cb5-52"></span>
<span id="cb5-53">    ctrl1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>: r0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> v0, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t"</span>: t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h})</span>
<span id="cb5-54">    q1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kepler.model.pack(ctrl1)</span>
<span id="cb5-55"></span>
<span id="cb5-56">    qs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [q0.clone(), q1.clone()]</span>
<span id="cb5-57">    q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q0, q1</span>
<span id="cb5-58"></span>
<span id="cb5-59">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tqdm.tqdm(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)):</span>
<span id="cb5-60">        q_next, ok <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> integrator.step(q_prev, q_curr)</span>
<span id="cb5-61">        qs.append(q_next.clone())</span>
<span id="cb5-62">        q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr, q_next</span>
<span id="cb5-63"></span>
<span id="cb5-64">    qs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.stack(qs, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb5-65"></span>
<span id="cb5-66">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Kepler Problem:"</span>)</span>
<span id="cb5-67">    energies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"energy"</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> recorder.records]</span>
<span id="cb5-68">    angular <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"angular_momentum"</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> recorder.records]</span>
<span id="cb5-69">    similarity <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dynamical_similarity"</span>]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> recorder.records]</span>
<span id="cb5-70">    </span>
<span id="cb5-71">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Energy (should be constant):"</span>)</span>
<span id="cb5-72">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"  min:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(energies), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(energies), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"drift:"</span>, energies[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> energies[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb5-73">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Angular momentum (should be constant):"</span>)</span>
<span id="cb5-74">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"  min:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(angular), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(angular), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"drift:"</span>, angular[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> angular[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span></code></pre></div>
</div>
<p>Ignore the “dynamical similarity” material for now.</p>
<p>We get:</p>
<pre><code>Kepler Problem:
Energy (should be constant):
  min: 0.6735297151115243 max: 0.6868012832352087 drift: -0.0026780225061522334
Angular momentum (should be constant):
  min: 0.8000193606114198 max: 0.8000206253911845 drift: -1.9376809246018922e-07</code></pre>
<p>As expected.</p>
</section>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>I originally thought this would be a super short post but the tale grew in the telling, plus led me to several additional rabbit holes that I may pursue in future posts (algebraic geometry! self-similarity!). We are still doing “physics” but I will eventually reach “controls”. Thanks for reading.</p>
</section>
<section id="changelog" class="level1">
<h1>Changelog</h1>
<p>12/8/2025 - Refactored post to move part of Kepler example.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Why not just assume the Lagrangian is constant under translation by time? My intent here is to try and preserve the option of “dynamical similarity”, where time is scaled simultaneously with some other variable, or by some transformation other than <img src="https://latex.codecogs.com/png.latex?t%20%5Cmapsto%20t%20+%20%5Cepsilon">. Another thought: could you have some kind of degenerate dynamical similarity <em>without</em> some notion of virtual time?↩︎</p></li>
<li id="fn2"><p>One quick note: <img src="https://latex.codecogs.com/png.latex?%5Cdot%20t%20=%200"> would correspond to some kind of degenerate symmetry, where <img src="https://latex.codecogs.com/png.latex?t%20%5Cto%20%5Ctext%7Bconstant%7D">. So it shouldn’t happen.↩︎</p></li>
<li id="fn3"><p>In code terms, this means we can just add a dummy argument <img src="https://latex.codecogs.com/png.latex?t"> to any existing Lagrangian function <img src="https://latex.codecogs.com/png.latex?L(q,%20%5Cdot%20q)"> and discard it when doing calculations.↩︎</p></li>
<li id="fn4"><p>Slight notation discrepancy - this is a different <img src="https://latex.codecogs.com/png.latex?G"> than in the previous section.↩︎</p></li>
<li id="fn5"><p>Slight notation discrepancy - this is a different <img src="https://latex.codecogs.com/png.latex?G"> than in the previous section.↩︎</p></li>
<li id="fn6"><p><img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D"> is it’s own Lie Algebra.↩︎</p></li>
<li id="fn7"><p>Please note that <img src="https://latex.codecogs.com/png.latex?t%5E%7B-1%7D"> doesn’t imply anything about the group structure of the time dimension. It is simply the group inverse.↩︎</p></li>
<li id="fn8"><p>I’m not 100% sure this makes sense due to the way the parametrizations work, but I think there may be some general algorithm “extending” a Lagrangian by appending new Lie groups to the manifold.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Geometric Controls</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/game_theory/posts/noether_time/</guid>
  <pubDate>Fri, 05 Dec 2025 05:00:00 GMT</pubDate>
  <media:content url="https://www.wassilykandinsky.net/work-49.php" medium="image"/>
</item>
<item>
  <title>Is a Picture Worth a Thousand Words?</title>
  <link>https://demonstrandom.com/essays/posts/picture_worth_thousand_words/</link>
  <description><![CDATA[ 





<p><img src="https://demonstrandom.com/essays/posts/picture_worth_thousand_words/magritte.jpeg" class="img-fluid" alt="Not a real Magritte"></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>In a <a href="../../../essays/posts/preference_oracles/index.html">previous essay</a> I considered the problems associated with generating novels. With the introduction of the new <a href="https://blog.google/technology/ai/nano-banana-pro/">Nano-Banana Pro</a>, let’s revisit those same basic information bottleneck argument with respect to pictures. All arguments are back-of-the-envelope.</p>
</section>
<section id="count-the-bits" class="level1">
<h1>Count the Bits</h1>
<p>Consider the map:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AF:%20%5Ctext%7BText%7D%20%5Cto%20%5Ctext%7BPictures%7D%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?F"> is Nano-Banana (or any other generative model that produces pictures). You feed it a sequence of words and out pops a picture<sup>1</sup>.</p>
<p>Let’s suppose for simplicity that <img src="https://latex.codecogs.com/png.latex?F"> is a function (so a given input produces just one deterministic output) and that <img src="https://latex.codecogs.com/png.latex?F"> is surjective<sup>2</sup> (for a given picture, there is at least one “text” that maps to it).</p>
<p><img src="https://demonstrandom.com/essays/posts/picture_worth_thousand_words/surjectivity.jpg" class="img-fluid" style="width:100.0%" alt="Surjectivity - 110+ words prompted"></p>
<p>How many possible pictures are there?</p>
<p>Let’s assume pictures are 2048 x 2048 pixels<sup>3</sup>. Then there are <img src="https://latex.codecogs.com/png.latex?4194304"> total pixels. At 24 bits/pixel, we have <img src="https://latex.codecogs.com/png.latex?100663296"> bits in a picture.</p>
<p>If the map was a surjective function, the number of inputs must be at least as large as the number of possible outputs. In order to specify a particular pixel array uniquely, we need to come up with a set of words that point to it. If we assume a generous 15 bits per word<sup>4</sup>, and an input must be at least <img src="https://latex.codecogs.com/png.latex?100663296"> bits, we therefore need at least <img src="https://latex.codecogs.com/png.latex?6710886"> words, or roughly <img src="https://latex.codecogs.com/png.latex?13000"> single-spaced pages of text, to exactly specify one image.</p>
</section>
<section id="natural-image-manifold" class="level1">
<h1>Natural Image Manifold</h1>
<p><img src="https://demonstrandom.com/essays/posts/picture_worth_thousand_words/natural_image_manifold.jpeg" class="img-fluid" style="width:100.0%" alt="Natural Image Manifold - 11 words prompted"></p>
<p>Obviously, that conclusion is absurd. It’s not actually that hard to generate roughly what you want in Nano-Banana Pro. For example, the picture above I generated with 11 words. The result was reasonable, and close enough to my intent that I included it here.</p>
<p>Most random arrangements of pixels look like static noise. The ‘natural image manifold’ is the (very small) subsection of pixel space containing images recognizable (or at least, of interest) to humans. And the map from text to images is not actually surjective.</p>
<p><img src="https://demonstrandom.com/essays/posts/picture_worth_thousand_words/nano-banana1.jpeg" class="img-fluid"></p>
<p>How big is the natural image manifold?</p>
<p>State-of-the-art codecs can get 0.3–0.7 bpp before noticeable artifacts show up<sup>5</sup>. Let’s take the middle value. Our 2048 by 2048 image has <img src="https://latex.codecogs.com/png.latex?4194304"> pixels. At 0.5 bpp, that’s approximately <img src="https://latex.codecogs.com/png.latex?2097152"> bits of perceptually relevant information. That’s around 140000 words, or 280 pages.</p>
<p>So we might say that a picture is worth 140000 words.</p>
</section>
<section id="semantic-images" class="level1">
<h1>Semantic Images</h1>
<p><img src="https://demonstrandom.com/essays/posts/picture_worth_thousand_words/robot.jpeg" class="img-fluid"></p>
<p>In practice, usually a human is looking for an image drawn from a rough equivalence class of images rather than a particular set of pixels.</p>
<p>A prompt like “a robot on a bench” is a whole region of the natural image manifold, which contains millions of possible pictures. You can ask:</p>
<ul>
<li>which robot?</li>
<li>what exact pose?</li>
<li>surrounded by which tree species?</li>
<li>where is the sun?</li>
<li>what’s the weather?</li>
<li>how high is the camera?</li>
<li>is it a 35mm lens? an 85mm lens?</li>
<li>are there spiderwebs? broken branches?</li>
</ul>
<p>People care about object identity, relations, scene type, lighting category, viewpoint class, mood, style, etc. How many bits of semantic control over images does a user really need? And given a piece of text, most of the text is redundant in terms of semantic bits. The same choices are reinforced: which objects are present, the style, the lighting, etc.</p>
<p>A toy upper bound might be 50 bits. That’s roughly <img src="https://latex.codecogs.com/png.latex?10%5E%7B15%7D"> possible categories. And a 50 bit password is already very secure<sup>6</sup>. That’s why prompting can work as well as it does. The gap is filled with non-semantic visual content: whatever the user takes for granted.</p>
<p>So is a picture worth a thousand words? It depends on the picture, the words, and what the viewer cares about.</p>
</section>
<section id="art-golf" class="level1">
<h1>Art Golf</h1>
<p>Consider the following game (“Art Golf”). Take an image or piece of art (especially an abstract image, or unknown work). Without giving the name of the artist, the name of the piece of art, or the name of a particular artistic movement, try to prompt the generative model to produce the original piece of art using only text. Fewer words is better.</p>
</section>
<section id="final-thoughts" class="level1">
<h1>Final Thoughts</h1>
<p><img src="https://demonstrandom.com/essays/posts/picture_worth_thousand_words/key.jpeg" class="img-fluid"></p>
<p>If you use a prompt of 10 to 50 words (150 to 700 “bits on disk”), and a natural 2K image is 2.5 million bits, then the model must be filling in 99.9%+ of the visual detail. You can call this “hallucinating”, or “inference”: at the end of the day the human is making a small number of decisions to determine a much larger object.</p>
<p>I don’t think making the model bigger can “fix” this. We might better approximate the natural image distribution (needing fewer words to specify an image), but at the end of the day there’s no way to produce a uniquely specified image unless the prompt contains enough bits to select that image. The human must specify all the necessary bits to identify the required image.</p>
<p>This isn’t limited to AI, but applies in general to all principals and agents. If you commission an artwork or design, you commission a specification and the artist figures out the details. Outsourcing to an external party is only valuable when you <em>don’t</em> want to specify every bit.</p>
<p>While they are both considered “AI”, the problem of intent seems fundamentally different than the problem of converting between words and text.</p>
</section>
<section id="ai-disclosure" class="level1">
<h1>AI Disclosure</h1>
<p>All of the images in this post were made using Nano-Banana Pro.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>In theory you can also feed a generative model a picture, or information in some other form. You could add sketches, CAD models, structured scene graphs, reference images, etc. But while this does make things much more efficient, we ultimately run into the paradox where we are specifying the image to specify the image.↩︎</p></li>
<li id="fn2"><p>Probably not actually realistic. Most images will appear to be noise and will not be determinable in words. This is why we get such vast numbers: you’d have to specify each pixel one-by-one. Note also that we don’t ask for injectivity (one-to-one). If the function was both injective and surjective it would be invertible (we could invert it to take pictures to text). Lack of injectivity allows more that one prompt to map to a single image.↩︎</p></li>
<li id="fn3"><p>I looked for the technical specs and didn’t immediately find them. Seems like 2048 by 2048 at 2k resolution. This is an approximation; I don’t think it should affect the argument.↩︎</p></li>
<li id="fn4"><p>Classic results by Claude Shannon put an English word somewhere between 10-15 bits. See the bottom of the section <a href="../../../essays/posts/preference_oracles/index.html#information-theory">here</a>.↩︎</p></li>
<li id="fn5"><p>If we <em>did</em> have an injective and invertible Nano-Banana, we could store an image as text and encode/decode it with the model. I would be very interested to see if some variation of Nano-Banana or other Neural Image Compression frameworks can reliably compress images better than a modern codecs, without visual artifacts.↩︎</p></li>
<li id="fn6"><p>See <a href="https://palant.info/2023/01/30/password-strength-explained/">here</a>. ~50 bits of “real” entropy is enough for a reasonable password. We could extend the number of semantic bits by more without affecting the argument.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Essays</category>
  <category>Speculative</category>
  <guid>https://demonstrandom.com/essays/posts/picture_worth_thousand_words/</guid>
  <pubDate>Sun, 23 Nov 2025 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/essays/posts/picture_worth_thousand_words/magritte.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Noether’s Theorem and Geometric Controls</title>
  <link>https://demonstrandom.com/game_theory/posts/noether_geometric_controls/</link>
  <description><![CDATA[ 





<p><a href="https://www.famsf.org/artworks/transformation-of-energy-from-the-science-pictures-portfolio"><img src="https://demonstrandom.com/game_theory/posts/noether_geometric_controls/transformation_of_energy.jpg" class="img-fluid" style="width:55.0%" alt="Berenice Abbott, Transformation of Energy, 1958-1961"></a></p>
<section id="motivation" class="level1">
<h1>Motivation</h1>
<p>What’s the point of geometric integrators? Why bother with the formalism around Lagrangians and Hamiltonians to manage basic control problems or simulations?</p>
<p>In <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html">the last post</a>, I worked on the intuition behind geometric controls. This post continues that effort.</p>
</section>
<section id="setup" class="level1">
<h1>Setup</h1>
<section id="group-actions" class="level2">
<h2 class="anchored" data-anchor-id="group-actions">Group Actions</h2>
<p>We have a configuration manifold <img src="https://latex.codecogs.com/png.latex?Q"> with tangent bundle <img src="https://latex.codecogs.com/png.latex?TQ">.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?G"> be a Lie group. Introduce the smooth map <img src="https://latex.codecogs.com/png.latex?%5CPhi"> such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CPhi:%20G%20%5Ctimes%20Q%20%5Cto%20Q%0A"> <img src="https://latex.codecogs.com/png.latex?%0A(g,%20q)%20%5Cmapsto%20%5CPhi_g(q)%0A"></p>
<p>Choose a smooth curve <img src="https://latex.codecogs.com/png.latex?g(%5Cepsilon)%20%5Cin%20G"> with <img src="https://latex.codecogs.com/png.latex?g(0)=%5Ctext%7Bid%7D_G">. For each <img src="https://latex.codecogs.com/png.latex?q%5Cin%20Q">, the map</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cepsilon%20%5Cmapsto%20g(%5Cepsilon)%5Ccdot%20q%0A"></p>
<p>is a smooth curve in <img src="https://latex.codecogs.com/png.latex?Q">. We can think of <img src="https://latex.codecogs.com/png.latex?g(%5Cepsilon)%5Ccdot%20q"> as a curve through <img src="https://latex.codecogs.com/png.latex?Q">, induced by the action of each <img src="https://latex.codecogs.com/png.latex?g(%5Cepsilon)"> on <img src="https://latex.codecogs.com/png.latex?q">.</p>
<p>The derivative of <img src="https://latex.codecogs.com/png.latex?g(%5Cepsilon)%5Ccdot%20q"> at <img src="https://latex.codecogs.com/png.latex?%5Cepsilon=0"> defines a tangent vector <img src="https://latex.codecogs.com/png.latex?%0A%5Comega_Q(q)%20:=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Cbigl%5Bg(%5Cepsilon)%5Ccdot%20q%5Cbigr%5D%5Cbigg%7C_%7B%5Cepsilon=0%7D%0A%5Cin%20T_qQ%0A"></p>
<p>and hence the map <img src="https://latex.codecogs.com/png.latex?q%20%5Cmapsto%20%5Comega_Q(q)"> defines a smooth vector field on <img src="https://latex.codecogs.com/png.latex?Q">.</p>
<p>We call this map <img src="https://latex.codecogs.com/png.latex?%5Comega_Q(q)"> the “infinitesimal generator” of the curve <img src="https://latex.codecogs.com/png.latex?g(%5Cepsilon)">.</p>
</section>
<section id="g-invariance" class="level2">
<h2 class="anchored" data-anchor-id="g-invariance"><img src="https://latex.codecogs.com/png.latex?G">-invariance</h2>
<p>Construct the map</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AT%5CPhi_g%20:%20T_qQ%20%5Cto%20T_%7B%5CPhi_g(q)%7DQ%0A"></p>
<p>Which takes an element of the tangent bundle at <img src="https://latex.codecogs.com/png.latex?q%20%5Cin%20Q"> and gives the element of the tangent bundle at <img src="https://latex.codecogs.com/png.latex?%5CPhi_g(q)">.</p>
<p>If <img src="https://latex.codecogs.com/png.latex?%5Cforall%20g%20%5Cin%20G">, we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%20T%5CPhi_g(%5Cdot%20q))%20=%20L(q,%20%5Cdot%20q)%0A"></p>
<p>then we say that the Lagrangian is <img src="https://latex.codecogs.com/png.latex?G">-invariant<sup>1</sup>.</p>
</section>
<section id="noethers-theorem" class="level2">
<h2 class="anchored" data-anchor-id="noethers-theorem">Noether’s Theorem</h2>
<p>Suppose the Lagrangian is <img src="https://latex.codecogs.com/png.latex?G">-invariant:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cforall%20g%20%5Cin%20G,%20q%5Cin%20Q">, <img src="https://latex.codecogs.com/png.latex?%0AL(%5CPhi_g(q),%20T%5CPhi_g(%5Cdot%20q))%20=%20L(q,%20%5Cdot%20q)%0A"></p>
<p>Define the <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">–shifted trajectory by <img src="https://latex.codecogs.com/png.latex?%0Aq_%5Cepsilon(t)%20:=%20g(%5Cepsilon)%5Ccdot%20q(t)%0A"></p>
<p>Note that multiplying by <img src="https://latex.codecogs.com/png.latex?g(%7B%5Cepsilon%7D)"> is a smooth operator on <img src="https://latex.codecogs.com/png.latex?q">.</p>
<p>It’s also true that, by construction, <img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Bq_%5Cepsilon(t)%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%20%5Comega_Q(q(t))%0A"></p>
<p>First we will need a quick identity.</p>
<p>Start by differentiating <img src="https://latex.codecogs.com/png.latex?q_%5Cepsilon(t)"> with respect to <img src="https://latex.codecogs.com/png.latex?t">: <img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%20q_%5Cepsilon(t)%0A=%20%5Cfrac%7Bd%7D%7Bdt%7D%5Cleft%5Bg(%5Cepsilon)%5Ccdot%20q(t)%5Cright%5D%0A"></p>
<p>Then differentiate again, <img src="https://latex.codecogs.com/png.latex?%5Cdot%20q_%5Cepsilon(t)"> with respect to <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> at <img src="https://latex.codecogs.com/png.latex?%5Cepsilon=0"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5B%5Cdot%20q_%5Cepsilon(t)%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%20%5Cfrac%7Bd%7D%7Bd%7B%5Cepsilon%7D%7D%5Cbiggr%5B%5Cfrac%7Bd%7D%7Bdt%7D%5Cleft%5Bg(%5Cepsilon)%5Ccdot%20q(t)%5Cright%5D%20%5Cbiggr%5D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A"></p>
<p>Since both <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> and <img src="https://latex.codecogs.com/png.latex?t"> appear only through smooth compositions, the order of differentiation can be interchanged<sup>2</sup>: <img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5B%5Cdot%20q_%5Cepsilon(t)%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Cleft%5B%0A%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Bq_%5Cepsilon(t)%5D%7C_%7B%5Cepsilon=0%7D%0A%5Cright%5D%0A"></p>
<p>Subbing in the definition of <img src="https://latex.codecogs.com/png.latex?%5Comega_Q(q)">, this becomes <span id="eq-omega_Q_identity"><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5B%5Cdot%20q_%5Cepsilon(t)%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%0A%5Cfrac%7Bd%7D%7Bdt%7D%5C,%5B%5Comega_Q(q(t))%5D%0A%5Ctag%7B1%7D"></span></p>
<p>Which is the identity we need.</p>
<p>We are now ready to derive the main result. Differentiate the Lagrangian <img src="https://latex.codecogs.com/png.latex?L(q_%7B%5Cepsilon%7D,%20%5Cdot%20q_%7B%5Cepsilon%7D)"> at <img src="https://latex.codecogs.com/png.latex?%5Cepsilon=0"> and apply the chain rule (with implicit evaluation).</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5B%0AL%5Cbigl(q_%5Cepsilon(t),%5C,%20%5Cdot%20q_%5Cepsilon(t))%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5Ccdot%20%5Cfrac%7Bdq_%7B%5Cepsilon%7D%7D%7Bd%7B%5Cepsilon%7D%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A+%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D%0A%5Ccdot%0A%5Cfrac%7Bd%5Cdot%20q_%7B%5Cepsilon%7D%7D%7Bd%5Cepsilon%7D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A"></p>
<p>The first term on the RHS we sub in <img src="https://latex.codecogs.com/png.latex?%5Comega_Q(q)">, the second term we sub in using Equation&nbsp;1: <img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5B%0AL%5Cbigl(q_%5Cepsilon(t),%5C,%20%5Cdot%20q_%5Cepsilon(t))%5D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5Ccdot%20%5Comega_Q(q)%0A+%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D%0A%5Ccdot%0A%5Cfrac%7Bd%7D%7Bdt%7D%5B%5Comega_Q(q)%5D%0A"></p>
<p>Since the Lagrangian is <img src="https://latex.codecogs.com/png.latex?G">–invariant, and <img src="https://latex.codecogs.com/png.latex?q_%7B%5Cepsilon%7D"> is the application of the smooth operator <img src="https://latex.codecogs.com/png.latex?g(%5Cepsilon)"> to <img src="https://latex.codecogs.com/png.latex?q">, then <img src="https://latex.codecogs.com/png.latex?%5Cforall%20%5Cepsilon"> we can rewrite <img src="https://latex.codecogs.com/png.latex?L(q_%7B%5Cepsilon%7D,%20%5Cdot%20q_%7B%5Cepsilon%7D)"> as <img src="https://latex.codecogs.com/png.latex?L(q,%20%5Cdot%20q)">.</p>
<p>Therefore:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%0AL%5Cbigl%5Bq_%5Cepsilon(t),%5C,%20%5Cdot%20q_%5Cepsilon(t)%5Cbigr%5D%5Cbigg%7C_%7B%5Cepsilon=0%7D%0A=%200%0A"></p>
<p>the derivative on the LHS is zero.</p>
<p>We obtain <img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5Ccdot%20%5Comega_Q(q)%0A+%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D%0A%5Ccdot%0A%5Cfrac%7Bd%7D%7Bdt%7D%5B%5Comega_Q(q)%5D%0A=%200%0A"></p>
<p>Substitute in <img src="https://latex.codecogs.com/png.latex?p%20:=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D"> (previously defined in the <a href="../../../game_theory/posts/discrete_controls_lagrange/index.html#hamiltonian">last post</a>)<sup>3</sup>.</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5Ccdot%20%5Comega_Q(q)%0A%5C;+%5C;%0Ap%20%5Ccdot%20%5Cfrac%7Bd%7D%7Bdt%7D%5Cbigl%5B%5Comega_Q(q)%5Cbigr%5D%0A=%200%0A"></p>
<p>Now, assume the Euler–Lagrange equations hold. Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%20%5Cleft(%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Cright)%0A=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%0A"></p>
<p>Since <img src="https://latex.codecogs.com/png.latex?p%20:=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D"> we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bdp%7D%7Bdt%7D%0A=%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%0A"></p>
<p>So</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bdp%7D%7Bdt%7D%5Ccdot%5Comega_Q(q)%0A%5C;+%5C;%0Ap%5Ccdot%5Cfrac%7Bd%7D%7Bdt%7D%5B%5Comega_Q(q)%5D%0A=%200%0A"></p>
<p>Therefore (using the product rule in reverse)</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Cleft%5Bp%20%5Ccdot%20%5Comega_Q(q)%5Cright%5D%20=%200%0A"></p>
<p>So <img src="https://latex.codecogs.com/png.latex?p%20%5Ccdot%20%5Comega_Q(q)"> is a conserved quantity over time!</p>
<p>We define the “Noether charge”</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7BJ%20:=%20p%20%5Ccdot%20%5Comega_Q(q)%7D%0A"></p>
<p>Noether’s (first) theorem says: given some <img src="https://latex.codecogs.com/png.latex?G"> invariant Lagrangian, and some smooth operator <img src="https://latex.codecogs.com/png.latex?g"> (a symmetry) on <img src="https://latex.codecogs.com/png.latex?q">, <img src="https://latex.codecogs.com/png.latex?J"> is conserved along every solution of the Euler–Lagrange equations.</p>
</section>
<section id="example" class="level2">
<h2 class="anchored" data-anchor-id="example">Example</h2>
<p>Let’s compute the Noether charge for a free rotor<sup>4</sup> under the action <img src="https://latex.codecogs.com/png.latex?g%20:%20q%20%5Cto%20q%20+%20%5Cepsilon">.</p>
<p><img src="https://latex.codecogs.com/png.latex?q"> is just <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> in this case, so the Lagrangian is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(q,%5Cdot%20q)%20=%20%5Cfrac%7B1%7D%7B2%7Dm%5Cell%5E2%5Cdot%20q%20%5E2%0A"></p>
<p>We have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap%20=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D%20=%20m%20%5Cell%5E2%20%5Cdot%20q%0A"></p>
<p>We just need <img src="https://latex.codecogs.com/png.latex?%0Aw_Q(q(t))%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%20%5Bq_%5Cepsilon(t)%5D%5Cbiggr%7C_%7B%5Cepsilon%20=%200%7D%20=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%20%5Bq%20+%20%5Cepsilon%5D%5Cbiggr%7C_%7B%5Cepsilon%20=%200%7D%20=%201%0A"></p>
<p>So we have <img src="https://latex.codecogs.com/png.latex?%0AJ%20=%20m%20%5Cell%5E2%20%5Cdot%20q%20=%20m%5Cell%5E2%5Cdot%20%5Ctheta%0A"></p>
<p>which is the angular momentum.</p>
</section>
<section id="noethers-with-lie-groups" class="level2">
<h2 class="anchored" data-anchor-id="noethers-with-lie-groups">Noether’s with Lie Groups</h2>
<p>The proof for Lie groups is essentially the same (I will omit it).</p>
<p>One note: if <img src="https://latex.codecogs.com/png.latex?Q"> is a Lie group, symmetries are described directly by group multiplication.</p>
<p>That is, fix an element <img src="https://latex.codecogs.com/png.latex?%5Comega"> of the Lie algebra <img src="https://latex.codecogs.com/png.latex?%5Cmathfrak%7Bg%7D"> (the generator), and define a perturbed configuration by acting on <img src="https://latex.codecogs.com/png.latex?q"> with a small group element:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aq_%5Cepsilon%20:=%20%5CPhi%5C!%5Cbigl(%5Cexp(%5Cepsilon%5Comega),%5C,%20q%5Cbigr)%0A"></p>
<p>Usually this is group multiplication</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aq_%5Cepsilon%20:=%20%5CPhi%5C!%5Cbigl(%5Cexp(%5Cepsilon%5Comega),%5C,%20q%5Cbigr)%20=%0A%5Clog(%5Cexp(%5Cepsilon%20%5Comega)%5Cexp(q))%0A"></p>
<p><a href="../../../game_theory/posts/discrete_controls_lagrange/index.html#code">Recall</a> that in our code the <img src="https://latex.codecogs.com/png.latex?%5Clog"> map converts from group representation back to vector.</p>
</section>
</section>
<section id="discrete-noether" class="level1">
<h1>Discrete Noether</h1>
<p>Let’s get the discrete version of Noether’s Theorem.</p>
<p><img src="https://latex.codecogs.com/png.latex?G">-invariance is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL_d%5Cbigl(%5CPhi_g(q_k),%5C,%20%5CPhi_g(q_%7Bk+1%7D)%5Cbigr)%0A=%20L_d(q_k,%20q_%7Bk+1%7D)%0A"></p>
<p>The infinitesimal generator is <img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%5Cfrac%7Bd%20q_%7B%5Cepsilon%7D%5Ek%7D%7Bd%5Cepsilon%7D%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%20%5Comega_Q(q_k)%0A"></p>
<p>The discrete momentum: <img src="https://latex.codecogs.com/png.latex?%0Ap_k%20:=%20D_2%20L_d(q_%7Bk-1%7D,%20q_k),%0A"></p>
<p>And the Noether charge is: <img src="https://latex.codecogs.com/png.latex?%0AJ_k%20:=%20p_k%20%5Ccdot%20%5Comega_Q(q_k)%0A"></p>
<p>How do we compute the infinitesimal generator <img src="https://latex.codecogs.com/png.latex?%5Comega_Q(q_k)">?</p>
<p>Before we had</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Comega_Q(q)%20:=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%5Cbigl%5Bg(%5Cepsilon)%5Ccdot%20q%5Cbigr%5D%5Cbigg%7C_%7B%5Cepsilon=0%7D%0A%5Cin%20T_qQ%0A"></p>
<p>So <img src="https://latex.codecogs.com/png.latex?q_k"> is given by <img src="https://latex.codecogs.com/png.latex?g(%5Cepsilon)%20%5Ccdot%20q_k"> and the derivative with respect to <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> is approximated by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Comega_Q(q_k)%20=%20%5Cfrac%7Bg(%5Cepsilon)%5Ccdot%20q_k%20-%20q_k%7D%7B%5Cepsilon%7D%0A"></p>
<p>We can compute <img src="https://latex.codecogs.com/png.latex?g(%5Cepsilon)"> in Lie Algebra coordinates</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Comega_Q(q_k)%20=%20%5Cfrac%7B%5Clog%20(%5Cexp(%5Cepsilon%20%5Comega)%5Cexp(q_k))%20-%20q_k%7D%7B%5Cepsilon%7D%0A"></p>
<p>Note that in <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D"> this reduces to <img src="https://latex.codecogs.com/png.latex?%0A%5Comega_Q(q_k)%20=%20%5Cfrac%7B%5Clog%20(%5Cexp(%5Cepsilon%20%5Comega)%5Cexp(q_k))%20-%20q_k%7D%7B%5Cepsilon%7D%20=%20%5Cfrac%7Bq_k%20+%20%5Cepsilon%20%5Comega%20-%20q_k%7D%7B%5Cepsilon%7D%20=%20%5Comega%0A"></p>
</section>
<section id="code" class="level1">
<h1>Code</h1>
<p>Let’s start to code. Here’s the <code>FreeRotor</code>:</p>
<div id="6e7a1007" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> FreeRotor(VariationalSystem):</span>
<span id="cb1-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> control_plane(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb1-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb1-4">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>: Rn(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb1-5">        }</span>
<span id="cb1-6">    </span>
<span id="cb1-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> params(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb1-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"length"</span>]</span>
<span id="cb1-9"></span>
<span id="cb1-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lagrangian(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ctrl, dctrl):</span>
<span id="cb1-11">        th  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ctrl.theta</span>
<span id="cb1-12">        thd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dctrl.theta</span>
<span id="cb1-13"></span>
<span id="cb1-14">        m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.mass</span>
<span id="cb1-15">        l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.length</span>
<span id="cb1-16"></span>
<span id="cb1-17">        T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> l<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> thd<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>thd</span>
<span id="cb1-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> T </span></code></pre></div>
</div>
<p>I swapped <code>theta</code> to <code>Rn(1)</code> due to continued problems with <code>SOn</code>. Will come back to it later<sup>5</sup>.</p>
<p>Let’s add a new method to <code>VariationalIntegrator</code></p>
<div id="1a2e4c62" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> VariationalIntegrator: </span>
<span id="cb2-2">    ...</span>
<span id="cb2-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> register_noether_charge(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, name:<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, symmetry: Symmetry):</span>
<span id="cb2-4">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _new_charge(qk, qk1):</span>
<span id="cb2-5">            p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D2_Ld(qk, qk1)</span>
<span id="cb2-6"></span>
<span id="cb2-7">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># qk_eps = symmetry.log(symmetry.exp(self.tol * params) * symmetry.exp(qk))</span></span>
<span id="cb2-8">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># omega_qk = (qk_eps - qk)/self.tol</span></span>
<span id="cb2-9"></span>
<span id="cb2-10">            qk1_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> symmetry.log(symmetry.exp(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> symmetry.generator) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> symmetry.exp(qk1))</span>
<span id="cb2-11">            omega_qk1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (qk1_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> qk1)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol</span>
<span id="cb2-12"></span>
<span id="cb2-13">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>omega_qk1).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb2-14">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.noether_charges[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _new_charge </span>
<span id="cb2-15"></span>
<span id="cb2-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> step(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, q_prev: torch.Tensor, q_curr: torch.Tensor):</span>
<span id="cb2-17"></span>
<span id="cb2-18">        q_next <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> q_prev)</span>
<span id="cb2-19">        q_next <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_next.clone().detach().requires_grad_(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb2-20">        const_term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D2_Ld(q_prev, q_curr).detach()</span>
<span id="cb2-21"></span>
<span id="cb2-22">        success <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb2-23"></span>
<span id="cb2-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.max_iters):</span>
<span id="cb2-25">            F <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> const_term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D1_Ld(q_curr, q_next)</span>
<span id="cb2-26"></span>
<span id="cb2-27">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> F.norm().item() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol:</span>
<span id="cb2-28">                success <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb2-29">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb2-30"></span>
<span id="cb2-31">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> F_of(x: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb2-32">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> const_term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D1_Ld(q_curr, x)</span>
<span id="cb2-33"></span>
<span id="cb2-34">            J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.autograd.functional.jacobian(F_of, q_next)</span>
<span id="cb2-35">            delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.linalg.solve(J, F)</span>
<span id="cb2-36"></span>
<span id="cb2-37">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> torch.no_grad():</span>
<span id="cb2-38">                q_next <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-=</span> delta</span>
<span id="cb2-39">            q_next.requires_grad_(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb2-40"></span>
<span id="cb2-41">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> delta.norm().item() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol:</span>
<span id="cb2-42">                success <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb2-43">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb2-44">        q_prev_det <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_prev.detach()</span>
<span id="cb2-45">        q_curr_det <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr.detach()</span>
<span id="cb2-46">        q_next_det <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_next.detach()</span>
<span id="cb2-47"></span>
<span id="cb2-48">        noether_charge_outputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb2-49">            name: fn(q_curr_det, q_next_det)</span>
<span id="cb2-50">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, fn <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.noether_charges.items()</span>
<span id="cb2-51">        }</span>
<span id="cb2-52"></span>
<span id="cb2-53">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.on_step <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb2-54">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.on_step({</span>
<span id="cb2-55">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q_prev"</span>: q_prev_det,</span>
<span id="cb2-56">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q_curr"</span>: q_curr_det,</span>
<span id="cb2-57">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q_next"</span>: q_next_det,</span>
<span id="cb2-58">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>: noether_charge_outputs,</span>
<span id="cb2-59">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"success"</span>: success</span>
<span id="cb2-60">            })</span>
<span id="cb2-61"></span>
<span id="cb2-62">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> q_next_det, success</span></code></pre></div>
</div>
<p>This takes a <code>Symmetry</code>, computes the Noether charge. <code>step</code> is also modified. How does a user provide the Symmetry?</p>
<p>Let’s do</p>
<div id="3fd27918" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<span id="cb3-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Symmetry:</span>
<span id="cb3-3"></span>
<span id="cb3-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, group: LieGroup, generator: torch.Tensor):</span>
<span id="cb3-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> group</span>
<span id="cb3-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.generator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generator</span>
<span id="cb3-7"></span>
<span id="cb3-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> exp(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, v: torch.Tensor):</span>
<span id="cb3-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.exp(v)</span>
<span id="cb3-10"></span>
<span id="cb3-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> log(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g):</span>
<span id="cb3-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.log(g)</span>
<span id="cb3-13"></span>
<span id="cb3-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> shift(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, q: torch.Tensor, eps: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb3-15">        g_q   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.exp(q)</span>
<span id="cb3-16">        g_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.exp(eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.generator.to(q.device))</span>
<span id="cb3-17">        g_new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> g_q</span>
<span id="cb3-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.log(g_new)</span></code></pre></div>
</div>
<p>We take a Lie group and define a “shift” operation that must give a symmetry.</p>
<p>We must also modify our callback function junk:</p>
<div id="2cead866" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> StepRecorder:</span>
<span id="cb4-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb4-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.records <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb4-4"></span>
<span id="cb4-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> on_step(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, record):</span>
<span id="cb4-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.records.append(record)</span></code></pre></div>
</div>
<p>Let’s try with the free rotor:</p>
<div id="df4e389b" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb5-2"></span>
<span id="cb5-3">    freeRotor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FreeRotor({</span>
<span id="cb5-4">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>,</span>
<span id="cb5-5">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"length"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb5-6">    })</span>
<span id="cb5-7">    </span>
<span id="cb5-8">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.001</span></span>
<span id="cb5-9">    recorder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StepRecorder()</span>
<span id="cb5-10">    integrator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> VariationalIntegrator(freeRotor, step_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>h, on_step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>recorder.on_step)</span>
<span id="cb5-11"></span>
<span id="cb5-12">    theta_group, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> freeRotor.model.layout[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>]</span>
<span id="cb5-13">    omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64)</span>
<span id="cb5-14">    rotor_symmetry <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Symmetry(group<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>theta_group, generator<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>omega)</span>
<span id="cb5-15">    integrator.register_noether_charge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"angular_momentum"</span>, rotor_symmetry)</span>
<span id="cb5-16"></span>
<span id="cb5-17">    theta0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span></span>
<span id="cb5-18">    theta_dot0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-19">    ctrl0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>: torch.tensor([theta0])})</span>
<span id="cb5-20">    q0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> freeRotor.model.pack(ctrl0)</span>
<span id="cb5-21"></span>
<span id="cb5-22">    ctrl1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>: torch.tensor([theta0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> theta_dot0])})</span>
<span id="cb5-23">    q1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> freeRotor.model.pack(ctrl1)</span>
<span id="cb5-24"></span>
<span id="cb5-25">    steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10000</span></span>
<span id="cb5-26">    qs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [q0.clone(), q1.clone()]</span>
<span id="cb5-27">    q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q0, q1</span>
<span id="cb5-28"></span>
<span id="cb5-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tqdm.tqdm(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)):</span>
<span id="cb5-30">        q_next, ok <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> integrator.step(q_prev, q_curr)</span>
<span id="cb5-31">        qs.append(q_next.clone())</span>
<span id="cb5-32">        q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr, q_next</span>
<span id="cb5-33"></span>
<span id="cb5-34">    qs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.stack(qs, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb5-35">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>([record[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"noether_charges"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> record <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> recorder.records])</span></code></pre></div>
</div>
<p>And we see the angular momentum is conserve at 1.0000:</p>
<pre><code>[{'angular_momentum': tensor(1.0000)}, {'angular_momentum': tensor(1.0000)},...]</code></pre>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>We’ve successfully implemented a Noether’s theorem implementation in our geometric controls library. Next time, I will presumably add in time dependence and look at jet controls, but it is possible we will instead look more deeply at invariants.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>We have essentially reduced the Lagrangian to a single variable <img src="https://latex.codecogs.com/png.latex?q">, and then applied <img src="https://latex.codecogs.com/png.latex?%5CPhi_g"> to <img src="https://latex.codecogs.com/png.latex?q"> by “pushing forward” <img src="https://latex.codecogs.com/png.latex?%5CPhi_g"> through the tangent mapping.↩︎</p></li>
<li id="fn2"><p>Clairaut’s theorem.↩︎</p></li>
<li id="fn3"><p>I now realize this wasn’t explicit in the last post, but our fiber derivative <img src="https://latex.codecogs.com/png.latex?p%20=%20d_vL_q">, restricted to a particular path (instead of arbitrary <img src="https://latex.codecogs.com/png.latex?v">) reduces to <img src="https://latex.codecogs.com/png.latex?p%20=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D">. Potentially there is some technical subtlety here I am unclear on. If I look in Marsden &amp; Ratiu they seem to define <img src="https://latex.codecogs.com/png.latex?p"> as <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%20q%7D">.↩︎</p></li>
<li id="fn4"><p>I’d do the pendulum but thanks to gravity it’s not really symmetric. The free rotor removes gravity. The versions of the Lagrangian/Hamiltonian formulation and Noether’s theorem I’ve derived already don’t depend on absolute time (so I can’t derive energy). If we add time back in, time-invariance leads to energy conservation (the Hamiltonian is constant across time). Currently we are doing something more similar to Pontryagin control.↩︎</p></li>
<li id="fn5"><p>ChatGPT seems to think this is due to some subtle coordinate representation issue. I am somehow implicitly assuming the group is Abelian which is why it breaks for SOn. There might be some Newton solver issues as well (the code is not fully adapted for Lie groups). Ignoring for now.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Geometric Controls</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/game_theory/posts/noether_geometric_controls/</guid>
  <pubDate>Fri, 21 Nov 2025 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/game_theory/posts/noether_geometric_controls/transformation_of_energy.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Controls from the Geometric Perspective</title>
  <link>https://demonstrandom.com/game_theory/posts/discrete_controls_lagrange/</link>
  <description><![CDATA[ 





<p><a href="https://www.arthistoryproject.com/artists/liubov-popova/space-force-construction/"><img src="https://demonstrandom.com/game_theory/posts/discrete_controls_lagrange/liubov_popova-space_force_construction-1921-trivium-art-history.jpg" class="img-fluid" style="width:55.0%" alt="Liubov Popova, Space Force, 1921"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>In my post on <a href="../../../game_theory/posts/differential_stag_hunt/index.html">differential games and stag hunt</a> I mentioned discrete controls. When looking into this topic, I ended up in a rabbit hole around geometric controls. To learn a bit more, I took a look at the paper <a href="https://arxiv.org/abs/0705.3868">Discrete Control Systems</a>, by Taeyoung Lee, Melvin Leok, N. Harris McClamroch, but many parts of the exposition diverged from the paper as I proceeded in my investigation.</p>
<p>In particular, I wanted the control problem to motivate the Lagrangian, rather than deriving the Lagrangian in terms of the desired physics.</p>
<p>AI Disclosure: I used ChatGPT to generate a bunch of the LaTeX in this post. Mistakes my own.</p>
</section>
<section id="background" class="level1">
<h1>Background</h1>
<p>Consider some system with configuration space <img src="https://latex.codecogs.com/png.latex?Q">.</p>
<p><img src="https://latex.codecogs.com/png.latex?Q"> is a set, and each element <img src="https://latex.codecogs.com/png.latex?q"> of <img src="https://latex.codecogs.com/png.latex?Q"> is one particular configuration. For mechanical systems, <img src="https://latex.codecogs.com/png.latex?Q"> would be all possible positions of the system and <img src="https://latex.codecogs.com/png.latex?q"> would be a particular position. <img src="https://latex.codecogs.com/png.latex?Q"> might be a particle location in 3D (<img src="https://latex.codecogs.com/png.latex?Q%20=%20%5Cmathbb%7BR%7D%5E3">), the orientation of a rigid body (<img src="https://latex.codecogs.com/png.latex?Q%20=%20SO(3)">), or something else.</p>
<p>Suppose the system starts in configuration <img src="https://latex.codecogs.com/png.latex?q_0"> and we want it to be in <img src="https://latex.codecogs.com/png.latex?q_N">. Which path should we take to transform the configuration? We can frame this problem using the Lagrangian. However, first we need to assume a bit more about <img src="https://latex.codecogs.com/png.latex?Q">, namely that it is smooth manifold equipped with a Riemannian metric and a tangent bundle<sup>1</sup>.</p>
<section id="manifold" class="level2">
<h2 class="anchored" data-anchor-id="manifold">Manifold</h2>
<p><img src="https://latex.codecogs.com/png.latex?Q"> is a smooth manifold of dimension <img src="https://latex.codecogs.com/png.latex?n"> if around every point <img src="https://latex.codecogs.com/png.latex?q%20%5Cin%20Q"> we can find a neighborhood <img src="https://latex.codecogs.com/png.latex?U"> and a map <img src="https://latex.codecogs.com/png.latex?%5Cphi"> such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cphi%20:%20U%20%5Cto%20%5Cmathbb%7BR%7D%5En%0A"></p>
<p>that is bijective, infinitely-differentiable, and has a smooth inverse <img src="https://latex.codecogs.com/png.latex?%5Cphi%5E%7B-1%7D:%20%5Cmathbb%7BR%7D%5En%20%5Cto%20U">. We call the map <img src="https://latex.codecogs.com/png.latex?%5Cphi"> a chart, or coordinate system.</p>
<p>Furthermore, we require overlapping charts to agree smoothly. In practical terms, this means we have two differentiable functions:</p>
<div id="8494f031" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> to_local_coords(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, q, center):</span>
<span id="cb1-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span>
<span id="cb1-3">    </span>
<span id="cb1-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> from_local_coords(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, center):</span>
<span id="cb1-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span></code></pre></div>
</div>
<p>such that <code>from_local_coords(to_local_coords(q, center), center) = q</code> (at least approximately) and vice-versa.</p>
<p>For a Euclidean space (<img src="https://latex.codecogs.com/png.latex?Q%20=%20%5Cmathbb%7BR%7D%5E3">), <img src="https://latex.codecogs.com/png.latex?%5Cphi"> is simply the identity.</p>
</section>
<section id="riemannian-metric" class="level2">
<h2 class="anchored" data-anchor-id="riemannian-metric">Riemannian Metric</h2>
<p>To get a Riemannian manifold, there is a second requirement. At each point <img src="https://latex.codecogs.com/png.latex?q%20%5Cin%20Q">, we construct the tangent space <img src="https://latex.codecogs.com/png.latex?T_qQ"> of all possible velocity vectors at <img src="https://latex.codecogs.com/png.latex?q"> (keep in mind we said <img src="https://latex.codecogs.com/png.latex?Q"> was infinitely differentiable). We also have an inner product <img src="https://latex.codecogs.com/png.latex?g_q"> at each point <img src="https://latex.codecogs.com/png.latex?q"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag_q%20:%20T_qQ%20%5Ctimes%20T_qQ%20%5Cto%20%5Cmathbb%7BR%7D%0A"></p>
<p>To be a Riemannian metric<sup>2</sup>, <img src="https://latex.codecogs.com/png.latex?g_q"> needs to meet the following four conditions:</p>
<ol type="1">
<li><img src="https://latex.codecogs.com/png.latex?g_q(v_1,%20v_2)%20=%20g_q(v_2,%20v_1)"> (Symmetry)</li>
<li><img src="https://latex.codecogs.com/png.latex?g_q(av_1%20+%20bv_2,%20w)%20=%20ag_q(v_1,w)%20+%20bg_q(v_2,%20w)"> and <img src="https://latex.codecogs.com/png.latex?g_q(v,%20aw_1%20+%20bw_2)%20=%20ag_q(v,w_1)%20+%20bg_q(v,%20w_2)"> (Bilinearity)</li>
<li><img src="https://latex.codecogs.com/png.latex?g_q(v,v)%20%3E%200"> for all <img src="https://latex.codecogs.com/png.latex?v%20%5Cneq%200"> (Positive Definite)</li>
<li>For every chart <img src="https://latex.codecogs.com/png.latex?%5Cvarphi:%20U%20%5Cto%20%5Cmathbb%7BR%7D%5En">, the metric components <img src="https://latex.codecogs.com/png.latex?g_%7Bij%7D(x)%20=%20g(%5Cpartial_i,%20%5Cpartial_j)"> in coordinates are <img src="https://latex.codecogs.com/png.latex?C%5E%5Cinfty"> (Smooth)</li>
</ol>
<p>Basically, to implement our (now Riemannian) manifold, we will need something like:</p>
<div id="2f440c98" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Manifold:</span>
<span id="cb2-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Base class for smooth manifolds"""</span></span>
<span id="cb2-3">    </span>
<span id="cb2-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> metric(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, q, v1, v2):</span>
<span id="cb2-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span>
<span id="cb2-6">    </span>
<span id="cb2-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> inner_product(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, q, v1, v2):</span>
<span id="cb2-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.metric(q, v1, v2)</span></code></pre></div>
</div>
</section>
<section id="tangent-bundle" class="level2">
<h2 class="anchored" data-anchor-id="tangent-bundle">Tangent Bundle</h2>
<p>The tangent bundle <img src="https://latex.codecogs.com/png.latex?TQ"> is defined as the collection of all position and velocity pairs:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0ATQ%20:=%20%5C%7B(q,%20v)%20:%20q%20%5Cin%20Q,%20v%20%5Cin%20T_qQ%20%5C%7D%0A"></p>
</section>
<section id="lagrangian" class="level2">
<h2 class="anchored" data-anchor-id="lagrangian">Lagrangian</h2>
<p>Now that we have established the structure, we can define the Lagrangian.</p>
<p>The Lagrangian is a (smooth) function that assigns a number to each element of the tangent bundle:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL%20:%20TQ%20%5Cto%20%5Cmathbb%7BR%7D%0A"></p>
<p>The Lagrangian encodes a “cost” for each way of moving through the configuration space. Given any trajectory <img src="https://latex.codecogs.com/png.latex?q(t)"> from <img src="https://latex.codecogs.com/png.latex?q_0"> to <img src="https://latex.codecogs.com/png.latex?q_N">, we can evaluate its goodness by computing the action:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS%5Bq%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L(q(t),%20%5Cdot%7Bq%7D(t))%20%5C,%20dt%0A"></p>
<p>The action <img src="https://latex.codecogs.com/png.latex?S"> assigns a single number to each possible path. Hamilton’s Principle states that the physically realized path the one where nearby paths have approximately the same action (that path is a critical point in the space of paths).</p>
</section>
<section id="euler-lagrange-equations" class="level2">
<h2 class="anchored" data-anchor-id="euler-lagrange-equations">Euler-Lagrange Equations</h2>
<p>Consider a trajectory <img src="https://latex.codecogs.com/png.latex?q(t)"> and a small variation in that trajectory <img src="https://latex.codecogs.com/png.latex?%5Ceta(t)"> with fixed endpoints. We know that <img src="https://latex.codecogs.com/png.latex?%5Ceta(t_0)%20=%20%5Ceta(t_N)%20=%200">, because the system must still start and end at the desired configurations (we have just varied the path). Thus the varied path is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aq(t)%20+%20%5Cepsilon%5Ceta(t)%0A"></p>
<p>for small <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">. The action along the varied path is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AS(%5Cepsilon)%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20L(q%20+%20%5Cepsilon%20%5Ceta,%20%5Cdot%7Bq%7D%20+%20%5Cepsilon%20%5Cdot%7B%5Ceta%7D)%20dt%0A"></p>
<p>We want our trajectory to be a critical point in the space of paths. So</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20S%7D%7B%5Cpartial%5Cepsilon%7D%5Cbiggr%5Cvert_%7B%5Cepsilon%20=%200%7D%20=%200%0A"></p>
<p>by differentiating under the integrand and using the chain rule, we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20S%7D%7B%5Cpartial%5Cepsilon%7D%5Cbiggr%5Cvert_%7B%5Cepsilon%20=%200%7D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20%5Cbig%5B%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5Ceta%20+%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%5Cdot%7B%5Ceta%7D%20%20%20%5Cbig%5D%20dt%0A"></p>
<p>We can break this up and integrate the second term by parts:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Cdot%7B%5Ceta%7D%5C,%20dt%0A=%20%5Cleft.%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Ceta%20%5Cright%7C_%7Bt_0%7D%5E%7Bt_N%7D%0A-%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%20%5Cfrac%7Bd%7D%7Bdt%7D%20%5Cleft(%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Cright)%20%5Ceta%5C,%20dt%0A"></p>
<p>since <img src="https://latex.codecogs.com/png.latex?%5Ceta(t_0)%20=%20%5Ceta(t_N)%20=%200">, the first term is zero. Substituting back we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft.%20%5Cfrac%7BdS%7D%7Bd%5Cepsilon%7D%20%5Cright%7C_%7B%5Cepsilon=0%7D%0A=%20%5Cint_%7Bt_0%7D%5E%7Bt_N%7D%0A%5Cleft%5B%0A%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%0A-%20%5Cfrac%7Bd%7D%7Bdt%7D%5Cleft(%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Cright)%0A%5Cright%5D%20%5Ceta%5C,%20dt%20=%200%0A"></p>
<p>Since this is true for any <img src="https://latex.codecogs.com/png.latex?%5Ceta(t)">, the integrand is zero everywhere:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7B%0A%5Cfrac%7Bd%7D%7Bdt%7D%20%5Cleft(%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Cright)%0A-%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%20=%200%0A%7D%0A"></p>
<p>The boxed term is the Euler-Lagrange equations.</p>
</section>
<section id="hamiltonian" class="level2">
<h2 class="anchored" data-anchor-id="hamiltonian">Hamiltonian</h2>
<p>The action functional (when we integrate over the Lagrangian) is a global principle. It cares about the entire path we have looked at. The Lagrangian itself is the infinitesimal contribution to this global quantity: it evaluates how “expensive” it is to move through a infinitesimal segment of the path.</p>
<p>How does that translate into an actual policy? That is, given the current state, which infinitesimal step should we take next?</p>
<p>The Lagrangian is a function</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL%20:%20TQ%20%5Cto%20%5Cmathbb%7BR%7D%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?L"> restricts locally to:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL_q:%20T_qQ%20%5Cto%20%5Cmathbb%7BR%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0Av%20%5Cmapsto%20L(q,%20v)%0A"></p>
<p>This is a “marginal cost” of moving from <img src="https://latex.codecogs.com/png.latex?q"> with velocity <img src="https://latex.codecogs.com/png.latex?v">.</p>
<p>Given a <img src="https://latex.codecogs.com/png.latex?q">, how does <img src="https://latex.codecogs.com/png.latex?L(q,v)"> change if we alter <img src="https://latex.codecogs.com/png.latex?v"> within the tangent space <img src="https://latex.codecogs.com/png.latex?T_qQ">? That is, if what is the effect of a small change in speed on the marginal cost?</p>
<p>We perturb <img src="https://latex.codecogs.com/png.latex?v"> in the direction <img src="https://latex.codecogs.com/png.latex?w%20%5Cin%20T_qQ">, the derivative in that direction at a given <img src="https://latex.codecogs.com/png.latex?q"> and <img src="https://latex.codecogs.com/png.latex?w"> is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(w)%20:=%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%20%5BL(q,%20v%20+%20%5Cepsilon%20w)%5D%5Cbiggr%7C_%7B%5Cepsilon=0%7D%0A"></p>
<p>We call this the “fiber derivative”. We may also denote this as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap%20:=%20d_vL_q%0A"></p>
<p>Each <img src="https://latex.codecogs.com/png.latex?p"> tells you how sensitive the cost is to change in <img src="https://latex.codecogs.com/png.latex?v">.</p>
<p>Consider the canonical evaluation map</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bev%7D_q:%20T%5E*_qQ%20%5Ctimes%20T_qQ%20%5Cto%20%5Cmathbb%7BR%7D%0A"> such that <img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bev%7D_q(p,%20v)%20%5Cmapsto%20p(v)%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?p(v)"> is the <em>first-order predicted cost change</em> for a given <img src="https://latex.codecogs.com/png.latex?v">, which has the same units as <img src="https://latex.codecogs.com/png.latex?L(q,%20v)">. So if we fix <img src="https://latex.codecogs.com/png.latex?q">, we can say that for any <img src="https://latex.codecogs.com/png.latex?v"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap(v)%20-%20L(q,v)%0A"></p>
<p>is the instantaneous “error in cost” (best affine approximation) between the linearization of <img src="https://latex.codecogs.com/png.latex?L(q,v)"> in local coordinates (<img src="https://latex.codecogs.com/png.latex?p(v)">) and the “actual” instantaneous cost <img src="https://latex.codecogs.com/png.latex?L(q,v)">.</p>
<p>The most consistent local description of <img src="https://latex.codecogs.com/png.latex?L"> given <img src="https://latex.codecogs.com/png.latex?p"> is the maximum possible value of this difference over all directions <img src="https://latex.codecogs.com/png.latex?v">. In other words, given my sensitivity <img src="https://latex.codecogs.com/png.latex?p"> to <img src="https://latex.codecogs.com/png.latex?v">, and the cost <img src="https://latex.codecogs.com/png.latex?L(q,v)"> of moving at <img src="https://latex.codecogs.com/png.latex?v">, which <img src="https://latex.codecogs.com/png.latex?v"> should I move at to obtain to best approximate the true global optimum? In equations, define:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(q,p)%20:=%20%5Csup_%7Bv%5Cin%20T_qQ%7D%20(p(v)%20-%20L(q,v))%0A"></p>
<p>This is the “fiberwise Legendre transform”. The resulting function <img src="https://latex.codecogs.com/png.latex?H:%20T%5E*Q%20%5Cto%20%5Cmathbb%7BR%7D"> is called the “Hamiltonian”.</p>
</section>
<section id="hamiltons-equations" class="level2">
<h2 class="anchored" data-anchor-id="hamiltons-equations">Hamilton’s Equations</h2>
<p>To move between the “velocity picture” <img src="https://latex.codecogs.com/png.latex?(q,v)"> and the “covector picture” <img src="https://latex.codecogs.com/png.latex?(q,p)">, we need to ensure that every velocity has a unique covector representing its local rate of change of cost, and vice versa.</p>
<p>We already have the “fiber derivative” from before: <img src="https://latex.codecogs.com/png.latex?%0Ap%20:=%20d_vL_q%20%5Cin%20T_q%5E*Q.%0A"></p>
<p>This defines the Legendre map <img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbb%7BF%7DL:%20TQ%20%5Cto%20T%5E*Q,%5Cqquad%20(q,v)%5Cmapsto%20(q,p=d_vL_q)%0A"></p>
<p>For the Hamiltonian to exist as a function (rather than a possibly multivalue-relation<sup>3</sup>), <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BF%7DL"> must be locally invertible. Lucky for us, the Riemannian metric <img src="https://latex.codecogs.com/png.latex?g_q:%20T_qQ%5Ctimes%20T_qQ%20%5Cto%20%5Cmathbb%7BR%7D"> gives us a canonical way to manage this.</p>
<p>Under the metric isomorphism <img src="https://latex.codecogs.com/png.latex?g_q:%20T_qQ%20%5Cto%20T_q%5E*Q"><sup>4</sup>, we identify <img src="https://latex.codecogs.com/png.latex?%0Ap%20=%20g_q(v,%5Ccdot)%0A"></p>
<p>Because <img src="https://latex.codecogs.com/png.latex?g_q"> is positive definite and nondegenerate, this map is automatically invertible.</p>
<p>If we assume a unique supremum at <img src="https://latex.codecogs.com/png.latex?v(q,p)">, and take our identity:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AH(q,p)%20=%20p(v(q,p))%20-%20L(q,v(q,p))%0A"></p>
<p>and differentiate <img src="https://latex.codecogs.com/png.latex?H">, it gives <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0AdH%0A%20%20&amp;=%20d%5Cbig(p(v(q,p))%5Cbig)%20-%20d%5Cbig(L(q,v(q,p))%5Cbig)%20%5C%5C%5B6pt%5D%0A%20%20&amp;=%20%5Cbig(v%5C,dp%20+%20p(dv)%5Cbig)%0A%20%20%20%20%20-%20%5CBig(%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5C,dq%0A%20%20%20%20%20%20%20%20%20%20%20%20+%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20v%7D%5C,dv%20%5CBig)%20%5C%5C%5B6pt%5D%0A%20%20&amp;=%20v%5C,dp%20-%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5C,dq%0A%20%20%20%20%20+%20%5Cbig(p%20-%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20v%7D%5Cbig)%5C,dv%20%5C%5C%5B6pt%5D%0A%20%20&amp;=%20v%5C,dp%20-%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%5C,dq.%0A%5Cend%7Baligned%7D%0A"></p>
<p>hence, matching on the multivariable chain rule: <img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7B%5Cpartial%20H%7D%7B%5Cpartial%20p%7D(q,p)%20=%20v,%0A%5Cqquad%0A%5Cfrac%7B%5Cpartial%20H%7D%7B%5Cpartial%20q%7D(q,p)%20=%20-%5C,%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D(q,v)%0A"></p>
<p>Thus the evolution of <img src="https://latex.codecogs.com/png.latex?(q,p)"> is governed by the first-order flow <img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7B%0A%5Cdot%7Bq%7D%20=%20%5Cfrac%7B%5Cpartial%20H%7D%7B%5Cpartial%20p%7D(q,p),%0A%5Cqqua%0A%5Cdot%7Bp%7D%20=%20-%5C,%5Cfrac%7B%5Cpartial%20H%7D%7B%5Cpartial%20q%7D(q,p)%0A%7D%0A"></p>
<p>These are Hamilton’s equations.</p>
</section>
</section>
<section id="mechanics-on-lie-groups" class="level1">
<h1>Mechanics on Lie Groups</h1>
<p>We should also discuss Lie Groups and Lie Algebras. These become necessary when we are controlling a robot through states more complex than paths (for example, you may want to control the robots rotational orientation in space).</p>
<section id="lie-groups" class="level2">
<h2 class="anchored" data-anchor-id="lie-groups">Lie Groups</h2>
<p>A Lie group is a group that is also a real smooth manifold. To define one, we need a two group operations (multiplication and inversion) that are both smooth maps. That is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmu%20:%20G%20%5Ctimes%20G%20%5Cto%20G%0A"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cmu(x,y)%20=%20xy%0A"></p>
<p>is smooth.</p>
<p>As an standard example, consider</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7BSO%7D(2,%20%5Cmathbb%7BR%7D)%0A=%20%5Cleft%5C%7B%0A%5Cbegin%7Bpmatrix%7D%0A%5Ccos%5Cvarphi%20&amp;%20-%5Csin%5Cvarphi%20%5C%5C%0A%5Csin%5Cvarphi%20&amp;%20%5Ccos%5Cvarphi%0A%5Cend%7Bpmatrix%7D%0A:%20%5Cvarphi%20%5Cin%20%5Cmathbb%7BR%7D%20/%202%5Cpi%5Cmathbb%7BZ%7D%0A%5Cright%5C%7D.%0A"></p>
<p>The group operation is matrix multiplication, the identity is the identity matrix, and given an element <img src="https://latex.codecogs.com/png.latex?R"> of the Lie Group, the inverse is <img src="https://latex.codecogs.com/png.latex?R(-%5Cvarphi)"> <img src="https://latex.codecogs.com/png.latex?%0A%5Cquad%0AR(%5Cvarphi)%5E%7B-1%7D%20=%20R(-%5Cvarphi)%20=%0A%5Cbegin%7Bpmatrix%7D%0A%5Ccos%5Cvarphi%20&amp;%20%5Csin%5Cvarphi%20%5C%5C%0A-%5Csin%5Cvarphi%20&amp;%20%5Ccos%5Cvarphi%0A%5Cend%7Bpmatrix%7D.%0A"></p>
<p>Let’s say we want to track or control a rotating rigid body. We could use the Euler-Lagrange equations</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%20%5Cleft(%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D%20%5Cright)%0A-%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20q%7D%20=%200%0A"></p>
<p>but what is <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Cdot%7Bq%7D%7D"> in this case? <img src="https://latex.codecogs.com/png.latex?R"> is a rotation matrix satisfying <img src="https://latex.codecogs.com/png.latex?R%5ETR%20=%20I"> and <img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bdet%7D(R)%20=%201">. The derivative <img src="https://latex.codecogs.com/png.latex?%5Cdot%7BR%7D"> must preserve this structure, and we can’t just add or subtract rotation matrices.</p>
<p>For any time-dependent configuration <img src="https://latex.codecogs.com/png.latex?g(t)"> in a Lie group, we always have <img src="https://latex.codecogs.com/png.latex?%0Ag(t)%5E%7B-1%7D%20g(t)%20=%20%5Cmathrm%7Bid%7D_G.%0A"></p>
<p>Differentiating,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Bg%5E%7B-1%7D(t)%5D%5C,%20g(t)%0A%5C;+%5C;%0Ag%5E%7B-1%7D(t)%5C,%20%5Cdot%20g(t)%0A%5C;=%5C;%200.%0A"></p>
<p>Rearranging,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag%5E%7B-1%7D(t)%5C,%5Cdot%20g(t)%20=%20-%5C,%5Cfrac%7Bd%7D%7Bdt%7D%5Bg%5E%7B-1%7D(t)%5D%5C,%20g(t).%0A"></p>
<p>We define</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Comega(t)%20:=%20g%5E%7B-1%7D(t)%5C,%5Cdot%20g(t),%0A"></p>
<p>so that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%20g(t)%20=%20g(t)%5C,%5Comega(t).%0A"></p>
<p>So <img src="https://latex.codecogs.com/png.latex?%5Comega(t)"> is the unique object such that multiplying it by <img src="https://latex.codecogs.com/png.latex?g(t)"> reconstructs the time-derivative of the motion. Therefore, <img src="https://latex.codecogs.com/png.latex?g%5E%7B-1%7D%5Cdot%7Bg%7D%20=%20%5Comega">.</p>
<p>Consider now the path: <img src="https://latex.codecogs.com/png.latex?%5Cgamma(s)%20:=%20g%5E%7B-1%7D(t)g(t+s)">:</p>
<p>At <img src="https://latex.codecogs.com/png.latex?s%20=%200">, we have <img src="https://latex.codecogs.com/png.latex?%5Cgamma(0)%20=%20%5Ctext%7Bid%7D_G">. For any other <img src="https://latex.codecogs.com/png.latex?s">, we have that <img src="https://latex.codecogs.com/png.latex?%5B%5Cdot%7B%5Cgamma%7D(s)%5D%5Cbiggr%7C_%7Bs=0%7D%20=%20g%5E%7B-1%7D%5Cdot%7Bg%7D%20=%20%5Comega">. So any <img src="https://latex.codecogs.com/png.latex?%5Comega"> is a derivative of a path at the identity.</p>
<p>Hence, <img src="https://latex.codecogs.com/png.latex?%5Comega%20%5Cin%20T_eG">, the tangent space at the identity of the Lie Group.</p>
</section>
<section id="lie-algebras" class="level2">
<h2 class="anchored" data-anchor-id="lie-algebras">Lie Algebras</h2>
<p>The tangent space at the identity of a Lie Group <img src="https://latex.codecogs.com/png.latex?G"> is called the Lie Algebra (Lie Algebras are denoted in mathfrak).</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathfrak%7Bg%7D%20=%20T_%7Be%7DG%0A"></p>
<p>It has some nice properties:</p>
<ol type="1">
<li>It’s a vector space (can add velocities, scale them).</li>
<li>All velocities in the group can be written as <img src="https://latex.codecogs.com/png.latex?%5Cdot%7Bg%7D%20=%20g%5Comega"> for some <img src="https://latex.codecogs.com/png.latex?%5Comega%20%5Cin%20T_eG">.</li>
</ol>
<p>As we determined in the last section, we know that <img src="https://latex.codecogs.com/png.latex?%5Cdot%7Bg%7D%20=%20g%5Comega">, where <img src="https://latex.codecogs.com/png.latex?%5Comega%20=%20g%5E%7B-1%7D%5Cdot%7Bg%7D%20%5Cin%20%5Cmathfrak%7Bg%7D">. Since the <img src="https://latex.codecogs.com/png.latex?%5Comega"> live inside a vector space, if we could rewrite the Euler-Lagrange equations in terms of <img src="https://latex.codecogs.com/png.latex?%5Comega">, we could use the vector space structure to add and scale them! This would solve our problem.</p>
</section>
<section id="euler-poincare-equation" class="level2">
<h2 class="anchored" data-anchor-id="euler-poincare-equation">Euler-Poincare’ Equation</h2>
<p>So, we want to rewrite the Euler-Lagrange equations in terms of <img src="https://latex.codecogs.com/png.latex?%5Comega">.</p>
<p>We know <img src="https://latex.codecogs.com/png.latex?%0A%5Comega%20=%20g%5E%7B-1%7D%5Cdot%7Bg%7D%0A"></p>
<p>The Lagrangian is thus <img src="https://latex.codecogs.com/png.latex?%0AL(g,%20%5Cdot%7Bg%7D)%20=%20L(g%20%5Ccdot%20%5Ctext%7Bid%7D_G,%20g%20%5Ccdot%20%5Comega)%0A"></p>
<p>If we assume the Lagrangian is left-invariant<sup>5</sup>, then we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL(%5Ctext%7Bid%7D_G,%20%5Comega)%0A"></p>
<p>Call this the “reduced lagrangian”</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cell:%20%5Cmathfrak%7Bg%7D%20%5Cto%20%5Cmathbb%7BR%7D%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cell(%5Comega)%20:=%20L(%5Ctext%7Bid%7D_G,%20%5Comega)%0A"></p>
<p>Let’s now think of the curves</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Comega(t)%20=%20g(t)%5E%7B-1%7D%5Cdot%7Bg%7D(t)%0A"></p>
<p>We will use the same trick we did to derive Euler-Lagrange, where we modify the path by <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> and then find a critical point with respect to the variation.</p>
<p>Define the variation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20g(t)%20:=%20%5Cfrac%7B%5Cpartial%20g_%7B%5Cepsilon%7D(t)%7D%7B%5Cpartial%20%5Cepsilon%7D%5Cbiggr%7B%7C%7D_%7B%5Cepsilon=0%7D%0A"></p>
<p>The endpoints are fixed, and hence have variation <img src="https://latex.codecogs.com/png.latex?0">.</p>
<p>So we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ceta(t):=%20g(t)%5E%7B-1%7D%5Cdelta%20g(t)%20%5Cin%20%5Cmathfrak%7Bg%7D%0A"></p>
<p>or</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20g(t)%20=%20g(t)%5Ceta(t)%0A"></p>
<p>Now define, for each <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">, <img src="https://latex.codecogs.com/png.latex?%0A%5Comega_%5Cepsilon(t)%20:=%20g_%5Cepsilon(t)%5E%7B-1%7D%20%5Cfrac%7B%5Cpartial%20g_%5Cepsilon(t)%7D%7B%5Cpartial%20t%7D%0A"></p>
<p>We want: <img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20%5Comega(t)%0A:=%20%5Cleft.%5Cfrac%7B%5Cpartial%20%5Comega_%5Cepsilon(t)%7D%7B%5Cpartial%20%5Cepsilon%7D%5Cright%7C_%7B%5Cepsilon=0%7D%0A"> in terms of <img src="https://latex.codecogs.com/png.latex?%5Comega"> and <img src="https://latex.codecogs.com/png.latex?%5Ceta">.</p>
<p>Start with <img src="https://latex.codecogs.com/png.latex?%0A%5Comega_%5Cepsilon%20=%20g_%5Cepsilon%5E%7B-1%7D%5Cdot%7Bg%7D_%5Cepsilon%0A"></p>
<p>Differentiate with respect to <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"><sup>6</sup>: <img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20%5Comega%20=%20(%5Cdelta%20g%5E%7B-1%7D)%20%5Cdot%7Bg%7D%20+%20g%5E%7B-1%7D%20%5Cdelta%20%5Cdot%7Bg%7D%0A"></p>
<p>Compute <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20g%5E%7B-1%7D"> using the identity <img src="https://latex.codecogs.com/png.latex?g_%5Cepsilon%20g_%5Cepsilon%5E%7B-1%7D%20=%20%5Ctext%7Bid%7D_G">: <img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20g%5E%7B-1%7D%20=%20-%5C,g%5E%7B-1%7D%20(%5Cdelta%20g)%5C,%20g%5E%7B-1%7D%0A"></p>
<p>Compute <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20%5Cdot%7Bg%7D">: <img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20%5Cdot%7Bg%7D%0A%20%20%20=%20%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%20t%7D(%5Cdelta%20g)%0A%20%20%20=%20%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%20t%7D(g%5Ceta)%0A%20%20%20=%20%5Cdot%7Bg%7D%5C,%5Ceta%20+%20g%5Cdot%7B%5Ceta%7D%0A"></p>
<p>Substitute both into the expression for <img src="https://latex.codecogs.com/png.latex?%5Cdelta%5Comega">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%20%20%20%5Cdelta%20%5Comega%0A%20%20%20&amp;=%20(%5Cdelta%20g%5E%7B-1%7D)%5Cdot%7Bg%7D%20+%20g%5E%7B-1%7D%5Cdelta%5Cdot%7Bg%7D%20%5C%5C%5B4pt%5D%0A%20%20%20&amp;=%20%5Cbig(-g%5E%7B-1%7D(%5Cdelta%20g)g%5E%7B-1%7D%5Cbig)%5Cdot%7Bg%7D%0A%20%20%20%20%20%20+%20g%5E%7B-1%7D(%5Cdot%7Bg%7D%5C,%5Ceta%20+%20g%5C,%5Cdot%7B%5Ceta%7D).%0A%5Cend%7Baligned%7D%0A"></p>
<p>Now insert <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20g%20=%20g%5Ceta"> and simplify:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%20%20%20%5Cbegin%7Baligned%7D%0A%20%20%20-g%5E%7B-1%7D(g%5Ceta)g%5E%7B-1%7D%5Cdot%7Bg%7D%20&amp;=%20-%5Ceta%20(g%5E%7B-1%7D%5Cdot%7Bg%7D)%20=%20-%5Ceta%5Comega%5C%5C%0A%20%20%20g%5E%7B-1%7D%5Cdot%7Bg%7D%5C,%5Ceta%20&amp;=%20%5Comega%5Ceta%5C%5C%0A%20%20%20g%5E%7B-1%7Dg%5C,%5Cdot%7B%5Ceta%7D%20&amp;=%20%5Cdot%7B%5Ceta%7D%0A%20%20%20%5Cend%7Baligned%7D%0A"></p>
<p>So we have <img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20%5Comega%20=%20%5Cdot%7B%5Ceta%7D%20+%20%5Cbiggr(%20%5Comega%5Ceta%20-%20%5Ceta%5Comega%20%5Cbiggr)%0A"></p>
<p>The second term is called the “Lie bracket” and denoted:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5B%5Comega,%5Ceta%5D%20:=%20%5Comega%5Ceta%20-%20%5Ceta%5Comega%0A"></p>
<p>So: <img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7B%5Cdelta%20%5Comega%20=%20%5Cdot%7B%5Ceta%7D%20+%20%5B%5Comega,%20%5Ceta%5D%7D%0A"></p>
<p>The action over the reduced Lagrangian is <img src="https://latex.codecogs.com/png.latex?%0AS%5B%5Comega%5D%20=%20%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%20%5Cell(%5Comega(t))dt%0A"></p>
<p>Vary it:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20S%0A=%20%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%20%5Cfrac%7Bd%7D%7Bd%5Cepsilon%7D%0A%5Cbiggr%5B%5Cell(%5Comega%20+%20%5Cepsilon%20%5C,%20%5Cdelta%20%5Comega)%5Cbiggr%5D_%7B%5Cepsilon=0%7D%20dt%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0A=%20%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%20d%5Cell(%5Comega)%5B%5Cdelta%5Comega%5D%20%5C,%20dt%0A"></p>
<p>Define <img src="https://latex.codecogs.com/png.latex?%0A%5Cmu%20:=%20%5Cfrac%7B%5Cpartial%20%5Cell%7D%7B%5Cpartial%20%5Comega%7D%20%5Cin%20%5Cmathfrak%7Bg%7D%5E*%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cmu"> is in the dual Lie Algebra <img src="https://latex.codecogs.com/png.latex?%5Cmathfrak%7Bg%7D%5E%7B*%7D">, which is linear functionals on <img src="https://latex.codecogs.com/png.latex?%5Cmathfrak%7Bg%7D"><sup>7</sup>.</p>
<p>Using the constrained variation <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20%5Comega%20=%20%5Cdot%7B%5Ceta%7D%20+%20%5B%5Comega,%5Ceta%5D"> we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20S%0A=%20%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%20%5Cmu(%5Cdelta%5Comega)%20dt%0A=%20%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%20%5Cmu(%5Cdot%7B%5Ceta%7D%20+%20%5B%5Comega,%5Ceta%5D)%20dt%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cmu"> is linear, so we can break the integral up. Integrating the first term by parts, and using <img src="https://latex.codecogs.com/png.latex?%5Ceta(t_0)=%5Ceta(t_1)=0">, we find that the first term gives <img src="https://latex.codecogs.com/png.latex?%0A%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%20%5Cmu(%5Cdot%7B%5Ceta%7D)%20dt%0A=%20-%20%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%20%5Cdot%7B%5Cmu%7D(%5Ceta)dt.%0A"></p>
<p>The second term can be rewritten using the coadjoint operator<sup>8</sup>: <img src="https://latex.codecogs.com/png.latex?%0A%5B%5Coperatorname%7Bad%7D_%5Comega%5E*%5Cmu%5D(%5Ceta)%0A:=%20%5Cmu(%5B%5Comega,%5Ceta%5D).%0A"></p>
<p>Substituting, the total variation becomes <img src="https://latex.codecogs.com/png.latex?%0A%5Cdelta%20S%0A=%20%5Cint_%7Bt_0%7D%5E%7Bt_1%7D%0A%20%20%20%20%5B-%5Cdot%7B%5Cmu%7D%20+%20%5Coperatorname%7Bad%7D_%5Comega%5E*%5Cmu%5D(%0A%20%20%20%20%5Ceta)%0A%20%20dt%0A"></p>
<p>Since <img src="https://latex.codecogs.com/png.latex?%5Ceta(t)"> is arbitrary with fixed endpoints, stationarity <img src="https://latex.codecogs.com/png.latex?%5Cdelta%20S=0"> implies that the functional</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5B-%5Cdot%7B%5Cmu%7D%20+%20%5Coperatorname%7Bad%7D_%5Comega%5E*%5Cmu%5D%0A"></p>
<p>vanishes identically (is the zero functional), and hence we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7B%0A%5Cdot%7B%5Cmu%7D%20=%20%5Coperatorname%7Bad%7D_%5Comega%5E*%5Cmu,%0A%5Cqquad%0A%5Cmu%20=%20%5Cfrac%7B%5Cpartial%20%5Cell%7D%7B%5Cpartial%20%5Comega%7D.%0A%7D%0A"></p>
<p>These are the Euler–Poincaré equations.</p>
</section>
<section id="exp-and-log" class="level2">
<h2 class="anchored" data-anchor-id="exp-and-log">Exp and Log</h2>
<p>One last note on Lie Groups before we continue.</p>
<p>The “exponential map” lets us move between the Lie Group and the Lie Algebra:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bexp%7D:%20%5Cmathfrak%7Bg%7D%20%5Cto%20G%0A"></p>
<p>That is, if we solve the equation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%7Bg%7D(t)%20=%20g(t)%5Comega%0A"></p>
<p>This give us <img src="https://latex.codecogs.com/png.latex?g(t)%20=%20%5Ctext%7Bexp%7D(t%20%5Comega%20)"><sup>9</sup></p>
<p>Algebraically we get the properties of the typical exponential:</p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cexp(0)%20=%20%5Ctext%7Bid%7D_%7BG%7D"></li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cexp((s+t)%5Comega)%20=%20%5Cexp(s%5Comega)%5Cexp(t%5Comega)"></li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cexp(-%5Comega)%20=%20%5Cexp(%5Comega)%5E%7B-1%7D"></li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cexp(%5Comega)%20=%20%5Csum_%7Bn=0%7D%5E%7B%5Cinfty%7D%20%5Cfrac%7B%5Comega%5En%7D%7Bn!%7D%20=%20I%20+%20%5Comega%20+%20%5Cfrac%7B%5Comega%5E2%7D%7B2!%7D%20+%20%5Cfrac%7B%5Comega%5E3%7D%7B3!%7D%20+%20%5Ccdots"> (for matrix Lie Groups)</li>
</ul>
<p>The logarithm is the (local) inverse: <img src="https://latex.codecogs.com/png.latex?%0A%5Clog:%20U%20%5Csubset%20G%20%5Cto%20%5Cmathfrak%7Bg%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?U"> is a neighborhood of the identity.</p>
<p>For matrix groups: <img src="https://latex.codecogs.com/png.latex?%0A%5Clog(I+A)%20=%20A%20-%20%5Cfrac%7BA%5E2%7D%7B2%7D%20+%20%5Cfrac%7BA%5E3%7D%7B3%7D%20-%20%5Cfrac%7BA%5E4%7D%7B4%7D%20+%20%5Ccdots%0A"></p>
<p>(when <img src="https://latex.codecogs.com/png.latex?%5C%7CA%5C%7C%20%3C%201">)</p>
</section>
</section>
<section id="discrete-mode" class="level1">
<h1>Discrete Mode</h1>
<p>We have now derived all the relevant geometric concepts. We need to adapt to a discrete setting. Instead of the lagrangian acting on <img src="https://latex.codecogs.com/png.latex?TQ">, it acts on <img src="https://latex.codecogs.com/png.latex?Q%5Ctimes%20Q">, where <img src="https://latex.codecogs.com/png.latex?(q_k,%20q_%7Bk+1%7D)%20%5Cin%20Q%20%5Ctimes%20Q">. That is, we use the positions at <img src="https://latex.codecogs.com/png.latex?q_%7Bk+1%7D"> rather than velocities<sup>10</sup>.</p>
<p>Instead of the action integral, we have an action sum:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathfrak%7BG%7D_d(q_0,%20q_1,%20...,%20q_n)%20=%20%5Csum_%7Bk=0%7D%5E%7BN-1%7DL_d(q_k,%20q_%7Bk+1%7D)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?L_d"> is the discrete Lagrangian.Instead of the Euler-Lagrange equations, we have the discrete Euler-Lagrange equations<sup>11</sup></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AD_2L_d(q_%7Bk%E2%88%921%7D,%20q_k)%20+%20D_1L_d(q_k,%20q_%7Bk+1%7D)%20=%200%0A"></p>
<p>The discrete Hamilton’s equations</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ap_k%20=%20%E2%88%92D_1L_%7Bd_k%7D%0A"> <img src="https://latex.codecogs.com/png.latex?%0Ap_%7Bk+1%7D%20=%20D_2L_%7Bd_k%7D%0A"></p>
<p>and the discrete Euler-Poincare’ equations:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A&amp;%20T_e%5E*L_%7Bf_0%7D%20%5Ccdot%20D_2L_d(g_0,f_0)%0A-%20%5Coperatorname%7BAd%7D_%7Bf_1%7D%5E*%5Cbig(T_e%5E*L_%7Bf_1%7D%20%5Ccdot%20D_2L_d(g_1,f_1)%5Cbig)%20%5C%5C%5B6pt%5D%0A&amp;%20%5Cquad%20+%5C,%20T_e%5E*L_%7Bg_1%7D%20%5Ccdot%20D_1L_d(g_1,f_1)%0A=%200%0A%5Cend%7Baligned%7D%0A"></p>
<p>The <img src="https://latex.codecogs.com/png.latex?D_i"> are partial derivatives. The various <img src="https://latex.codecogs.com/png.latex?L">’s in this last equation are (not more Lagrangians) and <img src="https://latex.codecogs.com/png.latex?T_e">’s are tangent maps. We will break it down in the code section.</p>
</section>
<section id="code" class="level1">
<h1>Code</h1>
<p>Let’s try to build some software to see these methods in action.</p>
<section id="existing-libraries" class="level2">
<h2 class="anchored" data-anchor-id="existing-libraries">Existing Libraries</h2>
<p>Before we start, let’s look at how some existing libraries architect similar concepts. ChatGPT gave some examples.</p>
<ol type="1">
<li>In <a href="https://trep.readthedocs.io/en/stable/">trep</a> you define a <code>System</code> object, then add frames, forces, etc. and run a variational integrator (<code>MidpointVI</code>, a variational integrator).</li>
</ol>
<p>Code is vaguely like:</p>
<div id="d987d256" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">system <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> trep.System()</span>
<span id="cb3-2">frames <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb3-3">    ty(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Provided as an angle reference</span></span>
<span id="cb3-4">    rx(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>), [tz(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, mass<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)]</span>
<span id="cb3-5">    ]</span>
<span id="cb3-6">system.import_frames(frames)</span>
<span id="cb3-7">trep.potentials.Gravity(system, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.8</span>))</span>
<span id="cb3-8">trep.forces.Damping(system, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>)</span>
<span id="cb3-9">q0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.23</span>,)   </span>
<span id="cb3-10">q1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.24</span>,)</span>
<span id="cb3-11">mvi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> trep.MidpointVI(system)</span>
<span id="cb3-12">mvi.initialize_from_configs(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, q0, dt, q1)</span>
<span id="cb3-13"></span>
<span id="cb3-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># then run the main loop</span></span></code></pre></div>
</div>
<p>I like that systems are separated from the integrators, but I find the way systems are defined to be somewhat unintuitive, and the API seems to involve a lot of “variable” names, so it doesn’t seem very extensible.</p>
<ol start="2" type="1">
<li><a href="https://github.com/artivis/manif">manif</a> and <a href="https://kornia.readthedocs.io/en/latest/geometry.liegroup.html">kornia</a> are references for designing a Lie Group API<sup>12</sup>.</li>
</ol>
<p>The minimal operations are the same between the two. <code>manif</code> has right, left, plus and minus operators for perturbations on the tangent space. <code>kornia</code> has the added advantage of differentiable operations. Both have Jacobians (though implemented differently) and “hat” and “vee” operators.</p>
<ol start="3" type="1">
<li><a href="https://gepettoweb.laas.fr/doc/loco-3d/crocoddyl/master/doxygen-html/">crocoddyl</a> and by extension <a href="https://github.com/stack-of-tasks/pinocchio">Pinocchio</a>. I looked at this library for the <a href="../../../game_theory/posts/differential_stag_hunt/index.html#Code_Architecture">differential games post</a> as well.</li>
</ol>
</section>
</section>
<section id="implementation" class="level1">
<h1>Implementation</h1>
<p>Let’s take a (brief) look at the implementation.</p>
<section id="lie-group-library" class="level2">
<h2 class="anchored" data-anchor-id="lie-group-library">Lie Group Library</h2>
<p>First, I built a very simple Lie Group library. I won’t belabor the explanations here as this isn’t really the focus of this post, and there are numerous Lie Group libraries you can fin elsewhere.</p>
<section id="elements" class="level3">
<h3 class="anchored" data-anchor-id="elements">Elements</h3>
<p>Each Lie Group is made up of <code>LieGroupElements</code>. We define a few different types. Each element must implement all of the typical Lie Group operations.</p>
<div id="cb1fbc52" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">DEFAULT_EPS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span></span>
<span id="cb4-2">DEFAULT_SKEW_TOLERANCE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-5</span></span>
<span id="cb4-3"></span>
<span id="cb4-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> LieGroupElement(ABC):</span>
<span id="cb4-5">    </span>
<span id="cb4-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, group: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'LieGroup'</span>):</span>
<span id="cb4-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> group</span>
<span id="cb4-8">    </span>
<span id="cb4-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb4-10">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb4-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> tensor(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb4-13">    </span>
<span id="cb4-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__mul__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, other: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'LieGroupElement'</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'LieGroupElement'</span>:</span>
<span id="cb4-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(other, LieGroupElement):</span>
<span id="cb4-16">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Cannot multiply with </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(other)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.compose(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, other)</span>
<span id="cb4-18">    </span>
<span id="cb4-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__matmul__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, point: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.action(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, point)</span>
<span id="cb4-21">    </span>
<span id="cb4-22">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> inverse(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'LieGroupElement'</span>:</span>
<span id="cb4-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.inverse(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>)</span>
<span id="cb4-24">    </span>
<span id="cb4-25">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> log(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.group.log(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>)</span>
<span id="cb4-27">    </span>
<span id="cb4-28">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb4-29">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb4-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb4-31"></span>
<span id="cb4-32"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> TensorElement(LieGroupElement):</span>
<span id="cb4-33">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, group: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'LieGroup'</span>, data: torch.Tensor):</span>
<span id="cb4-34">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(group)</span>
<span id="cb4-35">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data</span>
<span id="cb4-36">    </span>
<span id="cb4-37">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb4-38">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> tensor(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._data</span>
<span id="cb4-40">    </span>
<span id="cb4-41">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb4-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Element(type=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>group<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">,shape=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>_data<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span></span>
<span id="cb4-43"></span>
<span id="cb4-44"></span>
<span id="cb4-45"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ProductElement(LieGroupElement):</span>
<span id="cb4-46">    </span>
<span id="cb4-47">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, group: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Product'</span>, components: Tuple[LieGroupElement, ...]):</span>
<span id="cb4-48">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(group)</span>
<span id="cb4-49">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> components</span>
<span id="cb4-50">        </span>
<span id="cb4-51">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(components) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(group.factors):</span>
<span id="cb4-52">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(</span>
<span id="cb4-53">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Expected </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(group.factors)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> components, got </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(components)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb4-54">            )</span>
<span id="cb4-55">    </span>
<span id="cb4-56">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb4-57">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> tensor(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-58">        tensors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb4-59">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> elem <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.components:</span>
<span id="cb4-60">            t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> elem.tensor</span>
<span id="cb4-61">            batch_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> t.shape[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(elem.group._element_shape())]</span>
<span id="cb4-62">            tensors.append(t.reshape(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb4-63">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cat(tensors, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb4-64">    </span>
<span id="cb4-65">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__getitem__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, index: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb4-66">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.components[index]</span>
<span id="cb4-67">    </span>
<span id="cb4-68">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb4-69">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Element(type=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>group<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">,components=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.components)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span></span></code></pre></div>
</div>
</section>
<section id="lie-group-abstraction" class="level3">
<h3 class="anchored" data-anchor-id="lie-group-abstraction">Lie Group Abstraction</h3>
<p>Next we have the high-level group structure:</p>
<div id="ff5d253d" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> LieGroup(ABC):</span>
<span id="cb5-2">    </span>
<span id="cb5-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb5-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb5-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb5-7">    </span>
<span id="cb5-8">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _identity_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb5-11">    </span>
<span id="cb5-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _compose_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb5-15">    </span>
<span id="cb5-16">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-17">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _inverse_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb5-19">    </span>
<span id="cb5-20">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-21">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _exp_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb5-23">    </span>
<span id="cb5-24">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-25">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _log_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb5-27">    </span>
<span id="cb5-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Public API</span></span>
<span id="cb5-29">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> identity(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._identity_impl(batch_shape)</span>
<span id="cb5-31">    </span>
<span id="cb5-32">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> compose(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._compose_impl(g, h)</span>
<span id="cb5-34">    </span>
<span id="cb5-35">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> inverse(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-36">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._inverse_impl(g)</span>
<span id="cb5-37">    </span>
<span id="cb5-38">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> exp(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._exp_impl(omega)</span>
<span id="cb5-40">    </span>
<span id="cb5-41">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> log(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._log_impl(g)</span>
<span id="cb5-43"></span>
<span id="cb5-44">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hat(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega_coords: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-45">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Coordinates to "natural form" of tensor (for readability/debugging)</span></span>
<span id="cb5-46">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Identity by default</span></span>
<span id="cb5-47">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> omega_coords</span>
<span id="cb5-48">    </span>
<span id="cb5-49">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> vee(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega_natural: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-50">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># "Natural form" to coordinates of tensor (for readability/debugging)</span></span>
<span id="cb5-51">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Identity by default</span></span>
<span id="cb5-52">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> omega_natural</span>
<span id="cb5-53"></span>
<span id="cb5-54">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Optional operations</span></span>
<span id="cb5-55">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> random(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-56">        omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randn(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim)</span>
<span id="cb5-57">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.exp(omega)</span>
<span id="cb5-58">    </span>
<span id="cb5-59">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> action(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, point: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-60">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span>(</span>
<span id="cb5-61">            <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>__class__<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> does not implement action"</span></span>
<span id="cb5-62">        )</span>
<span id="cb5-63">    </span>
<span id="cb5-64">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Derived operations</span></span>
<span id="cb5-65">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> rplus(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-66">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.compose(g, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.exp(omega))</span>
<span id="cb5-67">    </span>
<span id="cb5-68">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> rminus(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-69">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.log(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.compose(g.inverse(), h))</span>
<span id="cb5-70">    </span>
<span id="cb5-71">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lplus(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-72">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.compose(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.exp(omega), g)</span>
<span id="cb5-73">    </span>
<span id="cb5-74">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lminus(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-75">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.log(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.compose(g, h.inverse()))</span>
<span id="cb5-76">    </span>
<span id="cb5-77">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> adjoint(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-78">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Override for analytical formula (much faster)</span></span>
<span id="cb5-79">        batch_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g.tensor.shape[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._element_shape())]</span>
<span id="cb5-80">        Ad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim,</span>
<span id="cb5-81">                        dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g.tensor.dtype, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g.tensor.device)</span>
<span id="cb5-82">        </span>
<span id="cb5-83">        g_inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g.inverse()</span>
<span id="cb5-84">        </span>
<span id="cb5-85">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim):</span>
<span id="cb5-86">            omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim,</span>
<span id="cb5-87">                              dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g.tensor.dtype, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g.tensor.device)</span>
<span id="cb5-88">            omega[..., i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> DEFAULT_EPS</span>
<span id="cb5-89">            </span>
<span id="cb5-90">            perturbed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.exp(omega) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> g_inv</span>
<span id="cb5-91">            Ad[..., :, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.log(perturbed) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> DEFAULT_EPS</span>
<span id="cb5-92">        </span>
<span id="cb5-93">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Ad</span>
<span id="cb5-94">    </span>
<span id="cb5-95">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Utilities</span></span>
<span id="cb5-96">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _element_shape(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]:</span>
<span id="cb5-97">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.identity().tensor.shape</span>
<span id="cb5-98">    </span>
<span id="cb5-99">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb5-100">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.compose(g, h)</span>
<span id="cb5-101">    </span>
<span id="cb5-102">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb5-103">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_compact(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb5-104">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb5-105">    </span>
<span id="cb5-106">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-107">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb5-108">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span></code></pre></div>
</div>
</section>
<section id="specific-lie-groups" class="level3">
<h3 class="anchored" data-anchor-id="specific-lie-groups">Specific Lie Groups</h3>
<p>Here’s a few specific Lie Groups. Note that we also implement <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5En"> as a Lie Group. It’s operations are typical vector addition (for add) and multiplication by <img src="https://latex.codecogs.com/png.latex?-1"> (for inversion).</p>
<div id="ef70f69a" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> SOn(LieGroup):</span>
<span id="cb6-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb6-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:</span>
<span id="cb6-4">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"SO(n) requires n &gt;= 2, got </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb6-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> n</span>
<span id="cb6-6">    </span>
<span id="cb6-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb6-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb6-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-10">    </span>
<span id="cb6-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _identity_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-12">        I <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.eye(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n).expand(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n).clone()</span>
<span id="cb6-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, I)</span>
<span id="cb6-14">    </span>
<span id="cb6-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _compose_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-16">        result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.matmul(g.tensor, h.tensor)</span>
<span id="cb6-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, result)</span>
<span id="cb6-18">    </span>
<span id="cb6-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _inverse_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-20">        result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g.tensor.transpose(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, result)</span>
<span id="cb6-22">    </span>
<span id="cb6-23">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _exp_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-24">        omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.hat(omega)</span>
<span id="cb6-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> torch.is_grad_enabled():</span>
<span id="cb6-26">            skew_error <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> omega.transpose(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-27">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> skew_error <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> DEFAULT_SKEW_TOLERANCE:</span>
<span id="cb6-28">               <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"exp() expects skew-symmetric matrix"</span>)</span>
<span id="cb6-29">        </span>
<span id="cb6-30">        R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.linalg.matrix_exp(omega)</span>
<span id="cb6-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, R)</span>
<span id="cb6-32"></span>
<span id="cb6-33">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _log_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb6-34">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># really stupid 1st order approximation</span></span>
<span id="cb6-35">        R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g.tensor                           </span>
<span id="cb6-36">        skew <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> R.transpose(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)) </span>
<span id="cb6-37">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.vee(skew) </span>
<span id="cb6-38"></span>
<span id="cb6-39">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> random(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-40">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Uniform sampling via QR decomposition</span></span>
<span id="cb6-41">        A <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randn(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n)</span>
<span id="cb6-42">        Q, R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.linalg.qr(A)</span>
<span id="cb6-43">        </span>
<span id="cb6-44">        signs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.sign(torch.diagonal(R, dim1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, dim2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-45">        signs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.where(signs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, torch.ones_like(signs), signs)</span>
<span id="cb6-46">        Q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> signs.unsqueeze(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb6-47">        </span>
<span id="cb6-48">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, Q)</span>
<span id="cb6-49">    </span>
<span id="cb6-50">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> action(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, point: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb6-51">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rotate vectors</span></span>
<span id="cb6-52">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.matmul(g.tensor, point.unsqueeze(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)).squeeze(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-53">    </span>
<span id="cb6-54">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Inherit this for now </span></span>
<span id="cb6-55">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># def adjoint(self, g: LieGroupElement) -&gt; torch.Tensor:</span></span>
<span id="cb6-56">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     # For SO(n), n=2 and n=3, adjoint is the matrix itself</span></span>
<span id="cb6-57">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     return g.tensor</span></span>
<span id="cb6-58"></span>
<span id="cb6-59">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hat(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb6-60">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> omega.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim:</span>
<span id="cb6-61">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> omega</span>
<span id="cb6-62">        n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n</span>
<span id="cb6-63">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> omega.ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> omega.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> (n, n):</span>
<span id="cb6-64">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> omega</span>
<span id="cb6-65">        batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> omega.shape[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb6-66">        skew <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch, n, n, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>omega.device, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>omega.dtype)</span>
<span id="cb6-67">        idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb6-68">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n):</span>
<span id="cb6-69">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n):</span>
<span id="cb6-70">                skew[..., i, j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> omega[..., idx]</span>
<span id="cb6-71">                skew[..., j, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>omega[..., idx]</span>
<span id="cb6-72">                idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-73">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> skew </span>
<span id="cb6-74">    </span>
<span id="cb6-75">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> vee(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb6-76">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> omega.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim:</span>
<span id="cb6-77">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> omega</span>
<span id="cb6-78">        n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n</span>
<span id="cb6-79">        coords <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-80">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n):</span>
<span id="cb6-81">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n):</span>
<span id="cb6-82">                coords.append(omega[..., i, j])</span>
<span id="cb6-83">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.stack(coords, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-84">    </span>
<span id="cb6-85">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb6-86">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> is_compact(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb6-87">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb6-88">    </span>
<span id="cb6-89">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb6-90">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"SO(</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>_n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span></span>
<span id="cb6-91"></span>
<span id="cb6-92"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Rn(LieGroup):</span>
<span id="cb6-93">    </span>
<span id="cb6-94">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb6-95">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb6-96">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"R^n requires n &gt;= 1, got </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb6-97">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> n</span>
<span id="cb6-98">    </span>
<span id="cb6-99">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb6-100">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb6-101">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n</span>
<span id="cb6-102">    </span>
<span id="cb6-103">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _identity_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-104">        zeros <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n)</span>
<span id="cb6-105">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, zeros)</span>
<span id="cb6-106">    </span>
<span id="cb6-107">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _compose_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-108">        result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g.tensor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h.tensor</span>
<span id="cb6-109">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, result)</span>
<span id="cb6-110">    </span>
<span id="cb6-111">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _inverse_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-112">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>g.tensor)</span>
<span id="cb6-113">    </span>
<span id="cb6-114">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _exp_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb6-115">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> TensorElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega)</span>
<span id="cb6-116">    </span>
<span id="cb6-117">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _log_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb6-118">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> g.tensor</span>
<span id="cb6-119">    </span>
<span id="cb6-120">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> action(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, point: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb6-121">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Translation</span></span>
<span id="cb6-122">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> point <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> g.tensor</span>
<span id="cb6-123">    </span>
<span id="cb6-124">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> adjoint(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb6-125">        batch_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g.tensor.shape[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb6-126">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.eye(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g.tensor.dtype, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g.tensor.device).expand(</span>
<span id="cb6-127">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._n</span>
<span id="cb6-128">        ).clone()</span>
<span id="cb6-129">    </span>
<span id="cb6-130">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb6-131">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"R^</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>_n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span></code></pre></div>
</div>
</section>
<section id="products" class="level3">
<h3 class="anchored" data-anchor-id="products">Products</h3>
<p>We also implement products and semidirect products.</p>
<div id="6cff168a" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Product(LieGroup):</span>
<span id="cb7-2">    </span>
<span id="cb7-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, factors: List[LieGroup]):</span>
<span id="cb7-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(factors) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:</span>
<span id="cb7-5">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Product requires at least 2 groups"</span>)</span>
<span id="cb7-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> factors</span>
<span id="cb7-7">    </span>
<span id="cb7-8">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb7-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb7-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(g.dim <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors)</span>
<span id="cb7-11">    </span>
<span id="cb7-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _identity_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-13">        components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(g.identity(batch_shape) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors)</span>
<span id="cb7-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, components)</span>
<span id="cb7-15">    </span>
<span id="cb7-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _compose_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-17">        g_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(g)</span>
<span id="cb7-18">        h_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(h, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(h)</span>
<span id="cb7-19">        </span>
<span id="cb7-20">        components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(</span>
<span id="cb7-21">            g_comp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> h_comp</span>
<span id="cb7-22">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g_comp, h_comp <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(g_prod.components, h_prod.components)</span>
<span id="cb7-23">        )</span>
<span id="cb7-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, components)</span>
<span id="cb7-25">    </span>
<span id="cb7-26">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _inverse_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-27">        g_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(g)</span>
<span id="cb7-28">        components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(comp.inverse() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> comp <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> g_prod.components)</span>
<span id="cb7-29">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, components)</span>
<span id="cb7-30">    </span>
<span id="cb7-31">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _exp_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-32">        omega_split <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._split_tangent(omega)</span>
<span id="cb7-33">        components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(</span>
<span id="cb7-34">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors[i].exp(omega_split[i])</span>
<span id="cb7-35">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors))</span>
<span id="cb7-36">        )</span>
<span id="cb7-37">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, components)</span>
<span id="cb7-38">    </span>
<span id="cb7-39">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _log_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb7-40">        g_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(g)</span>
<span id="cb7-41">        logs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [factor.log(comp) </span>
<span id="cb7-42">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> factor, comp <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors, g_prod.components)]</span>
<span id="cb7-43">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cat(logs, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-44">    </span>
<span id="cb7-45">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> random(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-46">        components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(g.random(batch_shape) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors)</span>
<span id="cb7-47">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, components)</span>
<span id="cb7-48">    </span>
<span id="cb7-49">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> action(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, point: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb7-50">        g_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(g)</span>
<span id="cb7-51">        point_split <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._split_tangent(point)</span>
<span id="cb7-52">        </span>
<span id="cb7-53">        results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb7-54">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors[i].action(g_prod.components[i], point_split[i])</span>
<span id="cb7-55">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors))</span>
<span id="cb7-56">        ]</span>
<span id="cb7-57">        </span>
<span id="cb7-58">        batch_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> point.shape[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb7-59">        flat_results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [r.reshape(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> results]</span>
<span id="cb7-60">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cat(flat_results, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-61">    </span>
<span id="cb7-62">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> adjoint(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb7-63">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Block diagonal adjoint</span></span>
<span id="cb7-64">        g_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(g)</span>
<span id="cb7-65">        batch_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g.tensor.shape[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb7-66">        </span>
<span id="cb7-67">        Ad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>batch_shape, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim,</span>
<span id="cb7-68">                        dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g.tensor.dtype, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>g.tensor.device)</span>
<span id="cb7-69">        </span>
<span id="cb7-70">        offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb7-71">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, (comp, factor) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(g_prod.components, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors)):</span>
<span id="cb7-72">            dim_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> factor.dim</span>
<span id="cb7-73">            Ad[..., offset:offset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>dim_i, offset:offset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>dim_i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> factor.adjoint(comp)</span>
<span id="cb7-74">            offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> dim_i</span>
<span id="cb7-75">        </span>
<span id="cb7-76">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Ad</span>
<span id="cb7-77">    </span>
<span id="cb7-78">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _split_tangent(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> List[torch.Tensor]:</span>
<span id="cb7-79">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Split tangent vector into components</span></span>
<span id="cb7-80">        dims <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [g.dim <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors]</span>
<span id="cb7-81">        offsets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(torch.cumsum(torch.tensor(dims), dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb7-82">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [omega[..., offsets[i]:offsets[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(dims))]</span>
<span id="cb7-83">    </span>
<span id="cb7-84">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _to_product(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> ProductElement:</span>
<span id="cb7-85">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Convert generic element to ProductElement if needed</span></span>
<span id="cb7-86">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement):</span>
<span id="cb7-87">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> g</span>
<span id="cb7-88">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Expected ProductElement, got </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(g)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-89">    </span>
<span id="cb7-90">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb7-91">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" × "</span>.join(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">repr</span>(g) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors)</span>
<span id="cb7-92"></span>
<span id="cb7-93"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> SemidirectProduct(LieGroup):</span>
<span id="cb7-94"></span>
<span id="cb7-95">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, normal: LieGroup, actor: LieGroup):</span>
<span id="cb7-96">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> normal</span>
<span id="cb7-97">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> actor</span>
<span id="cb7-98">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.factors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [normal, actor]</span>
<span id="cb7-99">    </span>
<span id="cb7-100">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb7-101">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb7-102">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.normal.dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.dim</span>
<span id="cb7-103">    </span>
<span id="cb7-104">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _identity_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-105">        components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.identity(batch_shape), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.normal.identity(batch_shape))</span>
<span id="cb7-106">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, components)</span>
<span id="cb7-107">    </span>
<span id="cb7-108">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _compose_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement, h: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-109">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Semidirect product composition with action!"""</span></span>
<span id="cb7-110">        g_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(g)</span>
<span id="cb7-111">        h_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(h, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(h)</span>
<span id="cb7-112">        </span>
<span id="cb7-113">        g_actor, g_normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_prod.components</span>
<span id="cb7-114">        h_actor, h_normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h_prod.components</span>
<span id="cb7-115">        </span>
<span id="cb7-116">        result_actor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_actor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> h_actor</span>
<span id="cb7-117">        result_normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> TensorElement(</span>
<span id="cb7-118">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.normal,</span>
<span id="cb7-119">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.action(g_actor, h_normal.tensor)</span>
<span id="cb7-120">        )</span>
<span id="cb7-121">        </span>
<span id="cb7-122">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, (result_actor, result_normal))</span>
<span id="cb7-123">    </span>
<span id="cb7-124">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _inverse_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-125">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Semidirect product inverse"""</span></span>
<span id="cb7-126">        g_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(g)</span>
<span id="cb7-127">        g_actor, g_normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_prod.components</span>
<span id="cb7-128">        </span>
<span id="cb7-129">        g_actor_inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_actor.inverse()</span>
<span id="cb7-130">        g_normal_inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> TensorElement(</span>
<span id="cb7-131">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.normal,</span>
<span id="cb7-132">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.action(g_actor_inv, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>g_normal.tensor)</span>
<span id="cb7-133">        )</span>
<span id="cb7-134">        </span>
<span id="cb7-135">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, (g_actor_inv, g_normal_inv))</span>
<span id="cb7-136">    </span>
<span id="cb7-137">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _exp_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, omega: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-138">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Exponential (using product structure)"""</span></span>
<span id="cb7-139">        omega_actor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> omega[..., :<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.dim]</span>
<span id="cb7-140">        omega_normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> omega[..., <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.dim:]</span>
<span id="cb7-141">        </span>
<span id="cb7-142">        components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb7-143">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.exp(omega_actor),</span>
<span id="cb7-144">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.normal.exp(omega_normal)</span>
<span id="cb7-145">        )</span>
<span id="cb7-146">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, components)</span>
<span id="cb7-147">    </span>
<span id="cb7-148">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _log_impl(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb7-149">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Logarithm (using product structure)"""</span></span>
<span id="cb7-150">        g_prod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._to_product(g)</span>
<span id="cb7-151">        g_actor, g_normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_prod.components</span>
<span id="cb7-152">    </span>
<span id="cb7-153">        log_actor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.log(g_actor).flatten()</span>
<span id="cb7-154">        log_normal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.normal.log(g_normal).flatten()</span>
<span id="cb7-155"></span>
<span id="cb7-156">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cat([log_actor, log_normal], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb7-157">    </span>
<span id="cb7-158">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> random(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, batch_shape: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, ...] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> LieGroupElement:</span>
<span id="cb7-159">        components <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actor.random(batch_shape), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.normal.random(batch_shape))</span>
<span id="cb7-160">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ProductElement(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, components)</span>
<span id="cb7-161">    </span>
<span id="cb7-162">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _to_product(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, g: LieGroupElement) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> ProductElement:</span>
<span id="cb7-163">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(g, ProductElement):</span>
<span id="cb7-164">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> g</span>
<span id="cb7-165">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TypeError</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Expected ProductElement, got </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(g)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-166">    </span>
<span id="cb7-167">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb7-168">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>normal<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ⋊ </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>actor<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span></code></pre></div>
</div>
<p>As a bonus, here’s an example group implemented via semidirect product.</p>
<div id="35de2165" class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> SE(n: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> SemidirectProduct:</span>
<span id="cb8-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> SemidirectProduct(Rn(n), SOn(n))</span></code></pre></div>
</div>
</section>
</section>
</section>
<section id="basic-geometric-controls" class="level1">
<h1>Basic Geometric Controls</h1>
<p>Now we need to implement the actual control code. Let’s review what we mean when we say “controls” here, as we are still interpreting the Lagrangian in a slightly unusual way, and our picture here may seem “backwards” from the typical picture.</p>
<p>The Lagrangian, in our interpretation, is the “value/cost density” for each infinitesimal path segment that our system travails from <img src="https://latex.codecogs.com/png.latex?q_0"> (start position) to <img src="https://latex.codecogs.com/png.latex?q_1"> (end position). We control the system by defining which value/cost density we wish the system to maximize/minimize (depending on the sign). This corresponds to an equivalent “policy” (Hamiltonian) In mechanics, the cost/value density is the energy functional (usually <img src="https://latex.codecogs.com/png.latex?T-V">, the difference between the kinetic and potential energies).</p>
<section id="control-plane-helpers" class="level2">
<h2 class="anchored" data-anchor-id="control-plane-helpers">Control Plane Helpers</h2>
<p>Let’s look at our “control plane” helper functions. What we will do is define a few abstract variables (to control). These functions track those variables and pack/unpack them into state tensors.</p>
<div id="769bae19" class="cell" data-execution_count="9">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span>(frozen<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb9-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> StateHandle:</span>
<span id="cb9-3">    name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb9-4">    group: LieGroup</span>
<span id="cb9-5"></span>
<span id="cb9-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> AttrObject:</span>
<span id="cb9-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, mapping):</span>
<span id="cb9-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k, v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> mapping.items():</span>
<span id="cb9-9">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">setattr</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, k, v)</span>
<span id="cb9-10"></span>
<span id="cb9-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> DiscreteModel:</span>
<span id="cb9-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, control_plane):</span>
<span id="cb9-13">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">vars</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> control_plane</span>
<span id="cb9-14"></span>
<span id="cb9-15">        offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb9-16">        layout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb9-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, group <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">vars</span>.items():</span>
<span id="cb9-18">            d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> group.dim</span>
<span id="cb9-19">            layout[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (group, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>(offset, offset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>d))</span>
<span id="cb9-20">            offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> d</span>
<span id="cb9-21"></span>
<span id="cb9-22">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layout</span>
<span id="cb9-23">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> offset</span>
<span id="cb9-24"></span>
<span id="cb9-25">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> unpack(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, q):</span>
<span id="cb9-26">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb9-27">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, (_, sl) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layout.items():</span>
<span id="cb9-28">            out[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q[sl]</span>
<span id="cb9-29">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> AttrObject(out)</span>
<span id="cb9-30"></span>
<span id="cb9-31">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> pack(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ctrl):</span>
<span id="cb9-32">        parts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb9-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, (_, _) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layout.items():</span>
<span id="cb9-34">            parts.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">getattr</span>(ctrl, name).reshape(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb9-35">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cat(parts)</span></code></pre></div>
</div>
</section>
<section id="lagrangian-1" class="level2">
<h2 class="anchored" data-anchor-id="lagrangian-1">Lagrangian</h2>
<p>Let’s look at the actual Lagrangian “control place” abstraction. We implement the discrete langrangian we actually will use as a function of the <code>VariationalSystem</code> class, “under the hood”. To define a Lagrangian system, a user needs to inherit from this class, define their params and control plane variables, and implement the Lagrangian.</p>
<div id="1b4df177" class="cell" data-execution_count="10">
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> VariationalSystem:</span>
<span id="cb10-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, param_values):</span>
<span id="cb10-3">        </span>
<span id="cb10-4">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build params object</span></span>
<span id="cb10-5">        names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params()</span>
<span id="cb10-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({name: param_values[name] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> names})</span>
<span id="cb10-7"></span>
<span id="cb10-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build model</span></span>
<span id="cb10-9">        cp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.control_plane()</span>
<span id="cb10-10">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> DiscreteModel(cp)</span>
<span id="cb10-11"></span>
<span id="cb10-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> discrete_lagrangian(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, qk, qk1, h):</span>
<span id="cb10-13">        mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (qk <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> qk1)</span>
<span id="cb10-14">        vel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (qk1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> qk) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> h</span>
<span id="cb10-15">        ctrl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.model.unpack(mid)</span>
<span id="cb10-16">        dctrl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.model.unpack(vel)</span>
<span id="cb10-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lagrangian(ctrl, dctrl)</span>
<span id="cb10-18"></span>
<span id="cb10-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> control_plane(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span>
<span id="cb10-20">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> params(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span>
<span id="cb10-21">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lagrangian(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ctrl, dctrl): <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span></span></code></pre></div>
</div>
</section>
<section id="solvers" class="level2">
<h2 class="anchored" data-anchor-id="solvers">Solvers</h2>
<p>When the system definition is in place, we can subsequently run our <code>VariationalIntegrator</code> to actually solve the system.</p>
<div id="87ee1e23" class="cell" data-execution_count="11">
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> VariationalIntegrator:</span>
<span id="cb11-2"></span>
<span id="cb11-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, system: VariationalSystem, step_size: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>,</span>
<span id="cb11-4">                 max_iters: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>, tol: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-10</span>, on_step: Optional[Callable] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb11-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.system <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> system</span>
<span id="cb11-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(step_size)</span>
<span id="cb11-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.max_iters <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> max_iters</span>
<span id="cb11-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tol</span>
<span id="cb11-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.on_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> on_step</span>
<span id="cb11-10"></span>
<span id="cb11-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> D1_Ld(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, qk: torch.Tensor, qk1: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb11-12">        qk_var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> qk.clone().detach().requires_grad_(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-13">        Ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.system.discrete_lagrangian(qk_var, qk1, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.h)</span>
<span id="cb11-14">        grad_qk, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.autograd.grad(Ld, qk_var, create_graph<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> grad_qk</span>
<span id="cb11-16"></span>
<span id="cb11-17">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> D2_Ld(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, qk: torch.Tensor, qk1: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb11-18">        qk_var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> qk.clone().detach().requires_grad_(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-19">        qk1_var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> qk1.clone().detach().requires_grad_(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-20">        Ld <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.system.discrete_lagrangian(qk_var, qk1_var, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.h)</span>
<span id="cb11-21">        grad_qk1, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.autograd.grad(Ld, qk1_var, create_graph<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb11-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> grad_qk1.detach()</span>
<span id="cb11-23"></span>
<span id="cb11-24">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> step(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, q_prev: torch.Tensor, q_curr: torch.Tensor):</span>
<span id="cb11-25"></span>
<span id="cb11-26">        q_next <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> q_prev)</span>
<span id="cb11-27">        q_next <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_next.clone().detach().requires_grad_(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-28">        const_term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D2_Ld(q_prev, q_curr).detach()</span>
<span id="cb11-29"></span>
<span id="cb11-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.max_iters):</span>
<span id="cb11-31">            F <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> const_term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D1_Ld(q_curr, q_next)</span>
<span id="cb11-32"></span>
<span id="cb11-33">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> F.norm().item() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol:</span>
<span id="cb11-34">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> q_next.detach(), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb11-35"></span>
<span id="cb11-36">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> F_of(x: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb11-37">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> const_term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.D1_Ld(q_curr, x)</span>
<span id="cb11-38"></span>
<span id="cb11-39">            J <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.autograd.functional.jacobian(F_of, q_next)</span>
<span id="cb11-40">            delta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.linalg.solve(J, F)</span>
<span id="cb11-41"></span>
<span id="cb11-42">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> torch.no_grad():</span>
<span id="cb11-43">                q_next <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_next <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> delta</span>
<span id="cb11-44">            q_next.requires_grad_(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb11-45"></span>
<span id="cb11-46">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> delta.norm().item() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tol:</span>
<span id="cb11-47">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> q_next.detach(), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb11-48">        </span>
<span id="cb11-49">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.on_step <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb11-50">                <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.on_step(q_prev.detach(), q_curr.detach(), q_next.detach())</span>
<span id="cb11-51"></span>
<span id="cb11-52">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> q_next.detach(), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span></code></pre></div>
</div>
<p>As promised, let’s look a bit more in-depth at the code above. We have the two “partial derivative functions”. <code>D1_Ld</code> takes the derivative of <img src="https://latex.codecogs.com/png.latex?q_k">, then evaluates the discrete lagrangian. <code>D2_Ld</code> takes the derivative of <img src="https://latex.codecogs.com/png.latex?q_%7Bk+1%7D"> and does the same. The actual step function runs through Newton’s method (repeatedly linearizing that equation and solving a small linear system until convergence).</p>
</section>
<section id="example-pendulum" class="level2">
<h2 class="anchored" data-anchor-id="example-pendulum">Example: Pendulum</h2>
<p>Let’s look at the simplest possible example.</p>
<section id="lagrangian-2" class="level3">
<h3 class="anchored" data-anchor-id="lagrangian-2">Lagrangian</h3>
<p>Here we define the actual lagrangian for a system. We have a pendulum, with <img src="https://latex.codecogs.com/png.latex?%5Ctheta%20%5Cin%20SO_2">. We control the pendulum angle using a lagrangian that is equivalent to the one found in physics: we take the kinetic energy minus the potential energy.</p>
<div id="a77ca8fa" class="cell" data-execution_count="12">
<div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Pendulum(VariationalSystem):</span>
<span id="cb12-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> control_plane(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb12-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb12-4">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>: SOn(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb12-5">        }</span>
<span id="cb12-6">    </span>
<span id="cb12-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> params(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb12-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"length"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gravity"</span>]</span>
<span id="cb12-9"></span>
<span id="cb12-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lagrangian(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ctrl, dctrl):</span>
<span id="cb12-11">        th  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ctrl.theta</span>
<span id="cb12-12">        thd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dctrl.theta</span>
<span id="cb12-13"></span>
<span id="cb12-14">        m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.mass</span>
<span id="cb12-15">        l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.length</span>
<span id="cb12-16">        g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.params.gravity</span>
<span id="cb12-17"></span>
<span id="cb12-18">        T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> thd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> thd</span>
<span id="cb12-19">        V <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> torch.cos(th))</span>
<span id="cb12-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> V</span></code></pre></div>
</div>
</section>
<section id="helper-functions" class="level3">
<h3 class="anchored" data-anchor-id="helper-functions">Helper Functions</h3>
<p>Here’s some helper functions to record data, plot, etc.</p>
<div id="9e54efbc" class="cell" data-execution_count="13">
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> StepRecorder:</span>
<span id="cb13-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb13-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.records <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb13-4"></span>
<span id="cb13-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> on_step(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, q_prev, q_curr, q_next):</span>
<span id="cb13-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.records.append({</span>
<span id="cb13-7">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q_prev"</span>: q_prev.clone(),</span>
<span id="cb13-8">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q_curr"</span>: q_curr.clone(),</span>
<span id="cb13-9">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q_next"</span>: q_next.clone(),</span>
<span id="cb13-10">        })</span>
<span id="cb13-11"></span>
<span id="cb13-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> pendulum_observables_from_records(pendulum: Pendulum,</span>
<span id="cb13-13">                                      recorder: StepRecorder,</span>
<span id="cb13-14">                                      h: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>):</span>
<span id="cb13-15">    m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pendulum.params.mass</span>
<span id="cb13-16">    l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pendulum.params.length</span>
<span id="cb13-17">    g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pendulum.params.gravity</span>
<span id="cb13-18"></span>
<span id="cb13-19">    ts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb13-20">    thetas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb13-21">    theta_dots <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb13-22">    energies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb13-23"></span>
<span id="cb13-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k, rec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(recorder.records):</span>
<span id="cb13-25">        t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> h </span>
<span id="cb13-26">        q_prev <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q_prev"</span>]</span>
<span id="cb13-27">        q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"q_curr"</span>]</span>
<span id="cb13-28"></span>
<span id="cb13-29">        theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb13-30">        theta_prev <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_prev[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb13-31"></span>
<span id="cb13-32">        theta_dot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> theta_prev) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> h</span>
<span id="cb13-33"></span>
<span id="cb13-34">        T_k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> theta_dot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> theta_dot</span>
<span id="cb13-35">        V_k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> l <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> torch.cos(theta))</span>
<span id="cb13-36">        E_k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> T_k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> V_k</span>
<span id="cb13-37"></span>
<span id="cb13-38">        ts.append(t)</span>
<span id="cb13-39">        thetas.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(theta))</span>
<span id="cb13-40">        theta_dots.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(theta_dot))</span>
<span id="cb13-41">        energies.append(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(E_k))</span>
<span id="cb13-42"></span>
<span id="cb13-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ts, thetas, theta_dots, energies</span></code></pre></div>
</div>
</section>
<section id="outcome" class="level3">
<h3 class="anchored" data-anchor-id="outcome">Outcome</h3>
<p>Here’s the code to actually run everything:</p>
<div id="40815b22" class="cell" data-execution_count="14">
<div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb14-2">    torch.set_default_dtype(torch.float64)</span>
<span id="cb14-3"></span>
<span id="cb14-4">    pendulum <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Pendulum({</span>
<span id="cb14-5">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mass"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>,</span>
<span id="cb14-6">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"length"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>,</span>
<span id="cb14-7">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gravity"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.81</span>,</span>
<span id="cb14-8">    })</span>
<span id="cb14-9"></span>
<span id="cb14-10">    h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.001</span></span>
<span id="cb14-11">    recorder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StepRecorder()</span>
<span id="cb14-12">    integrator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> VariationalIntegrator(pendulum, step_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>h, on_step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>recorder.on_step)</span>
<span id="cb14-13"></span>
<span id="cb14-14">    theta0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span></span>
<span id="cb14-15">    theta_dot0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb14-16">    ctrl0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>: torch.tensor([theta0])})</span>
<span id="cb14-17">    q0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pendulum.model.pack(ctrl0)</span>
<span id="cb14-18"></span>
<span id="cb14-19">    ctrl1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AttrObject({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"theta"</span>: torch.tensor([theta0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> theta_dot0])})</span>
<span id="cb14-20">    q1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pendulum.model.pack(ctrl1)</span>
<span id="cb14-21"></span>
<span id="cb14-22">    steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10000</span></span>
<span id="cb14-23">    qs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [q0.clone(), q1.clone()]</span>
<span id="cb14-24">    q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q0, q1</span>
<span id="cb14-25"></span>
<span id="cb14-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>):</span>
<span id="cb14-27">        q_next, ok <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> integrator.step(q_prev, q_curr)</span>
<span id="cb14-28">        qs.append(q_next.clone())</span>
<span id="cb14-29">        q_prev, q_curr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q_curr, q_next</span>
<span id="cb14-30"></span>
<span id="cb14-31">    qs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.stack(qs, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb14-32"></span>
<span id="cb14-33">    ts, thetas, theta_dots, energies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pendulum_observables_from_records(</span>
<span id="cb14-34">        pendulum,</span>
<span id="cb14-35">        recorder,</span>
<span id="cb14-36">        h,</span>
<span id="cb14-37">    )</span>
<span id="cb14-38"></span>
<span id="cb14-39">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Final Theta:"</span>, thetas[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb14-40">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Energy stats:"</span>)</span>
<span id="cb14-41">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"  min:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(energies))</span>
<span id="cb14-42">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"  max:"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(energies))</span>
<span id="cb14-43">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"  drift:"</span>, energies[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> energies[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb14-44"></span>
<span id="cb14-45">    plot_theta(ts, thetas)</span>
<span id="cb14-46">    plot_energy(ts, energies)</span>
<span id="cb14-47">    plot_phase(thetas, theta_dots)</span>
<span id="cb14-48">    plt.show()</span></code></pre></div>
</div>
<p>With 10000 steps, we can see the final angle, and compute the min and max energies seen over the course of the simulation:</p>
<pre><code>Final Theta: 0.17636912077013364
Energy stats:
  min: 2.970827133701791
  max: 2.979793319236134
  drift: 0.0020382257398114945</code></pre>
<p>This looks pretty good. Enery is pretty conserved (out to the thousandths place). Furthermore, if I increase or decrease the step number, the error seems to increase/decrease roughly linearly, which is a good sign.</p>
<p>Here’s some visualizations of what the key metrics look like over time:</p>
<p><img src="https://demonstrandom.com/game_theory/posts/discrete_controls_lagrange/Pendulum_Figure_1.png" class="img-fluid" style="width:55.0%" alt="Figure 1, Pendulum over Time"></p>
<p><img src="https://demonstrandom.com/game_theory/posts/discrete_controls_lagrange/Energy_Over_Time_Figure_2.png" class="img-fluid" style="width:55.0%" alt="Figure 2, Energy over Time"></p>
<p><img src="https://demonstrandom.com/game_theory/posts/discrete_controls_lagrange/Phase_Portrait_Figure_3.png" class="img-fluid" style="width:55.0%" alt="Figure 3, Phase Portrait"></p>
<p>We see the pendulum rotates through the angles cyclically, and the energy is (roughly) conserved. So the code passes the basic sanity test.</p>
</section>
<section id="adding-lie-groups" class="level3">
<h3 class="anchored" data-anchor-id="adding-lie-groups">Adding Lie Groups</h3>
<p>The implementation above doesn’t actually use our Lie Group formulation. We should hook in our Lie Group library<sup>13</sup>.</p>
<p>We replace our discrete lagrangian code above with the following:</p>
<div id="bae2ab87" class="cell" data-execution_count="15">
<div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> discrete_lagrangian(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, qk: torch.Tensor, qk1: torch.Tensor, h: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb16-2"></span>
<span id="cb16-3">        mid_coords <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb16-4">        vel_coords <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb16-5"></span>
<span id="cb16-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, (group, sl) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.model.layout.items():</span>
<span id="cb16-7">            qk_i  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> qk[sl]      </span>
<span id="cb16-8">            qk1_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> qk1[sl]</span>
<span id="cb16-9"></span>
<span id="cb16-10">            gk  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> group.exp(qk_i)</span>
<span id="cb16-11">            gk1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> group.exp(qk1_i)</span>
<span id="cb16-12"></span>
<span id="cb16-13">            omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> group.log(gk.inverse() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> gk1) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> h        </span>
<span id="cb16-14">            g_mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gk <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> group.exp(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> omega) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># equivalent of midpoint quadrature </span></span>
<span id="cb16-15"></span>
<span id="cb16-16">            mid_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> group.log(g_mid)   </span>
<span id="cb16-17">            vel_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> omega             </span>
<span id="cb16-18"></span>
<span id="cb16-19">            mid_coords.append(mid_i)</span>
<span id="cb16-20">            vel_coords.append(vel_i)</span>
<span id="cb16-21"></span>
<span id="cb16-22">        mid <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat(mid_coords, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb16-23">        vel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat(vel_coords, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb16-24"></span>
<span id="cb16-25">        ctrl  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.model.unpack(mid)   </span>
<span id="cb16-26">        dctrl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.model.unpack(vel)   </span>
<span id="cb16-27"></span>
<span id="cb16-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lagrangian(ctrl, dctrl)</span></code></pre></div>
</div>
<p>In the purely Euclidean case, with <img src="https://latex.codecogs.com/png.latex?q_k,%20q_%7Bk+1%7D%20%5Cin%20%5Cmathbb%7BR%7D%5En">, the discrete Lagrangian I used is just the midpoint rule applied to the action integral. That is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL_d(q_k,%20q_%7Bk+1%7D;%20h)%0A:=%20h%5C,L%5C!%5Cleft(q_%7Bk+%5Ctfrac12%7D,%20%5C,%20v_k%5Cright)%0A"></p>
<p>where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aq_%7Bk+%5Ctfrac12%7D%20:=%20%5Cfrac%7B1%7D%7B2%7D(q_k%20+%20q_%7Bk+1%7D),%0A%5Cqquad%0Av_k%20:=%20%5Cfrac%7Bq_%7Bk+1%7D%20-%20q_k%7D%7Bh%7D%0A"></p>
<p>So the discrete action becomes</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathfrak%7BG%7D_d(q_0,%5Cdots,q_N)%0A=%20%5Csum_%7Bk=0%7D%5E%7BN-1%7D%20L_d(q_k,%20q_%7Bk+1%7D;%20h)%0A=%20%5Csum_%7Bk=0%7D%5E%7BN-1%7D%20h%5C,L%5C!%5Cleft(%0A%5Cfrac%7Bq_k%20+%20q_%7Bk+1%7D%7D%7B2%7D,%5C,%0A%5Cfrac%7Bq_%7Bk+1%7D%20-%20q_k%7D%7Bh%7D%0A%5Cright)%0A"></p>
<p>On a Lie group <img src="https://latex.codecogs.com/png.latex?G"> (or a product of Lie groups), the “midpoint” and the “finite difference velocity” should be expressed in group and algebra coordinates.</p>
<p>Given local coordinates <img src="https://latex.codecogs.com/png.latex?q_k,%20q_%7Bk+1%7D"> in the Lie algebra <img src="https://latex.codecogs.com/png.latex?%5Cmathfrak%7Bg%7D">, we interpret them as group elements via</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag_k%20:=%20%5Cexp(q_k),%20%5Cqquad%0Ag_%7Bk+1%7D%20:=%20%5Cexp(q_%7Bk+1%7D)%20%5Cin%20G%0A"></p>
<p>The group-relative increment from <img src="https://latex.codecogs.com/png.latex?g_k"> to <img src="https://latex.codecogs.com/png.latex?g_%7Bk+1%7D"> is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CDelta%20g_k%20:=%20g_k%5E%7B-1%7D%20g_%7Bk+1%7D%0A"></p>
<p>and its corresponding algebra element is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Comega_k%20:=%20%5Cfrac%7B1%7D%7Bh%7D%5C,%5Clog(%5CDelta%20g_k)%0A=%20%5Cfrac%7B1%7D%7Bh%7D%5C,%5Clog%5Cbigl(g_k%5E%7B-1%7D%20g_%7Bk+1%7D%5Cbigr)%0A%5C;%5Cin%5C;%20%5Cmathfrak%7Bg%7D%0A"></p>
<p>which plays the role of a discrete velocity.</p>
<p>A natural “midpoint” configuration is obtained by “flowing” halfway along this group velocity:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ag_%7Bk+%5Ctfrac12%7D%20:=%20g_k%20%5Cexp%5C!%5CBigl(%5Ctfrac12%20h%5C,%5Comega_k%5CBigr)%0A"></p>
<p>To feed this back into the Lagrangian <img src="https://latex.codecogs.com/png.latex?%5Cell%20:%20%5Cmathfrak%7Bg%7D%20%5Cto%20%5Cmathbb%7BR%7D"> written in algebra coordinates, we take</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aq_%7Bk+%5Ctfrac12%7D%20:=%20%5Clog(g_%7Bk+%5Ctfrac12%7D),%20%5Cqquad%0Av_k%20:=%20%5Comega_k%0A"></p>
<p>So the Lie-group discrete Lagrangian is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AL_d(q_k,%20q_%7Bk+1%7D;%20h)%0A:=%20h%5C,%5Cell%5C!%5Cleft(q_%7Bk+%5Ctfrac12%7D,%5C,%20v_k%5Cright)%0A=%20h%5C,%5Cell%5C!%5CBigl(%0A%5Clog%5C!%5Cbigl(g_k%20%5Cexp(%5Ctfrac12%20h%20%5Comega_k)%5Cbigr),%5C,%0A%5Comega_k%0A%5CBigr)%0A"></p>
<p>with <img src="https://latex.codecogs.com/png.latex?g_k%20=%20%5Cexp(q_k)"> and <img src="https://latex.codecogs.com/png.latex?%5Comega_k%20=%20%5Ctfrac1h%20%5Clog(g_k%5E%7B-1%7D%20g_%7Bk+1%7D)">.</p>
<p>When <img src="https://latex.codecogs.com/png.latex?G%20=%20%5Cmathbb%7BR%7D%5En"> with its additive Lie group structure (so <img src="https://latex.codecogs.com/png.latex?%5Cexp"> and <img src="https://latex.codecogs.com/png.latex?%5Clog"> are both the identity), this reduces exactly to the original midpoint formula above.</p>
<p>If we run the code we see:</p>
<pre><code>Final Theta: 0.7583324173185618
Energy stats:
  min: 2.4167871253032307
  max: 2.975317957405105
  drift: -0.1006878866394163</code></pre>
<p>Error is much worse. I suspect this is due to the <code>_log_impl</code> function in the <code>SOn</code> class (which is only a first-order approximation). This took some debugging to get to this point. There could easily be other issues, but I’m content to move on and look again only if I end up needing this library in the future.</p>
<p>Another issue is that the Lie Group wrappers really slow down the code. If we need to return to Lie Groups and the performance becomes a problem, I will optimize.</p>
</section>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>In this post, we built up some foundational understanding of geometric controls and Lie Groups/Algebras. We did this “geometrically” - our intuition is not derived from physics per se but from the relevant geometric abstractions.</p>
<p>I have a few other posts to complete before I come back to this topic, but I want to continue to pursue this line of reasoning in the upcoming year. This may include <img src="https://latex.codecogs.com/png.latex?n">-plectic control, Noetherian theorems, approximate Lagrangians, Morse theory, differential cohomology, etc. I’ll also hopefully begin to look at other systems from the geometric viewpoint (like thermodynamics). This is mostly based on a hunch that we can develop more intuitive and general abstractions for controls based on geometric principles, and that we can use these tools to gain some deeper physics insights into how agents function theoretically.</p>
<p>Additionally, I think that there’s low-hanging fruit in our discrete Lagrangian solver. There are three views we can use, that should all be equivalent: the Lagrangian (global optimization) view, the Hamiltonian (local policy) view, and the “black-box simulator” view. We should be able to build a minimal library that allows us to define a problem, develop a unified, abstract geometric representation, and then translate between the different views. There’s also something interesting about how we have dual views around the variables to be controlled and their gradients (perhaps there is something interesting we can do with autograd?). Hopefully we will also develop this further.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>This brings us to the realm of differential geometry. I will attempt to explain and derive these principles purely geometrically, without any reference to physics.↩︎</p></li>
<li id="fn2"><p>We may be able to get away with less structure here than a Riemannian metric, probably just <img src="https://latex.codecogs.com/png.latex?C%5E2"> with invertible Hessian and maybe positive definite (for the Hamiltonian). But let’s assume the full metric structure for now.↩︎</p></li>
<li id="fn3"><p>It’s interesting to consider what would happen if the Hamiltonian WAS multi-valued, or if we only had “approximate inverses” for some reason… maybe more on this in a future post (if I can figure it out).↩︎</p></li>
<li id="fn4"><p>For a general Lagrangian, the Legendre map is controlled by the Hessian of <img src="https://latex.codecogs.com/png.latex?L"> in <img src="https://latex.codecogs.com/png.latex?v">; the Riemannian metric is a special case where this Hessian comes from <img src="https://latex.codecogs.com/png.latex?g_q"> (as in mechanical systems).↩︎</p></li>
<li id="fn5"><p>This seems arbitrary. Geometrically, this is like assuming the initial reference frame inside the robot is arbitrary - that is, modulo the initial position, the paths followed are “the same”. You can also assume right-invariance (which assumes the “outside observers” reference frame doesn’t matter). This is basically dual to the left-invariance case, the same but from the observers point of view. It might come up in tracking or estimation (controlling how you view a robot). If you assume <em>both</em> (bi-invariance), then neither position nor direction matter. This is like a assuming a perfectly round sphere in free space floatig in a featureless fog. If we don’t assume <em>either</em> invariance, then the Lagrangian depends on position, not just velocity. There is something external breaking the symmetry.↩︎</p></li>
<li id="fn6"><p>Differentiating in this way makes the implicit assumption that we have matrix Lie Groups. If we don’t make that assumption the upshot is we get a slightly more general formula for the Lie bracket (later). <img src="https://latex.codecogs.com/png.latex?%5B%5Comega,%20%5Ceta%5D%20:=%20%5Ctext%7Bad%7D_%7B%5Comega%7D%5Ceta">.↩︎</p></li>
<li id="fn7"><p>The derivative of a smooth function <img src="https://latex.codecogs.com/png.latex?%5Cell:%20%5Cmathfrak%7Bg%7D%20%5Cto%20%5Cmathbb%7BR%7D"> at a point <img src="https://latex.codecogs.com/png.latex?%5Comega"> is a linear map <img src="https://latex.codecogs.com/png.latex?d%5Cell_%5Comega:%20%5Cmathfrak%7Bg%7D%20%5Cto%20%5Cmathbb%7BR%7D"> — that is, a covector. So <img src="https://latex.codecogs.com/png.latex?%5Cmu%20=%20%5Cfrac%7B%5Cpartial%20%5Cell%7D%7B%5Cpartial%20%5Comega%7D"> naturally lives in the dual space <img src="https://latex.codecogs.com/png.latex?%5Cmathfrak%7Bg%7D%5E*">.↩︎</p></li>
<li id="fn8"><p>Sign conventions in the literature sometimes differ by a minus sign. The adjoint map <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bad%7D_%5Comega:%20%5Cmathfrak%7Bg%7D%20%5Cto%20%5Cmathfrak%7Bg%7D"> is defined by <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bad%7D_%5Comega(%5Ceta)%20:=%20%5B%5Comega,%5Ceta%5D">. The coadjoint map <img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7Bad%7D_%5Comega%5E*:%20%5Cmathfrak%7Bg%7D%5E*%20%5Cto%20%5Cmathfrak%7Bg%7D%5E*"> is its dual (transpose) in the linear-algebra sense: for every <img src="https://latex.codecogs.com/png.latex?%5Cmu%20%5Cin%20%5Cmathfrak%7Bg%7D%5E*"> and <img src="https://latex.codecogs.com/png.latex?%5Ceta%20%5Cin%20%5Cmathfrak%7Bg%7D">, <img src="https://latex.codecogs.com/png.latex?(%5Coperatorname%7Bad%7D_%5Comega%5E*%20%5Cmu)(%5Ceta)%20=%20%5Cmu(%5B%5Comega,%5Ceta%5D)."> This is exactly what we used when we rewrote the term <img src="https://latex.codecogs.com/png.latex?%5Cmu(%5B%5Comega,%5Ceta%5D)"> as <img src="https://latex.codecogs.com/png.latex?(%5Coperatorname%7Bad%7D_%5Comega%5E*%5Cmu)(%5Ceta)"> in the variation.↩︎</p></li>
<li id="fn9"><p>Assumes <img src="https://latex.codecogs.com/png.latex?%5Comega"> is constant in time.↩︎</p></li>
<li id="fn10"><p>It seems like the reason is that the velocity is already encoded in the differences between positions. This may merit more thought, especially if we progress to “higher-dimensional” Lagrangians, as I may do in a future post. I did not rederive the equation for the discrete Lagrangian for this post.↩︎</p></li>
<li id="fn11"><p>The signs seem to be flipped because in the discrete setting, we are varying the points themselves, which does not require integration by parts.↩︎</p></li>
<li id="fn12"><p>manif cites <a href="https://arxiv.org/abs/1812.01537">this paper</a> in particular.↩︎</p></li>
<li id="fn13"><p>One important note: PyTorch <a href="https://stackoverflow.com/questions/73288332/is-there-a-way-to-compute-the-matrix-logarithm-of-a-pytorch-tensor">doesn’t seem to offer matrix log</a>, which led to a lot of weirdness around the <img src="https://latex.codecogs.com/png.latex?SO_n"> implementation, that I didn’t want to focus too hard on, as it isn’t the focus of the post. Be careful with this code!↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Geometric Controls</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/game_theory/posts/discrete_controls_lagrange/</guid>
  <pubDate>Sat, 15 Nov 2025 05:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/game_theory/posts/discrete_controls_lagrange/liubov_popova-space_force_construction-1921-trivium-art-history.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Differential Games and Stag Hunt</title>
  <link>https://demonstrandom.com/game_theory/posts/differential_stag_hunt/</link>
  <description><![CDATA[ 





<p><a href="https://harvardartmuseums.org/collections/object/227564"><img src="https://demonstrandom.com/game_theory/posts/differential_stag_hunt/Hot_Pursuit_by_Paul_Klee_1939.jpg" class="img-fluid" style="width:75.0%" alt="Paul Klee, Hot Pursuit, 1939"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>In the last <a href="../../../game_theory/posts/canonical_games/index.html">two</a> <a href="../../../game_theory/posts/gradient_learning_nash/index.html">posts</a> in this series we looked at games as static functions with discrete strategies. That is, each player picked a strategy <img src="https://latex.codecogs.com/png.latex?s_i%20%5Cin%20S_i"> and at the end of the game the payoffs for each player were assessed as <img src="https://latex.codecogs.com/png.latex?u_i(s_1,...,s_n)">.</p>
<p>Some games progress not across discrete strategies, but rather across continuous strategies (or strategies continuous in space and time). These games are called “differential games”. In this post, I attempt to construct a continuous version of <a href="../../../game_theory/posts/gradient_learning_nash/index.html#example">stag hunt</a>, and a general framework for simulating differential games. The intent here is to enable in-depth exploration of multi-agent control in subsequent posts.</p>
</section>
<section id="differential-games" class="level1">
<h1>Differential Games</h1>
<p>We can think of differential games as an extension of both control theory and game theory, which both in turn extend discrete controls. In a typical Markov decision process/discrete control problem, we use techniques like dynamic programming to help determine the optimal policy for a single agent over a discrete state/action space. Game theory extends discrete control to <img src="https://latex.codecogs.com/png.latex?n">-agents, while classical control theory extends discrete control to a continuous state/action space. Differential games has <img src="https://latex.codecogs.com/png.latex?n">-agents operating in a continuous state/action space.</p>
<svg width="600" height="380" viewbox="0 0 1200 760" xmlns="http://www.w3.org/2000/svg" aria-label="Commutative square: discrete→continuous and single→n agents">
  <defs>
    <marker id="arrow" viewbox="0 0 10 10" refx="9" refy="5" markerwidth="10" markerheight="10" orient="auto-start-reverse">
      <path d="M 0 0 L 10 5 L 0 10 z" fill="#343a40"></path>
    </marker>
    <style>
      text { font-family: inherit; }
      .box { fill:#ffffff; stroke:#343a40; stroke-width:3; rx:14; }
      .title { font-weight:700; font-size:28px; fill:#212529; }
      .sub   { font-size:18px; fill:#6c757d; }
      .edge  { stroke:#343a40; stroke-width:3; fill:none; marker-end:url(#arrow); }
      .lab   { font-size:20px; fill:#212529; }
    </style>
  </defs>

  <!-- Boxes -->
  <rect class="box" x="100" y="80" width="420" height="170"></rect>
  <rect class="box" x="680" y="80" width="420" height="170"></rect>
  <rect class="box" x="100" y="460" width="420" height="170"></rect>
  <rect class="box" x="680" y="460" width="420" height="170"></rect>

  <!-- TL: Discrete Control / MDP -->
  <text class="title" x="310" y="130" text-anchor="middle">Discrete Control / MDP</text>
  <text class="sub" x="310" y="170" text-anchor="middle">single agent</text>
  <text class="sub" x="310" y="200" text-anchor="middle">discrete state/action</text>

  <!-- TR: Game Theory -->
  <text class="title" x="890" y="130" text-anchor="middle">Game Theory</text>
  <text class="sub" x="890" y="170" text-anchor="middle">n agents</text>
  <text class="sub" x="890" y="200" text-anchor="middle">discrete state/action</text>

  <!-- BL: Control Theory -->
  <text class="title" x="310" y="510" text-anchor="middle">Control Theory</text>
  <text class="sub" x="310" y="550" text-anchor="middle">single agent</text>
  <text class="sub" x="310" y="580" text-anchor="middle">continuous state/action</text>

  <!-- BR: Differential Games -->
  <text class="title" x="890" y="510" text-anchor="middle">Differential Games</text>
  <text class="sub" x="890" y="550" text-anchor="middle">n agents</text>
  <text class="sub" x="890" y="580" text-anchor="middle">continuous state/action</text>

  <!-- Edges -->
  <line class="edge" x1="520" y1="165" x2="680" y2="165"></line>
  <text class="lab" x="600" y="145" text-anchor="middle">n agents</text>

  <line class="edge" x1="310" y1="250" x2="310" y2="460"></line>
  <text class="lab" x="285" y="360" transform="rotate(-90 285 360)" text-anchor="middle">continuous space</text>

  <line class="edge" x1="890" y1="250" x2="890" y2="460"></line>
  <text class="lab" x="915" y="360" transform="rotate(-90 915 360)" text-anchor="middle">continuous space</text>

  <line class="edge" x1="520" y1="545" x2="680" y2="545"></line>
  <text class="lab" x="600" y="525" text-anchor="middle">n agents</text>
</svg>
<p>More formally, in a differential game, each player controls a control input <img src="https://latex.codecogs.com/png.latex?u_i(t)">, and the state of the world <img src="https://latex.codecogs.com/png.latex?x(t)"> evolves according to a differential equation</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%7Bx%7D(t)%20=%20f(x(t),%20u_1(t),...,%20u_n(t))%0A"></p>
<p>Each agent receives some payoff <img src="https://latex.codecogs.com/png.latex?J_i"> that is a combination of a trajectory factor (an instantaneous loss function <img src="https://latex.codecogs.com/png.latex?L_i"> integrated over time) and a terminal factor <img src="https://latex.codecogs.com/png.latex?g_i">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AJ_i%20=%20%5Cint_0%5ET%20L_i(x(t),%20u_1(t),%20...,%20u_n(t))%20dt%20+%20g_i(x(T))%0A"></p>
<p>We can also think of differential games as continuous-time differentiable programs. Each player’s policy <img src="https://latex.codecogs.com/png.latex?%5Cpi_i(o_i(t))"> is a differentiable function of its observation, and the dynamics <img src="https://latex.codecogs.com/png.latex?f(x,u)"> act as a differentiable layer integrating the world forward alongside a natural loss function <img src="https://latex.codecogs.com/png.latex?L">. This perspective allows us to use modern machine learning tools to analyze equilibria in continuous environments.</p>
</section>
<section id="code-architecture" class="level1">
<h1>Code Architecture</h1>
<p>Let’s lay out the requirements to define differential games in Python. We will build up the software in layers. The main concern here is separating game definition from game execution. We will use a similar model to the one laid out in previous posts on classical game theory. First we will implement a “physics” layer, which will manage state. Then we will implement a “decision” layer, agents make choices within those physics<sup>1</sup>. Finally, we will have an <code>Arena</code> object that runs simulations and analyses on the outcomes.</p>
<p>Why do this? There are a few reasons:</p>
<ol type="1">
<li>State as immutable snapshots: We will bundle physical state, time, and payoffs as an immutable object. This enables branching, what-if analysis, and caching.</li>
<li>Pure simulation functions: Our <code>tick</code> and <code>simulate</code> functions are pure functions. This makes the simulator composable: you can pause mid-game, fork multiple futures, or replay with different policies. This is important for model-predictive control and counterfactual reasoning.</li>
<li>Differentiability everywhere: Since functions are pure, every component can support automatic differentiation. This will lets us backpropagate through entire trajectories to learn optimal policies via gradient descent.</li>
</ol>
<p>We’ll look at the code in the next section<sup>2</sup>.</p>
<section id="physics-layer" class="level2">
<h2 class="anchored" data-anchor-id="physics-layer">Physics Layer</h2>
<section id="state-spaces" class="level3">
<h3 class="anchored" data-anchor-id="state-spaces">State Spaces</h3>
<p>The first layer is the state space layer. We will keep the actual state specifications abstract so that they can apply to a multitude of different games. Each agent will have its own state, and the game may have a shared state as well.</p>
<p>The <code>StateSpace</code> object takes a list of agents and a shared state object and produce a single object maintaining the entire state, plus getters and setters for altering and retrieving different aspects of the state.</p>
<div id="92cf383b" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> StateSpec:</span>
<span id="cb1-3">    names: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># e.g., ['x', 'y', 'vx', 'vy']</span></span>
<span id="cb1-4">    </span>
<span id="cb1-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb1-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.names)</span>
<span id="cb1-7"></span>
<span id="cb1-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> StateSpace:</span>
<span id="cb1-9">    </span>
<span id="cb1-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, </span>
<span id="cb1-11">                 agents: List[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Agent'</span>],</span>
<span id="cb1-12">                 shared_spec: Optional[StateSpec] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb1-13"></span>
<span id="cb1-14">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agents</span>
<span id="cb1-15">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agent_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [a.name <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> agents]</span>
<span id="cb1-16">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agent_dims <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {a.name: a.state_spec.dim() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> agents}</span>
<span id="cb1-17">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.shared_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> shared_spec.dim() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> shared_spec <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-18">        </span>
<span id="cb1-19">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.slices, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._build_state_indexing()</span>
<span id="cb1-20">    </span>
<span id="cb1-21">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _build_state_indexing(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Tuple[Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>], <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]:</span>
<span id="cb1-22">        slices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb1-23">        offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-24">        </span>
<span id="cb1-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent_name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agent_names:</span>
<span id="cb1-26">            dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agent_dims[agent_name]</span>
<span id="cb1-27">            slices[agent_name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>(offset, offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dim)</span>
<span id="cb1-28">            offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> dim</span>
<span id="cb1-29">        </span>
<span id="cb1-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.shared_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb1-31">            slices[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'shared'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>(offset, offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.shared_dim)</span>
<span id="cb1-32">            offset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.shared_dim</span>
<span id="cb1-33">        </span>
<span id="cb1-34">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> slices, offset</span>
<span id="cb1-35">    </span>
<span id="cb1-36">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> zero(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb1-37">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.zeros(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dim)</span>
<span id="cb1-38">    </span>
<span id="cb1-39">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_state(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state: torch.Tensor, agent: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb1-40">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.slices[agent]]</span>
<span id="cb1-41">    </span>
<span id="cb1-42">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_shared(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb1-43">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.shared_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb1-44">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.slices[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'shared'</span>]]</span>
<span id="cb1-45">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.tensor([])</span>
<span id="cb1-46">    </span>
<span id="cb1-47">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> set_state(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state: torch.Tensor, agent: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, value: torch.Tensor):</span>
<span id="cb1-48">        state[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.slices[agent]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> value</span></code></pre></div>
</div>
<p>Lastly, we need the actual <code>GameState</code> object:</p>
<div id="683f8fd8" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<span id="cb2-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> GameState:</span>
<span id="cb2-3">    </span>
<span id="cb2-4">    physical_state: torch.Tensor </span>
<span id="cb2-5">    time: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span></span>
<span id="cb2-6">    cumulative_payoffs: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]</span>
<span id="cb2-7">    metadata: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, Any] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> field(default_factory<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>)</span>
<span id="cb2-8">    </span>
<span id="cb2-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> clone(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'GameState'</span>:</span>
<span id="cb2-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Deep copy for branching"""</span></span>
<span id="cb2-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> GameState(</span>
<span id="cb2-12">            physical_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.physical_state.clone(),</span>
<span id="cb2-13">            time<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.time,</span>
<span id="cb2-14">            cumulative_payoffs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.cumulative_payoffs.copy(),</span>
<span id="cb2-15">            metadata<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.metadata.copy()</span>
<span id="cb2-16">        )</span>
<span id="cb2-17">    </span>
<span id="cb2-18">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> with_state(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, new_physical_state: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'GameState'</span>:</span>
<span id="cb2-19">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Return new GameState with updated physical state"""</span></span>
<span id="cb2-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> GameState(</span>
<span id="cb2-21">            physical_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>new_physical_state,</span>
<span id="cb2-22">            time<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.time,</span>
<span id="cb2-23">            cumulative_payoffs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.cumulative_payoffs,</span>
<span id="cb2-24">            metadata<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.metadata</span>
<span id="cb2-25">        )</span>
<span id="cb2-26">    </span>
<span id="cb2-27">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> with_time(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, new_time: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'GameState'</span>:</span>
<span id="cb2-28">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Return new GameState with updated time"""</span></span>
<span id="cb2-29">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> GameState(</span>
<span id="cb2-30">            physical_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.physical_state,</span>
<span id="cb2-31">            time<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>new_time,</span>
<span id="cb2-32">            cumulative_payoffs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.cumulative_payoffs,</span>
<span id="cb2-33">            metadata<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.metadata</span>
<span id="cb2-34">        )</span>
<span id="cb2-35">    </span>
<span id="cb2-36">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> add_payoffs(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, step_payoffs: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'GameState'</span>:</span>
<span id="cb2-37">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Return new GameState with updated payoffs"""</span></span>
<span id="cb2-38">        new_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.cumulative_payoffs.copy()</span>
<span id="cb2-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent, reward <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> step_payoffs.items():</span>
<span id="cb2-40">            new_payoffs[agent] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> new_payoffs.get(agent, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> reward</span>
<span id="cb2-41">        </span>
<span id="cb2-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> GameState(</span>
<span id="cb2-43">            physical_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.physical_state,</span>
<span id="cb2-44">            time<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.time,</span>
<span id="cb2-45">            cumulative_payoffs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>new_payoffs,</span>
<span id="cb2-46">            metadata<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.metadata</span>
<span id="cb2-47">        )</span></code></pre></div>
</div>
<p>This is the immutable snapshot of the state at any given point in time.</p>
</section>
<section id="observations" class="level3">
<h3 class="anchored" data-anchor-id="observations">Observations</h3>
<p>Given the state, each agent will need to observe part of it (depending on the game). We define an <code>ObservationModel</code> to handle this.</p>
<div id="3e181355" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ObservationModel(ABC):</span>
<span id="cb3-2">    </span>
<span id="cb3-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb3-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> observe(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state: torch.Tensor, agent: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, cumulative_payoff: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb3-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb3-6">    </span>
<span id="cb3-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb3-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> obs_dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, agent: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb3-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span></code></pre></div>
</div>
</section>
<section id="dynamics" class="level3">
<h3 class="anchored" data-anchor-id="dynamics">Dynamics</h3>
<p>Next we have dynamics. The dynamics determine how the world actually evolves. Here’s the abstract interface.</p>
<div id="74c751ff" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Dynamics(ABC):</span>
<span id="cb4-2">    </span>
<span id="cb4-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb4-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> derivative(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, </span>
<span id="cb4-5">                   state: torch.Tensor,</span>
<span id="cb4-6">                   controls: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, torch.Tensor]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span></code></pre></div>
</div>
<p>We hand the dynamics a state and controls and it outputs the change in state<sup>3</sup>.</p>
</section>
<section id="constraints" class="level3">
<h3 class="anchored" data-anchor-id="constraints">Constraints</h3>
<p>Real systems have constraints: agents can’t leave the arena, they can’t pass through walls, they shouldn’t collide with each other. We handle constraints in two ways. The first is soft violations (<code>violated()</code>), a differentiable penalty that grows outside the feasible region, used during learning or optimization. The second is hard projection (<code>project()</code>), which pushes the state back onto the constraint surface and is used to enforce physics.</p>
<p>Here’s the abstract interface:</p>
<div id="ea0c104b" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Constraint(ABC):</span>
<span id="cb5-2">    </span>
<span id="cb5-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> violated(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-5">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb5-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Returns violation amount (0 = satisfied, &gt;0 = violated).</span></span>
<span id="cb5-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Must be differentiable</span></span>
<span id="cb5-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb5-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb5-10">    </span>
<span id="cb5-11">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb5-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> project(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-13">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Project state onto feasible set"""</span></span>
<span id="cb5-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span></code></pre></div>
</div>
<p>Here’s some examples:</p>
<div id="bb68679e" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> BoundaryConstraint(Constraint):</span>
<span id="cb6-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Box boundaries [x_min, x_max] × [y_min, y_max]"""</span></span>
<span id="cb6-3">    </span>
<span id="cb6-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state_space: StateSpace, bounds: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]]):</span>
<span id="cb6-5">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""bounds = {'x': (min, max), 'y': (min, max)}"""</span></span>
<span id="cb6-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_space</span>
<span id="cb6-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bounds</span>
<span id="cb6-8">    </span>
<span id="cb6-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> violated(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state):</span>
<span id="cb6-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Soft violation for differentiability</span></span>
<span id="cb6-11">        violation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb6-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.agent_names:</span>
<span id="cb6-13">            pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.get_state(state, agent)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb6-14">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Penalty grows quadratically outside bounds</span></span>
<span id="cb6-15">            violation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> torch.relu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-16">            violation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> torch.relu(pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-17">            violation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> torch.relu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-18">            violation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> torch.relu(pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> violation</span>
<span id="cb6-20">    </span>
<span id="cb6-21">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> project(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state):</span>
<span id="cb6-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Hard projection (like game engine collision resolution)"""</span></span>
<span id="cb6-23">        new_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state.clone()</span>
<span id="cb6-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.agent_names:</span>
<span id="cb6-25">            pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.get_state(state, agent)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb6-26">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Clamp position</span></span>
<span id="cb6-27">            pos_clamped <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.stack([</span>
<span id="cb6-28">                torch.clamp(pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]),</span>
<span id="cb6-29">                torch.clamp(pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb6-30">            ])</span>
<span id="cb6-31">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Reflect velocity if hit boundary</span></span>
<span id="cb6-32">            vel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.get_state(state, agent)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>]</span>
<span id="cb6-33">            vel_new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vel.clone()</span>
<span id="cb6-34">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]:</span>
<span id="cb6-35">                vel_new[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Bounce with damping</span></span>
<span id="cb6-36">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> pos[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bounds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]:</span>
<span id="cb6-37">                vel_new[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span></span>
<span id="cb6-38">            </span>
<span id="cb6-39">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.set_state(new_state, agent, </span>
<span id="cb6-40">                                torch.cat([pos_clamped, vel_new]))</span>
<span id="cb6-41">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> new_state</span>
<span id="cb6-42"></span>
<span id="cb6-43"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> CollisionConstraint(Constraint):</span>
<span id="cb6-44">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Agent-agent collision avoidance"""</span></span>
<span id="cb6-45">    </span>
<span id="cb6-46">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state_space, radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>):</span>
<span id="cb6-47">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_space</span>
<span id="cb6-48">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.radius <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> radius</span>
<span id="cb6-49">    </span>
<span id="cb6-50">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> violated(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state):</span>
<span id="cb6-51">        violation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb6-52">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, a1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.agent_names):</span>
<span id="cb6-53">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.agent_names[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]:</span>
<span id="cb6-54">                dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(</span>
<span id="cb6-55">                    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.get_state(state, a1)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> </span>
<span id="cb6-56">                    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.get_state(state, a2)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb6-57">                )</span>
<span id="cb6-58">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Soft barrier</span></span>
<span id="cb6-59">                violation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> torch.relu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.radius <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> dist)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-60">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> violation</span>
<span id="cb6-61">    </span>
<span id="cb6-62">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> project(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state):</span>
<span id="cb6-63">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Separate overlapping agents (like game engine)</span></span>
<span id="cb6-64">        new_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state.clone()</span>
<span id="cb6-65">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, a1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.agent_names):</span>
<span id="cb6-66">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.agent_names[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]:</span>
<span id="cb6-67">                p1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.get_state(state, a1)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb6-68">                p2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space.get_state(state, a2)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb6-69">                dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(p1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p2)</span>
<span id="cb6-70">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.radius:</span>
<span id="cb6-71">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Push apart</span></span>
<span id="cb6-72">                    direction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (p1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p2) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>)</span>
<span id="cb6-73">                    overlap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.radius <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> dist</span>
<span id="cb6-74">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Each moves half the overlap</span></span>
<span id="cb6-75">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (would need to update both states)</span></span>
<span id="cb6-76">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> new_state</span></code></pre></div>
</div>
</section>
<section id="payoffs" class="level3">
<h3 class="anchored" data-anchor-id="payoffs">Payoffs</h3>
<p>In differential games, payoffs typically have two components. The first is a running cost, associated with the trajectory, and the second is the terminal reward, assessed at the final stage.</p>
<div id="d4ddfd7f" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> PayoffModel(ABC):</span>
<span id="cb7-2">    </span>
<span id="cb7-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb7-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> agents(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]:</span>
<span id="cb7-5">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Return list of agent names"""</span></span>
<span id="cb7-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb7-7">    </span>
<span id="cb7-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> step(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, </span>
<span id="cb7-9">             state: torch.Tensor, </span>
<span id="cb7-10">             controls: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, torch.Tensor], </span>
<span id="cb7-11">             dt: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]:</span>
<span id="cb7-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Incremental payoff for this timestep (override for running costs)"""</span></span>
<span id="cb7-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {a: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents()}</span>
<span id="cb7-14">    </span>
<span id="cb7-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> terminal(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]:</span>
<span id="cb7-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Terminal payoff (override for end-of-game rewards)"""</span></span>
<span id="cb7-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {a: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents()}</span>
<span id="cb7-18">    </span>
<span id="cb7-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> total(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, </span>
<span id="cb7-20">              trajectory: List[Tuple[torch.Tensor, Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, torch.Tensor]]], </span>
<span id="cb7-21">              final_state: torch.Tensor,</span>
<span id="cb7-22">              dt: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]:</span>
<span id="cb7-23">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb7-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Total payoff over trajectory.</span></span>
<span id="cb7-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Default: sum step payoffs + terminal.</span></span>
<span id="cb7-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Override for discounting, non-additive payoffs, etc.</span></span>
<span id="cb7-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb7-28">        total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {a: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents()}</span>
<span id="cb7-29">        </span>
<span id="cb7-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> state, controls <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> trajectory:</span>
<span id="cb7-31">            step_payoff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.step(state, controls, dt)</span>
<span id="cb7-32">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents():</span>
<span id="cb7-33">                total[a] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> step_payoff[a]</span>
<span id="cb7-34">        </span>
<span id="cb7-35">        terminal_payoff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.terminal(final_state)</span>
<span id="cb7-36">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents():</span>
<span id="cb7-37">            total[a] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> terminal_payoff[a]</span>
<span id="cb7-38">        </span>
<span id="cb7-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> total</span></code></pre></div>
</div>
</section>
<section id="integration" class="level3">
<h3 class="anchored" data-anchor-id="integration">Integration</h3>
<p>To integrate the dynamics forward in time, we need a numerical integrator. The integrator takes the derivative from <code>Dynamics</code> and produces the next state. We support multiple schemes with different accuracy/speed tradeoffs:</p>
<div id="4776ca8a" class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Integrator(ABC):</span>
<span id="cb8-2">    </span>
<span id="cb8-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> step(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb8-4">             dynamics: Dynamics,</span>
<span id="cb8-5">             state: torch.Tensor,</span>
<span id="cb8-6">             controls: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, torch.Tensor],</span>
<span id="cb8-7">             dt: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>,</span>
<span id="cb8-8">             constraints: Optional[List[Constraint]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb8-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb8-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Integrate one timestep and project onto constraints.</span></span>
<span id="cb8-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        </span></span>
<span id="cb8-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Args:</span></span>
<span id="cb8-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            dynamics: dynamics model</span></span>
<span id="cb8-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            state: current state</span></span>
<span id="cb8-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            controls: control inputs</span></span>
<span id="cb8-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            dt: timestep</span></span>
<span id="cb8-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            constraints: optional list of constraints to enforce</span></span>
<span id="cb8-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            </span></span>
<span id="cb8-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Returns:</span></span>
<span id="cb8-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            new_state (after constraint projection if provided)</span></span>
<span id="cb8-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb8-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Integration</span></span>
<span id="cb8-23">        new_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._integrate(dynamics, state, controls, dt)</span>
<span id="cb8-24">        </span>
<span id="cb8-25">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Constraint projection (automatic if constraints provided)</span></span>
<span id="cb8-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> constraints:</span>
<span id="cb8-27">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> constraint <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> constraints:</span>
<span id="cb8-28">                new_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> constraint.project(new_state)</span>
<span id="cb8-29">        </span>
<span id="cb8-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> new_state</span>
<span id="cb8-31">    </span>
<span id="cb8-32">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb8-33">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _integrate(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb8-34">                   dynamics: Dynamics,</span>
<span id="cb8-35">                   state: torch.Tensor,</span>
<span id="cb8-36">                   controls: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, torch.Tensor],</span>
<span id="cb8-37">                   dt: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb8-38">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Actual integration scheme (implemented by subclasses)"""</span></span>
<span id="cb8-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb8-40"></span>
<span id="cb8-41"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> EulerIntegrator(Integrator):</span>
<span id="cb8-42">    </span>
<span id="cb8-43">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _integrate(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, dynamics, state, controls, dt):</span>
<span id="cb8-44">        dstate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dynamics.derivative(state, controls)</span>
<span id="cb8-45">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dstate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dt</span>
<span id="cb8-46"></span>
<span id="cb8-47"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> RK4Integrator(Integrator):</span>
<span id="cb8-48">    </span>
<span id="cb8-49">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _integrate(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, dynamics, state, controls, dt):</span>
<span id="cb8-50">        k1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dynamics.derivative(state, controls)</span>
<span id="cb8-51">        k2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dynamics.derivative(state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k1, controls)</span>
<span id="cb8-52">        k3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dynamics.derivative(state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k2, controls)</span>
<span id="cb8-53">        k4 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dynamics.derivative(state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k3, controls)</span>
<span id="cb8-54">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (k1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> k4)</span></code></pre></div>
</div>
</section>
</section>
<section id="decision-layer" class="level2">
<h2 class="anchored" data-anchor-id="decision-layer">Decision Layer</h2>
<p>The next layer of code determines how agents actually behave.</p>
<section id="policy" class="level3">
<h3 class="anchored" data-anchor-id="policy">Policy</h3>
<p>A policy maps observations to control actions: <img src="https://latex.codecogs.com/png.latex?u%20=%20%5Cpi(o)">.</p>
<div id="ee16bc36" class="cell" data-execution_count="9">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Policy(ABC):</span>
<span id="cb9-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Observation to control"""</span></span>
<span id="cb9-3">    </span>
<span id="cb9-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb9-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, obs: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb9-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Must be differentiable for learning"""</span></span>
<span id="cb9-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb9-8">    </span>
<span id="cb9-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb9-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> control_dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb9-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span></code></pre></div>
</div>
<p>There are two kinds of policies: learnable ( use neural networks with parameters we can optimize via gradient descent) and hand-crafted (regular Python functions, useful for baselines, testing, etc).</p>
<div id="4d59fb53" class="cell" data-execution_count="10">
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> NeuralPolicy(Policy, nn.Module):</span>
<span id="cb10-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Learnable policy"""</span></span>
<span id="cb10-3">    </span>
<span id="cb10-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, obs_dim: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, control_dim: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, hidden_dim: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>):</span>
<span id="cb10-5">        Policy.<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>)</span>
<span id="cb10-6">        nn.Module.<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>)</span>
<span id="cb10-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._control_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> control_dim</span>
<span id="cb10-8">        </span>
<span id="cb10-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.net <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Sequential(</span>
<span id="cb10-10">            nn.Linear(obs_dim, hidden_dim),</span>
<span id="cb10-11">            nn.Tanh(),</span>
<span id="cb10-12">            nn.Linear(hidden_dim, hidden_dim),</span>
<span id="cb10-13">            nn.Tanh(),</span>
<span id="cb10-14">            nn.Linear(hidden_dim, control_dim),</span>
<span id="cb10-15">            nn.Tanh(),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Bounded output</span></span>
<span id="cb10-16">        )</span>
<span id="cb10-17">        </span>
<span id="cb10-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Small init for stability</span></span>
<span id="cb10-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.net.modules():</span>
<span id="cb10-20">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(m, nn.Linear):</span>
<span id="cb10-21">                nn.init.orthogonal_(m.weight, gain<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>)</span>
<span id="cb10-22">                nn.init.zeros_(m.bias)</span>
<span id="cb10-23">    </span>
<span id="cb10-24">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, obs: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb10-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> nn.Module.<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, obs)</span>
<span id="cb10-26">    </span>
<span id="cb10-27">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, obs: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb10-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.net(obs)</span>
<span id="cb10-29">    </span>
<span id="cb10-30">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> control_dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb10-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._control_dim</span>
<span id="cb10-32"></span>
<span id="cb10-33"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> FunctionPolicy(Policy):</span>
<span id="cb10-34">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Hand-coded policy (can be differentiable or not)"""</span></span>
<span id="cb10-35">    </span>
<span id="cb10-36">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, </span>
<span id="cb10-37">                 fn: Callable[[torch.Tensor], torch.Tensor],</span>
<span id="cb10-38">                 control_dim: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>,</span>
<span id="cb10-39">                 differentiable: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>):</span>
<span id="cb10-40">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.fn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fn</span>
<span id="cb10-41">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._control_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> control_dim</span>
<span id="cb10-42">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.differentiable <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> differentiable</span>
<span id="cb10-43">    </span>
<span id="cb10-44">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, obs: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb10-45">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.differentiable:</span>
<span id="cb10-46">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.fn(obs)</span>
<span id="cb10-47">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb10-48">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> torch.no_grad():</span>
<span id="cb10-49">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.fn(obs)</span>
<span id="cb10-50">    </span>
<span id="cb10-51">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> control_dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb10-52">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._control_dim</span></code></pre></div>
</div>
</section>
<section id="agents" class="level3">
<h3 class="anchored" data-anchor-id="agents">Agents</h3>
<p>An <code>Agent</code> bundles together a state specification (what variables it tracks) and a set of named strategies (the policies it can choose from). This bridges the gap between continuous control (policies) and discrete game theory (strategy names like “Cooperate” or “Defect”).</p>
<div id="320ceef1" class="cell" data-execution_count="11">
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Agent:</span>
<span id="cb11-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Agent with state specification and named strategies"""</span></span>
<span id="cb11-3">    </span>
<span id="cb11-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb11-5">                 name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>,</span>
<span id="cb11-6">                 state_spec: StateSpec,</span>
<span id="cb11-7">                 strategy_set: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, Policy]):</span>
<span id="cb11-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb11-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Args:</span></span>
<span id="cb11-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            name: agent identifier</span></span>
<span id="cb11-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            state_spec: symbolic state specification</span></span>
<span id="cb11-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            strategy_set: dict of strategy_name -&gt; Policy</span></span>
<span id="cb11-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb11-14">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> name</span>
<span id="cb11-15">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_spec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_spec</span>
<span id="cb11-16">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.strategy_set <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> strategy_set</span>
<span id="cb11-17">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.strategy_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(strategy_set.keys())</span>
<span id="cb11-18">    </span>
<span id="cb11-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_policy(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, strategy_name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Policy:</span>
<span id="cb11-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.strategy_set[strategy_name]</span></code></pre></div>
</div>
</section>
<section id="game-definitions" class="level3">
<h3 class="anchored" data-anchor-id="game-definitions">Game Definitions</h3>
<p>Here’s the actual definition of our differential game. We need a <code>StateSpace</code>, a list of agents (with observation models, dynamics, and payoff models), and an initial sampler.</p>
<div id="649f8e42" class="cell" data-execution_count="12">
<div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> DifferentialGame:</span>
<span id="cb12-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb12-3">                 state_space: StateSpace,</span>
<span id="cb12-4">                 agents: List[Agent],</span>
<span id="cb12-5">                 obs_model: ObservationModel,</span>
<span id="cb12-6">                 dynamics: Dynamics,</span>
<span id="cb12-7">                 payoff_model: PayoffModel,</span>
<span id="cb12-8">                 initial_sampler: Callable[[], torch.Tensor],</span>
<span id="cb12-9">                 name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Differential Game"</span>):</span>
<span id="cb12-10">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_space</span>
<span id="cb12-11">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {a.name: a <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> agents}</span>
<span id="cb12-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agent_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [a.name <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> agents]</span>
<span id="cb12-13">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.obs_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> obs_model</span>
<span id="cb12-14">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dynamics <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dynamics</span>
<span id="cb12-15">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoff_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoff_model</span>
<span id="cb12-16">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.initial_sampler <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> initial_sampler</span>
<span id="cb12-17">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> name</span>
<span id="cb12-18">    </span>
<span id="cb12-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_strategy_sets(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]]:</span>
<span id="cb12-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Get available strategies per agent"""</span></span>
<span id="cb12-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {name: agent.strategy_names <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents.items()}</span>
<span id="cb12-22">    </span>
<span id="cb12-23">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb12-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'&lt;DifferentialGame "</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">" agents=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>agent_names<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">&gt;'</span></span></code></pre></div>
</div>
</section>
</section>
<section id="game-execution-layer" class="level2">
<h2 class="anchored" data-anchor-id="game-execution-layer">Game Execution Layer</h2>
<section id="arena" class="level3">
<h3 class="anchored" data-anchor-id="arena">Arena</h3>
<p>The <code>Arena</code> object executes games. While <code>DifferentialGame</code> defines the rules, <code>Arena</code> actually simulates trajectories. This separation means you can define a game once, then run it with different:</p>
<ul>
<li>Strategy profiles (cooperate vs defect)</li>
<li>Initial conditions (different starting positions)</li>
<li>Integration methods (Euler vs RK4)</li>
<li>Time horizons (short sprints vs long chases)</li>
</ul>
<p>The core method is <code>play()</code>, which takes a strategy profile (mapping each agent to a strategy name) and returns the full trajectory plus final payoffs.</p>
<div id="2c0b8c71" class="cell" data-execution_count="13">
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Arena:</span>
<span id="cb13-2">    </span>
<span id="cb13-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb13-4">                 game: DifferentialGame,</span>
<span id="cb13-5">                 integrator: Integrator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>,</span>
<span id="cb13-6">                 dt: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>,</span>
<span id="cb13-7">                 max_time: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>):</span>
<span id="cb13-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> game</span>
<span id="cb13-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.integrator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> integrator <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> EulerIntegrator()</span>
<span id="cb13-10">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dt</span>
<span id="cb13-11">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.max_time <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> max_time</span>
<span id="cb13-12"></span>
<span id="cb13-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> initial_state(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, </span>
<span id="cb13-14">                      physical_state: Optional[torch.Tensor] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> GameState:</span>
<span id="cb13-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> physical_state <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb13-16">            physical_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.initial_sampler()</span>
<span id="cb13-17">        </span>
<span id="cb13-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> GameState(</span>
<span id="cb13-19">            physical_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>physical_state,</span>
<span id="cb13-20">            time<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>,</span>
<span id="cb13-21">            cumulative_payoffs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{agent: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.agent_names}</span>
<span id="cb13-22">        )</span>
<span id="cb13-23"></span>
<span id="cb13-24">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> tick(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, </span>
<span id="cb13-25">             state: GameState, </span>
<span id="cb13-26">             policies: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, Policy],</span>
<span id="cb13-27">             constraints: Optional[List[Constraint]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> GameState:</span>
<span id="cb13-28"></span>
<span id="cb13-29">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Observe</span></span>
<span id="cb13-30">        observations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb13-31">            agent: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.obs_model.observe(</span>
<span id="cb13-32">                state.physical_state, </span>
<span id="cb13-33">                agent,</span>
<span id="cb13-34">                state.cumulative_payoffs.get(agent, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb13-35">            )</span>
<span id="cb13-36">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.agent_names</span>
<span id="cb13-37">        }</span>
<span id="cb13-38">        </span>
<span id="cb13-39">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Act</span></span>
<span id="cb13-40">        controls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb13-41">            agent: policies[agent](observations[agent])</span>
<span id="cb13-42">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.agent_names</span>
<span id="cb13-43">        }</span>
<span id="cb13-44">        </span>
<span id="cb13-45">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute step payoffs (before state changes)</span></span>
<span id="cb13-46">        step_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.payoff_model.step(</span>
<span id="cb13-47">            state.physical_state, </span>
<span id="cb13-48">            controls, </span>
<span id="cb13-49">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dt</span>
<span id="cb13-50">        )</span>
<span id="cb13-51">        </span>
<span id="cb13-52">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Integrate physics</span></span>
<span id="cb13-53">        new_physical <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.integrator.step(</span>
<span id="cb13-54">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.dynamics,</span>
<span id="cb13-55">            state.physical_state,</span>
<span id="cb13-56">            controls,</span>
<span id="cb13-57">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dt,</span>
<span id="cb13-58">            constraints</span>
<span id="cb13-59">        )</span>
<span id="cb13-60">        </span>
<span id="cb13-61">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build new state</span></span>
<span id="cb13-62">        new_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (state</span>
<span id="cb13-63">                     .with_state(new_physical)</span>
<span id="cb13-64">                     .with_time(state.time <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dt)</span>
<span id="cb13-65">                     .add_payoffs(step_payoffs))</span>
<span id="cb13-66">        </span>
<span id="cb13-67">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> new_state</span>
<span id="cb13-68">    </span>
<span id="cb13-69">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> simulate(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb13-70">                 policies: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, Policy],</span>
<span id="cb13-71">                 initial: Optional[GameState] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>,</span>
<span id="cb13-72">                 until: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>,</span>
<span id="cb13-73">                 constraints: Optional[List[Constraint]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> List[GameState]:</span>
<span id="cb13-74">       </span>
<span id="cb13-75">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> initial <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb13-76">            initial <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.initial_state()</span>
<span id="cb13-77">        </span>
<span id="cb13-78">        end_time <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> until <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> until <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.max_time</span>
<span id="cb13-79">        </span>
<span id="cb13-80">        trajectory <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [initial]</span>
<span id="cb13-81">        state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> initial</span>
<span id="cb13-82">        </span>
<span id="cb13-83">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> state.time <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> end_time:</span>
<span id="cb13-84">            state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tick(state, policies, constraints)</span>
<span id="cb13-85">            trajectory.append(state)</span>
<span id="cb13-86">        </span>
<span id="cb13-87">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> trajectory</span>
<span id="cb13-88"></span>
<span id="cb13-89">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> play(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb13-90">             strategy_profile: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>],</span>
<span id="cb13-91">             initial: Optional[GameState] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>,</span>
<span id="cb13-92">             constraints: Optional[List[Constraint]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Tuple[List[GameState], Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]]:</span>
<span id="cb13-93"></span>
<span id="cb13-94">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Convert strategy names to policies</span></span>
<span id="cb13-95">        policies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb13-96">            agent: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.agents[agent].get_policy(strategy_profile[agent])</span>
<span id="cb13-97">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.agent_names</span>
<span id="cb13-98">        }</span>
<span id="cb13-99">        </span>
<span id="cb13-100">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Simulate</span></span>
<span id="cb13-101">        trajectory <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.simulate(policies, initial, constraints<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>constraints)</span>
<span id="cb13-102">        </span>
<span id="cb13-103">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add terminal payoffs</span></span>
<span id="cb13-104">        final_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> trajectory[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb13-105">        terminal_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.payoff_model.terminal(final_state.physical_state)</span>
<span id="cb13-106">        </span>
<span id="cb13-107">        total_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> final_state.cumulative_payoffs.copy()</span>
<span id="cb13-108">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent, reward <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> terminal_payoffs.items():</span>
<span id="cb13-109">            total_payoffs[agent] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> reward</span>
<span id="cb13-110">        </span>
<span id="cb13-111">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> trajectory, total_payoffs</span>
<span id="cb13-112">    </span>
<span id="cb13-113">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> expected_payoffs(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb13-114">                        strategy_profile: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>],</span>
<span id="cb13-115">                        n_samples: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]:</span>
<span id="cb13-116"></span>
<span id="cb13-117">        total_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {agent: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.agent_names}</span>
<span id="cb13-118">        </span>
<span id="cb13-119">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_samples):</span>
<span id="cb13-120">            _, payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.play(strategy_profile, initial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Random init each time</span></span>
<span id="cb13-121">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.agent_names:</span>
<span id="cb13-122">                total_payoffs[agent] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> payoffs[agent]</span>
<span id="cb13-123">        </span>
<span id="cb13-124">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {agent: total_payoffs[agent] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> n_samples <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.agent_names}</span></code></pre></div>
</div>
</section>
</section>
<section id="analysis" class="level2">
<h2 class="anchored" data-anchor-id="analysis">Analysis</h2>
<section id="converting-to-normal-form" class="level3">
<h3 class="anchored" data-anchor-id="converting-to-normal-form">Converting to Normal Form</h3>
<p>One of the unique features of this framework is the ability to convert continuous differential games back into discrete normal form. This lets us use all our existing game theory tools, like finding Nash equilibria, computing evolutionary dynamics, and visualizing payoff matrices.</p>
<div id="b2375cd9" class="cell" data-execution_count="14">
<div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> StrategyProfileIterator:</span>
<span id="cb14-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb14-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Iterate over all strategy profiles.</span></span>
<span id="cb14-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Essential for converting differential game to normal form.</span></span>
<span id="cb14-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb14-6">    </span>
<span id="cb14-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, strategy_sets: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]]):</span>
<span id="cb14-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb14-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        strategy_sets: dict of agent -&gt; list of strategy names</span></span>
<span id="cb14-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb14-11">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.strategy_sets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> strategy_sets</span>
<span id="cb14-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(strategy_sets.keys())</span>
<span id="cb14-13">    </span>
<span id="cb14-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__iter__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb14-15">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Yield all strategy profiles"""</span></span>
<span id="cb14-16">        strategy_lists <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.strategy_sets[agent] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents]</span>
<span id="cb14-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> profile_tuple <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> itertools.product(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>strategy_lists):</span>
<span id="cb14-18">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents, profile_tuple))</span>
<span id="cb14-19">    </span>
<span id="cb14-20">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> count(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb14-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Total number of profiles"""</span></span>
<span id="cb14-22">        count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb14-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> strategies <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.strategy_sets.values():</span>
<span id="cb14-24">            count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(strategies)</span>
<span id="cb14-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> count</span>
<span id="cb14-26"></span>
<span id="cb14-27"></span>
<span id="cb14-28"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> NormalFormConverter:</span>
<span id="cb14-29">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Convert differential game to normal form """</span></span>
<span id="cb14-30">    </span>
<span id="cb14-31">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb14-32">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> to_payoff_matrix(arena: Arena,</span>
<span id="cb14-33">                        players: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>],</span>
<span id="cb14-34">                        n_samples: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb14-35">        </span>
<span id="cb14-36">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get strategy sets for selected players</span></span>
<span id="cb14-37">        all_sets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> arena.game.get_strategy_sets()</span>
<span id="cb14-38">        strategy_sets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {p: all_sets[p] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> players}</span>
<span id="cb14-39">        </span>
<span id="cb14-40">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Other agents use first strategy by default</span></span>
<span id="cb14-41">        fixed_strategies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb14-42">            agent: all_sets[agent][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb14-43">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> arena.game.agent_names</span>
<span id="cb14-44">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> players</span>
<span id="cb14-45">        }</span>
<span id="cb14-46">        </span>
<span id="cb14-47">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build tensor shape</span></span>
<span id="cb14-48">        dims <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(strategy_sets[p]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> players]</span>
<span id="cb14-49">        payoff_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(players)] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dims</span>
<span id="cb14-50">        payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(payoff_shape)</span>
<span id="cb14-51">        </span>
<span id="cb14-52">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Iterate over all profiles</span></span>
<span id="cb14-53">        iterator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StrategyProfileIterator(strategy_sets)</span>
<span id="cb14-54">        </span>
<span id="cb14-55">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> profile <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> iterator:</span>
<span id="cb14-56">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Combine with fixed strategies</span></span>
<span id="cb14-57">            full_profile <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>fixed_strategies, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>profile}</span>
<span id="cb14-58">            </span>
<span id="cb14-59">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get indices for this profile</span></span>
<span id="cb14-60">            indices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(strategy_sets[p].index(profile[p]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> players)</span>
<span id="cb14-61">            </span>
<span id="cb14-62">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute expected payoff</span></span>
<span id="cb14-63">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> n_samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb14-64">                _, payoff_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> arena.play(full_profile, differentiable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb14-65">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb14-66">                payoff_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> arena.expected_payoffs(full_profile, n_samples)</span>
<span id="cb14-67">            </span>
<span id="cb14-68">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Store in tensor</span></span>
<span id="cb14-69">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, player <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(players):</span>
<span id="cb14-70">                payoffs[(i,) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> indices] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoff_dict[player]</span>
<span id="cb14-71">        </span>
<span id="cb14-72">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> payoffs</span></code></pre></div>
</div>
</section>
<section id="visualization" class="level3">
<h3 class="anchored" data-anchor-id="visualization">Visualization</h3>
<p>Here’s also a visualization tool, to help see what’s going on in the game.</p>
<div id="4fdce043" class="cell" data-execution_count="15">
<div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> plot_trajectory(trajectory: List[GameState],</span>
<span id="cb15-2">                   state_space: StateSpace,</span>
<span id="cb15-3">                   title: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>):</span>
<span id="cb15-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Plot 2D trajectories (assumes first 2 dims are x, y)"""</span></span>
<span id="cb15-5">    fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>))</span>
<span id="cb15-6">    </span>
<span id="cb15-7">    colors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {name: <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'C</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(state_space.agent_names)}</span>
<span id="cb15-8">    </span>
<span id="cb15-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent_name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state_space.agent_names:</span>
<span id="cb15-10">        positions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb15-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> game_state <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> trajectory:  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># game_state is GameState</span></span>
<span id="cb15-12">            state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> game_state.physical_state  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ← Extract tensor</span></span>
<span id="cb15-13">            agent_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_space.get_state(state, agent_name)</span>
<span id="cb15-14">            positions.append(agent_state[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb15-15"></span>
<span id="cb15-16">        positions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array(positions)</span>
<span id="cb15-17">        ax.plot(positions[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], positions[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb15-18">               color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>colors[agent_name], label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>agent_name, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb15-19">        ax.scatter(positions[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], positions[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb15-20">                  color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>colors[agent_name], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">150</span>, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'o'</span>, edgecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'black'</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb15-21">        ax.scatter(positions[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], positions[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb15-22">                  color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>colors[agent_name], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">150</span>, marker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'X'</span>, edgecolor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'black'</span>, linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb15-23">    </span>
<span id="cb15-24">    ax.set_aspect(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'equal'</span>)</span>
<span id="cb15-25">    ax.legend()</span>
<span id="cb15-26">    ax.grid(alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>)</span>
<span id="cb15-27">    ax.set_title(title)</span>
<span id="cb15-28">    plt.tight_layout()</span>
<span id="cb15-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> fig</span></code></pre></div>
</div>
<div id="3acfe9ae" class="cell" data-execution_count="16">
<div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> plot_payoff_heatmap(payoff_tensor: torch.Tensor,</span>
<span id="cb16-2">                       players: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>],</span>
<span id="cb16-3">                       strategy_sets: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]]):</span>
<span id="cb16-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Visualize 2-player payoff matrix"""</span></span>
<span id="cb16-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(players) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:</span>
<span id="cb16-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Can only plot 2-player games"</span>)</span>
<span id="cb16-7">    </span>
<span id="cb16-8">    p1, p2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> players</span>
<span id="cb16-9">    p1_strats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> strategy_sets[p1]</span>
<span id="cb16-10">    p2_strats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> strategy_sets[p2]</span>
<span id="cb16-11">    </span>
<span id="cb16-12">    fig, (ax1, ax2) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb16-13">    </span>
<span id="cb16-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Player 1 payoffs</span></span>
<span id="cb16-15">    matrix1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoff_tensor[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].numpy()</span>
<span id="cb16-16">    im1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax1.imshow(matrix1, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'RdYlGn'</span>, aspect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'auto'</span>)</span>
<span id="cb16-17">    ax1.set_xticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p2_strats)))</span>
<span id="cb16-18">    ax1.set_yticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p1_strats)))</span>
<span id="cb16-19">    ax1.set_xticklabels(p2_strats)</span>
<span id="cb16-20">    ax1.set_yticklabels(p1_strats)</span>
<span id="cb16-21">    ax1.set_xlabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> strategy'</span>)</span>
<span id="cb16-22">    ax1.set_ylabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p1<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> strategy'</span>)</span>
<span id="cb16-23">    ax1.set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p1<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> Payoffs'</span>)</span>
<span id="cb16-24">    </span>
<span id="cb16-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p1_strats)):</span>
<span id="cb16-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p2_strats)):</span>
<span id="cb16-27">            ax1.text(j, i, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>matrix1[i, j]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>,</span>
<span id="cb16-28">                    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'center'</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'center'</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bold'</span>)</span>
<span id="cb16-29">    </span>
<span id="cb16-30">    plt.colorbar(im1, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax1)</span>
<span id="cb16-31">    </span>
<span id="cb16-32">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Player 2 payoffs</span></span>
<span id="cb16-33">    matrix2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoff_tensor[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].numpy()</span>
<span id="cb16-34">    im2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax2.imshow(matrix2, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'RdYlGn'</span>, aspect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'auto'</span>)</span>
<span id="cb16-35">    ax2.set_xticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p2_strats)))</span>
<span id="cb16-36">    ax2.set_yticks(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p1_strats)))</span>
<span id="cb16-37">    ax2.set_xticklabels(p2_strats)</span>
<span id="cb16-38">    ax2.set_yticklabels(p1_strats)</span>
<span id="cb16-39">    ax2.set_xlabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> strategy'</span>)</span>
<span id="cb16-40">    ax2.set_ylabel(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p1<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> strategy'</span>)</span>
<span id="cb16-41">    ax2.set_title(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>p2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> Payoffs'</span>)</span>
<span id="cb16-42">    </span>
<span id="cb16-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p1_strats)):</span>
<span id="cb16-44">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p2_strats)):</span>
<span id="cb16-45">            ax2.text(j, i, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>matrix2[i, j]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>,</span>
<span id="cb16-46">                    ha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'center'</span>, va<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'center'</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bold'</span>)</span>
<span id="cb16-47">    </span>
<span id="cb16-48">    plt.colorbar(im2, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax2)</span>
<span id="cb16-49">    plt.tight_layout()</span>
<span id="cb16-50">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> fig</span></code></pre></div>
</div>
</section>
</section>
</section>
<section id="example-stag-hunt" class="level1">
<h1>Example: Stag Hunt</h1>
<p>Now we’ll use this framework to build a pursuit-evasion game with stag hunt payoffs.</p>
<p>The setup is:</p>
<ul>
<li>There are 2 cooperative hunters (c1, c2)</li>
<li>There is 1 stag (worth 4 points, requires both hunters to catch)</li>
<li>There are 2 hares (worth 3 points each, can be caught by one hunter)</li>
<li>The stag is faster than the hares but slower than hunters</li>
<li>The hunters pursue using simple “move toward target” policies</li>
<li>Prey flee using “move away from threats” policies</li>
</ul>
<p>What differs from standard game theoretic stag hunt is that this plays out as a pursuit-evastion game in continuous space.</p>
<section id="definition" class="level2">
<h2 class="anchored" data-anchor-id="definition">Definition</h2>
<p>We define each agent as a point mass in 2D. The remaining code defines each element (including state positions) for each Agent.</p>
<div id="5635e048" class="cell" data-execution_count="17">
<div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1">POINT_MASS_2D <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateSpec([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'vx'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'vy'</span>])</span>
<span id="cb17-2">POINT_MASS_3D <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateSpec([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'z'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'vx'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'vy'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'vz'</span>])</span>
<span id="cb17-3"></span>
<span id="cb17-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> build_stag_hunt():</span>
<span id="cb17-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Build stag hunt differential game"""</span></span>
<span id="cb17-6">    </span>
<span id="cb17-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build agents with strategies</span></span>
<span id="cb17-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> make_pursuit(state_space, agent_name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, targets: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>], speed: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>):</span>
<span id="cb17-9">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fn(obs):</span>
<span id="cb17-10">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># obs is full state - extract this agent's position</span></span>
<span id="cb17-11">            own_pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_space.get_state(obs, agent_name)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb17-12">            target_pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.stack([state_space.get_state(obs, t)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> targets]).mean(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb17-13">            direction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> target_pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> own_pos</span>
<span id="cb17-14">            dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(direction) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span></span>
<span id="cb17-15">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (direction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> dist) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> speed</span>
<span id="cb17-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> FunctionPolicy(fn, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, differentiable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb17-17">    </span>
<span id="cb17-18">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> make_flee(state_space, agent_name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, threats: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>], speed: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>):</span>
<span id="cb17-19">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fn(obs):</span>
<span id="cb17-20">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># obs is full state - extract this agent's position</span></span>
<span id="cb17-21">            own_pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_space.get_state(obs, agent_name)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb17-22">            threat_pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.stack([state_space.get_state(obs, t)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> threats]).mean(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb17-23">            direction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> own_pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> threat_pos</span>
<span id="cb17-24">            dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(direction) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span></span>
<span id="cb17-25">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (direction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> dist) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> speed</span>
<span id="cb17-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> FunctionPolicy(fn, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, differentiable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb17-27">    </span>
<span id="cb17-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create agents with state specifications</span></span>
<span id="cb17-29">    agents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb17-30">        Agent(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, POINT_MASS_2D, {}),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Strategies added below</span></span>
<span id="cb17-31">        Agent(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>, POINT_MASS_2D, {}),</span>
<span id="cb17-32">        Agent(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>, POINT_MASS_2D, {}),</span>
<span id="cb17-33">        Agent(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>, POINT_MASS_2D, {}),</span>
<span id="cb17-34">        Agent(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>, POINT_MASS_2D, {}),</span>
<span id="cb17-35">    ]</span>
<span id="cb17-36">    </span>
<span id="cb17-37">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build state space from agents</span></span>
<span id="cb17-38">    state_space <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateSpace(agents, shared_spec<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>)</span>
<span id="cb17-39">    </span>
<span id="cb17-40">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Now add strategies (need state_space for closures)</span></span>
<span id="cb17-41">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].strategy_set <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb17-42">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseStag'</span>: make_pursuit(state_space, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>),</span>
<span id="cb17-43">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseHare'</span>: make_pursuit(state_space, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>),</span>
<span id="cb17-44">    }</span>
<span id="cb17-45">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].strategy_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].strategy_set.keys())</span>
<span id="cb17-46">    </span>
<span id="cb17-47">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].strategy_set <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb17-48">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseStag'</span>: make_pursuit(state_space, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>),</span>
<span id="cb17-49">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseHare'</span>: make_pursuit(state_space, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>),</span>
<span id="cb17-50">    }</span>
<span id="cb17-51">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].strategy_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].strategy_set.keys())</span>
<span id="cb17-52">    </span>
<span id="cb17-53">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].strategy_set <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>: make_flee(state_space, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.1</span>)}</span>
<span id="cb17-54">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].strategy_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>].strategy_set.keys())</span>
<span id="cb17-55">    </span>
<span id="cb17-56">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].strategy_set <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>: make_flee(state_space, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>)}</span>
<span id="cb17-57">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].strategy_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>].strategy_set.keys())</span>
<span id="cb17-58">    </span>
<span id="cb17-59">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>].strategy_set <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>: make_flee(state_space, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>)}</span>
<span id="cb17-60">    agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>].strategy_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(agents[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>].strategy_set.keys())</span>
<span id="cb17-61">    </span>
<span id="cb17-62">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Observation: full state</span></span>
<span id="cb17-63">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> FullObs(ObservationModel):</span>
<span id="cb17-64">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> observe(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state, agent):</span>
<span id="cb17-65">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state</span>
<span id="cb17-66">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> obs_dim(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, agent):</span>
<span id="cb17-67">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state_space.dim</span>
<span id="cb17-68">    </span>
<span id="cb17-69">    obs_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FullObs()</span>
<span id="cb17-70">    </span>
<span id="cb17-71">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Dynamics: kinematic (differentiable!)</span></span>
<span id="cb17-72">    all_agents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>]</span>
<span id="cb17-73">    </span>
<span id="cb17-74">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> StagHuntPayoff(PayoffModel):</span>
<span id="cb17-75">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state_space, agent_names):</span>
<span id="cb17-76">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.state_space <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_space</span>
<span id="cb17-77">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._agents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent_names</span>
<span id="cb17-78">    </span>
<span id="cb17-79">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> agents(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb17-80">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._agents</span>
<span id="cb17-81">    </span>
<span id="cb17-82">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> terminal(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state):    </span>
<span id="cb17-83">            positions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {a: state_space.get_state(state, a)[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> all_agents}</span>
<span id="cb17-84">        </span>
<span id="cb17-85">            payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {a: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> all_agents}</span>
<span id="cb17-86">            radius <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span></span>
<span id="cb17-87">            captured <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>}</span>
<span id="cb17-88">        </span>
<span id="cb17-89">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Stag (needs both)</span></span>
<span id="cb17-90">            d1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(positions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> positions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>]).item()</span>
<span id="cb17-91">            d2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(positions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> positions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>]).item()</span>
<span id="cb17-92">        </span>
<span id="cb17-93">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> d1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> radius <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> d2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> radius:</span>
<span id="cb17-94">                payoffs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span></span>
<span id="cb17-95">                payoffs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span></span>
<span id="cb17-96">                captured[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb17-97">                captured[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb17-98">        </span>
<span id="cb17-99">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Hares (first come first served)</span></span>
<span id="cb17-100">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> captured[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>]:</span>
<span id="cb17-101">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> hare <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>]:</span>
<span id="cb17-102">                    d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(positions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> positions[hare]).item()</span>
<span id="cb17-103">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> radius:</span>
<span id="cb17-104">                        payoffs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span></span>
<span id="cb17-105">                        captured[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb17-106">                        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb17-107">        </span>
<span id="cb17-108">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> captured[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>]:</span>
<span id="cb17-109">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> hare <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>]:</span>
<span id="cb17-110">                    d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(positions[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> positions[hare]).item()</span>
<span id="cb17-111">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> radius:</span>
<span id="cb17-112">                        payoffs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span></span>
<span id="cb17-113">                        captured[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb17-114">                        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb17-115">        </span>
<span id="cb17-116">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> payoffs</span>
<span id="cb17-117"></span>
<span id="cb17-118">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> KinematicDynamics(Dynamics):</span>
<span id="cb17-119">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, max_speeds: Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>]):</span>
<span id="cb17-120">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.max_speeds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> max_speeds</span>
<span id="cb17-121">        </span>
<span id="cb17-122">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> derivative(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, state, controls):</span>
<span id="cb17-123">            dstate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb17-124">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> all_agents:</span>
<span id="cb17-125">                agent_state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state_space.get_state(state, agent)</span>
<span id="cb17-126">                control <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> controls[agent]</span>
<span id="cb17-127">                </span>
<span id="cb17-128">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Soft clamping for differentiability</span></span>
<span id="cb17-129">                vel_des <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> control</span>
<span id="cb17-130">                speed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.norm(vel_des) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span></span>
<span id="cb17-131">                max_speed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.max_speeds[agent]</span>
<span id="cb17-132">                </span>
<span id="cb17-133">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Soft clamp: vel = direction * min(speed, max_speed)</span></span>
<span id="cb17-134">                scale_factor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> max_speed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> torch.tanh(speed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> max_speed) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> speed</span>
<span id="cb17-135">                vel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vel_des <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> scale_factor</span>
<span id="cb17-136">                </span>
<span id="cb17-137">                dpos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vel</span>
<span id="cb17-138">                dvel <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb17-139">                dstate.append(torch.cat([dpos, dvel]))</span>
<span id="cb17-140">            </span>
<span id="cb17-141">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cat(dstate)</span>
<span id="cb17-142">    </span>
<span id="cb17-143">    dynamics <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> KinematicDynamics({</span>
<span id="cb17-144">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>,</span>
<span id="cb17-145">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.1</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span></span>
<span id="cb17-146">    })</span>
<span id="cb17-147">    </span>
<span id="cb17-148">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initial state</span></span>
<span id="cb17-149">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> initial():</span>
<span id="cb17-150">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cat([</span>
<span id="cb17-151">            torch.tensor([<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>]),  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># c1</span></span>
<span id="cb17-152">            torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>]),   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># c2</span></span>
<span id="cb17-153">            torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>]),    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stag</span></span>
<span id="cb17-154">            torch.tensor([<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>]),   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># hare1</span></span>
<span id="cb17-155">            torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>]),    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># hare2</span></span>
<span id="cb17-156">        ])</span>
<span id="cb17-157">    </span>
<span id="cb17-158">    payoff_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StagHuntPayoff(state_space, all_agents)</span>
<span id="cb17-159">    game <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> DifferentialGame(state_space, agents, obs_model, dynamics, payoff_model, initial, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Stag Hunt"</span>)</span>
<span id="cb17-160">    </span>
<span id="cb17-161">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> game, state_space</span></code></pre></div>
</div>
</section>
<section id="results" class="level2">
<h2 class="anchored" data-anchor-id="results">Results</h2>
<p>Let’s see what the outputs look like:</p>
<div id="cfefe244" class="cell" data-execution_count="18">
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>: </span>
<span id="cb18-2">    game, state_space <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_stag_hunt()</span>
<span id="cb18-3">    arena <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Arena(game, dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, max_time<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">15.0</span>)</span>
<span id="cb18-4">    </span>
<span id="cb18-5">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>game<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-6">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Strategy sets: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>game<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get_strategy_sets()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-7">    </span>
<span id="cb18-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Test scenarios</span></span>
<span id="cb18-9">    profiles <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb18-10">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Both Cooperate"</span>, {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseStag'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseStag'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>}),</span>
<span id="cb18-11">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Both Defect"</span>, {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseHare'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseHare'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>}),</span>
<span id="cb18-12">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Asymmetric"</span>, {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseStag'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ChaseHare'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'stag'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare1'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hare2'</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Flee'</span>}),</span>
<span id="cb18-13">    ]</span>
<span id="cb18-14">    </span>
<span id="cb18-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> desc, profile <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> profiles:</span>
<span id="cb18-16">        traj, payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> arena.play(profile)</span>
<span id="cb18-17">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>desc<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: c1=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>payoffs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, c2=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>payoffs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-18">        plot_trajectory(traj, state_space, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>desc)</span>
<span id="cb18-19">        plt.show()</span>
<span id="cb18-20">    </span>
<span id="cb18-21">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Convert to normal form</span></span>
<span id="cb18-22">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"="</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>)</span>
<span id="cb18-23">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NORMAL FORM EXTRACTION"</span>)</span>
<span id="cb18-24">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"="</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>)</span>
<span id="cb18-25">    </span>
<span id="cb18-26">    payoff_tensor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> NormalFormConverter.to_payoff_matrix(arena, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>], n_samples<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb18-27">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Payoff tensor shape: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>payoff_tensor<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>shape<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-28">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Payoff tensor:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>payoff_tensor<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-29">    </span>
<span id="cb18-30">    plot_payoff_heatmap(payoff_tensor, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c1'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'c2'</span>], game.get_strategy_sets())</span></code></pre></div>
</div>
<p>We generate three plots. The first is the trajectories with both agents cooperating:</p>
<p><img src="https://demonstrandom.com/game_theory/posts/differential_stag_hunt/Figure_1-Both-Cooperate.png" class="img-fluid" alt="Figure 1 - Both Cooperate"></p>
<p>Second with two defections:</p>
<p><img src="https://demonstrandom.com/game_theory/posts/differential_stag_hunt/Figure_2-Both-Defect.png" class="img-fluid" alt="Figure 2 - Both Defect"></p>
<p>Third asymmetric:</p>
<p><img src="https://demonstrandom.com/game_theory/posts/differential_stag_hunt/Figure_3-Asymmetric.png" class="img-fluid" alt="Figure 3 - Asymmetric"></p>
</section>
</section>
<section id="conclusion-and-next-steps" class="level1">
<h1>Conclusion and Next Steps</h1>
<p>In this post, we implemented the beginnings of a differential games framework, then adapted it for pursuit-evasion games with two chasers and three heterogenous evaders. Specifically, the payoff structure of this game matches the well-known “stag hunt” game from game theory. In the next post, we will attempt to combine our <a href="../../../game_theory/posts/canonical_games/index.html">differentiable game canonicalizer</a> with this setup.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>This is similar to how optimal control tools are architected, like Drake or Crocoddyl, or robotics simulatos (MuJoCo). I thought a bit about video game engines as well (Unity, Unreal Engine), but there the physics tend to be implicit and most of the abstractions are oriented around entities and components for building the actual content.↩︎</p></li>
<li id="fn2"><p>Disclosure: Claude helped with some functions, but I reviewed all code. I do not believe an AI could write this unassisted at time of writing.↩︎</p></li>
<li id="fn3"><p>For now, this the linearization of the change in state. In theory the dynamics could also support higher order derivations. Furthermore, right now this is manually computed. We might be able to use automatic differentiation to handle this as well. More on this in a future post.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Games</category>
  <category>Research</category>
  <guid>https://demonstrandom.com/game_theory/posts/differential_stag_hunt/</guid>
  <pubDate>Mon, 20 Oct 2025 04:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/game_theory/posts/differential_stag_hunt/Hot_Pursuit_by_Paul_Klee_1939.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>SINDy with Control</title>
  <link>https://demonstrandom.com/ml/posts/sindyc/</link>
  <description><![CDATA[ 





<p><a href="https://commons.wikimedia.org/wiki/File%3ATheo_van_Doesburg_Counter-CompositionV_%281924%29.jpg?utm_source=chatgpt.com"><img src="https://demonstrandom.com/ml/posts/sindyc/Theo_van_Doesburg_Counter-CompositionV_(1924).jpg" class="img-fluid" style="width:45.0%" alt="Theo van Doesburg. *Counter Composition V*. (1924)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>The SINDy method is useful for fitting governing equations to data drawn from a dynamical system. However, for engineering applications we often seek not just to analyze dynamical systems but to <em>control</em> them. In this short post I implement the SINDyC method<sup>1</sup>, which generalizes SINDy to include external inputs and feedback control. This continues our investigations from <a href="../../../ml/posts/sindy/index.html">the last post in this series</a>.</p>
</section>
<section id="background" class="level1">
<h1>Background</h1>
<p>In <a href="../../../ml/posts/linear_methods_for_dynamical_systems/index.html">Dynamic Mode Decomposition</a>, we tried to fit a linear operator <img src="https://latex.codecogs.com/png.latex?A"> to a dynamical system such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_%7Bt+1%7D%20=%20Ax_t%0A"></p>
<p>DMDc extends this to instead fit the equation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_%7Bt+1%7D%20=%20Ax_t%20+%20Bu_t%0A"></p>
<p>Instead of just a matrix of snapshots, we also need a matrix of control history (the <img src="https://latex.codecogs.com/png.latex?u_k"> at each timestamp).</p>
<p>The <a href="../../../ml/posts/linear_methods_for_dynamical_systems/index.html#koopman-operator">Koopman analysis</a> for this system also now includes <img src="https://latex.codecogs.com/png.latex?u">. Instead of</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AKg(x_t)%20:=%20g(F(x_t))%20=%20g(x_%7Bt+1%7D)%0A"></p>
<p>We have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AK_*g(x_t,%20u_t)%20:=%20g(F(x_t,%20u_t),%20*)%20=%20g(x_%7Bt+1%7D,%20*)%0A"></p>
<p>Note that <img src="https://latex.codecogs.com/png.latex?K_*"> depends on the choice of control vector<sup>2</sup>.</p>
</section>
<section id="setup" class="level1">
<h1>Setup</h1>
<p>We start with the same dataset of snapshots <img src="https://latex.codecogs.com/png.latex?X">, but now we also record our control history at each time point:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CUpsilon%20=%20%5Bu_1,%20u_2,%20...,%20u_m%5D%0A"></p>
<p>As in SINDy, we create a dictionary of basis functions. Then, using the data, we fit a sparse set of coefficients to the basis functions:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%7BX%7D%20=%20%5Cmathbf%7B%5CTheta%7D(X,%20%5CUpsilon)%5Cmathbf%7B%5CXi%7D%0A"></p>
<p>However (and this is critical), if the signal <img src="https://latex.codecogs.com/png.latex?u"> corresponds to a feedback control signal, <em>we cannot disambiguate the effect of the feedback control from that of the internal system</em>. That is, we must intervene on the controls<sup>3</sup> to separate them from the dynamics.</p>
</section>
<section id="implementation" class="level1">
<h1>Implementation</h1>
<p>Coding this is almost trivially similar to SINDy, with a few modifications.</p>
<section id="library" class="level2">
<h2 class="anchored" data-anchor-id="library">Library</h2>
<p>We’ll need to upgrade our library of functions to include cross-terms between <img src="https://latex.codecogs.com/png.latex?x"> and <img src="https://latex.codecogs.com/png.latex?u"></p>
<div id="0bd09fda" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sindyc_library(X, U, poly_order_x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, poly_order_u<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb1-2">                   include_cross<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, include_sine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, include_cosine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>):</span>
<span id="cb1-3">    n_vars, n_samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X.shape</span>
<span id="cb1-4">    n_ctrl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> U.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb1-5"></span>
<span id="cb1-6">    feats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [np.ones(n_samples)]</span>
<span id="cb1-7">    descriptions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'1'</span>]</span>
<span id="cb1-8"></span>
<span id="cb1-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> order <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, poly_order_x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb1-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> combo <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combinations_with_replacement(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_vars), order):</span>
<span id="cb1-11">            term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.prod([X[i, :] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combo], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb1-12">            feats.append(term)</span>
<span id="cb1-13">            descriptions.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'*'</span>.join([<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combo]))</span>
<span id="cb1-14"></span>
<span id="cb1-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> order <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, poly_order_u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb1-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> combo <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combinations_with_replacement(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_ctrl), order):</span>
<span id="cb1-17">            term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.prod([U[i, :] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combo], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb1-18">            feats.append(term)</span>
<span id="cb1-19">            descriptions.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'*'</span>.join([<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'u_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combo]))</span>
<span id="cb1-20"></span>
<span id="cb1-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> include_cross:</span>
<span id="cb1-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_vars):</span>
<span id="cb1-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_ctrl):</span>
<span id="cb1-24">                feats.append(X[i, :] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> U[j, :])</span>
<span id="cb1-25">                descriptions.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">*u_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>j<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span>
<span id="cb1-26"></span>
<span id="cb1-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> include_sine:</span>
<span id="cb1-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_vars):</span>
<span id="cb1-29">            feats.append(np.sin(X[i, :]))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> descriptions.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'sin(x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)'</span>)</span>
<span id="cb1-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> include_cosine:</span>
<span id="cb1-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_vars):</span>
<span id="cb1-32">            feats.append(np.cos(X[i, :]))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> descriptions.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'cos(x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)'</span>)</span>
<span id="cb1-33"></span>
<span id="cb1-34">    Theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.column_stack(feats)</span>
<span id="cb1-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Theta, descriptions</span></code></pre></div>
</div>
</section>
<section id="algorithm" class="level2">
<h2 class="anchored" data-anchor-id="algorithm">Algorithm</h2>
<p>The actual <code>sindyc</code> algorithm is more or less the same as the <code>sindy</code> method:</p>
<div id="04eefa3c" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sindyc(X, U, dt, poly_order_x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, poly_order_u<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb2-2">           include_cross<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, lambda_reg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>, max_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>,</span>
<span id="cb2-3">           include_sine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, include_cosine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>):</span>
<span id="cb2-4">    dXdt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> finite_difference(X, dt) </span>
<span id="cb2-5">    Theta, descriptions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sindyc_library(</span>
<span id="cb2-6">        X, U,</span>
<span id="cb2-7">        poly_order_x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly_order_x,</span>
<span id="cb2-8">        poly_order_u<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly_order_u,</span>
<span id="cb2-9">        include_cross<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>include_cross,</span>
<span id="cb2-10">        include_sine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>include_sine,</span>
<span id="cb2-11">        include_cosine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>include_cosine</span>
<span id="cb2-12">    )</span>
<span id="cb2-13">    Xi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sequential_threshold_least_squares(Theta, dXdt, lambda_reg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>lambda_reg, max_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>max_iter)</span>
<span id="cb2-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Xi, descriptions</span></code></pre></div>
</div>
<p>We first use finite differences to get the derivatives of X, then we use <a href="../../../ml/posts/sindy/index.html#sequential-threshold-least-squares">sequential threshold least-squares</a> to compute the actual coefficients.</p>
</section>
</section>
<section id="example" class="level1">
<h1>Example</h1>
<p>Let’s take a look at a simple example<sup>4</sup>: a predator-prey system with a sinusoidal forcing function:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5Cdot%20x(t)%20&amp;=%20%5Calpha%5C,x(t)%20-%20%5Cbeta%5C,x(t)%5C,y(t)%20%5C;+%5C;%20k_%7B1%7D%5C,u_%7B1%7D(t),%5C%5C%0A%5Cdot%20y(t)%20&amp;=%20%5Cdelta%5C,x(t)%5C,y(t)%20-%20%5Cgamma%5C,y(t)%20%5C;-%5C;%20k_%7B2%7D%5C,u_%7B2%7D(t).%0A%5Cend%7Baligned%7D%0A"></p>
<p>where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0Au_%7B1%7D(t)%20%5C;&amp;=%5C;%200.3%5C,%5Csin(0.3%5C,t)%20%5C;+%5C;%200.2%5C,%5Ccos(0.11%5C,t)%20%5C%5C%0A%5Cqquad%0Au_%7B2%7D(t)%20%5C;&amp;=%5C;%200.25%5C,%5Csin(0.17%5C,t%20+%200.7)%0A%5Cend%7Baligned%7D%0A"></p>
<p>We’ll pick values for the coefficients as such:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5Cdot%20x(t)%20&amp;=%201.0%5C,x(t)%5C;-%5C;0.5%5C,x(t)%5C,y(t)%5C;+%5C;0.8%5C,u_%7B1%7D(t)%5C%5C%0A%5Cdot%20y(t)%20&amp;=%200.5%5C,x(t)%5C,y(t)%5C;-%5C;1.0%5C,y(t)%5C;-%5C;0.6%5C,u_%7B2%7D(t)%0A%5Cend%7Baligned%7D%0A"></p>
<section id="data-generation" class="level2">
<h2 class="anchored" data-anchor-id="data-generation">Data Generation</h2>
<p>We need to generate the data. Let’s write up the code for the predator-prey system and for our controls:</p>
<div id="5d40fac1" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> predator_prey_control_rhs(state, u, alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, beta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, delta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, gamma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, k1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, k2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>):</span>
<span id="cb3-2">    x, y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state</span>
<span id="cb3-3">    u1, u2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> u</span>
<span id="cb3-4">    dx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> beta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> k1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>u1</span>
<span id="cb3-5">    dy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> delta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> gamma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> k2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>u2</span>
<span id="cb3-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.array([dx, dy])</span></code></pre></div>
</div>
<p>Above is the predator-prey system. Here’s what our control functions will look like:</p>
<div id="27112382" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">u1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> t: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>np.sin(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>t) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>np.cos(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.11</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>t)</span>
<span id="cb4-2">u2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> t: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>np.sin(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.17</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span></code></pre></div>
</div>
<p>Now we can simulate the system:</p>
<div id="5adf7b6d" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Helper function, takes a step of fourth order Runge-Kutta</span></span>
<span id="cb5-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># See any numerical methods book, like https://link.springer.com/book/10.1007/978-3-540-78862-1</span></span>
<span id="cb5-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> rk4_step(ode_func_f_X, y_current, dt, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>ode_kwargs): </span>
<span id="cb5-4">    k1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ode_func_f_X(y_current, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>ode_kwargs)</span>
<span id="cb5-5">    k2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ode_func_f_X(y_current <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k1, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>ode_kwargs)</span>
<span id="cb5-6">    k3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ode_func_f_X(y_current <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k2, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>ode_kwargs)</span>
<span id="cb5-7">    k4 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ode_func_f_X(y_current <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k3, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>ode_kwargs)</span>
<span id="cb5-8">    y_next <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> y_current <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (k1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> k4)</span>
<span id="cb5-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> y_next</span>
<span id="cb5-10"></span>
<span id="cb5-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> simulate_predator_prey_with_control(x0, t, u1, u2, rhs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>predator_prey_control_rhs):</span>
<span id="cb5-12">    n  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(t)</span>
<span id="cb5-13">    dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> t[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> t[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb5-14">    X  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n))</span>
<span id="cb5-15">    U  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n))</span>
<span id="cb5-16">    X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.asarray(x0, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)</span>
<span id="cb5-17"></span>
<span id="cb5-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n):</span>
<span id="cb5-19">        u_vec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([u1(t[k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]), u2(t[k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)</span>
<span id="cb5-20">        U[:, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> u_vec</span>
<span id="cb5-21">        X[:, k] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rk4_step(rhs, X[:, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], dt, u<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>u_vec)</span>
<span id="cb5-22"></span>
<span id="cb5-23">    U[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([u1(t[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]), u2(t[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)</span>
<span id="cb5-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> X, U, dt</span></code></pre></div>
</div>
</section>
<section id="outcome" class="level2">
<h2 class="anchored" data-anchor-id="outcome">Outcome</h2>
<p>Putting the code together, we get:</p>
<div id="0d05200f" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb6-2">    t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">50.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>) </span>
<span id="cb6-3"></span>
<span id="cb6-4">    u1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> _t: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>np.sin(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>_t) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>np.cos(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.11</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>_t)</span>
<span id="cb6-5">    u2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> _t: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>np.sin(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.17</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>)</span>
<span id="cb6-6"></span>
<span id="cb6-7">    Xpp, Upp, dt_pp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> simulate_predator_prey_with_control(</span>
<span id="cb6-8">        x0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>),</span>
<span id="cb6-9">        t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>t,</span>
<span id="cb6-10">        u1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>u1,</span>
<span id="cb6-11">        u2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>u2,</span>
<span id="cb6-12">        rhs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>predator_prey_control_rhs</span>
<span id="cb6-13">    )</span>
<span id="cb6-14"></span>
<span id="cb6-15">    Xi_c, desc_c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sindyc(</span>
<span id="cb6-16">        Xpp, Upp, dt_pp,</span>
<span id="cb6-17">        poly_order_x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb6-18">        poly_order_u<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb6-19">        include_cross<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb6-20">        lambda_reg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>,</span>
<span id="cb6-21">        max_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span></span>
<span id="cb6-22">    )</span>
<span id="cb6-23"></span>
<span id="cb6-24">    print_equations(Xi_c, desc_c, feature_names<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>,<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>])</span></code></pre></div>
</div>
<p>And the outcome:</p>
<pre><code>dx/dt = 0.999839*x_0 - 0.499924*x_0*x_1 + 0.799839*u_0
dy/dt = -0.999844*x_1 + 0.499927*x_0*x_1 - 0.599914*u_1</code></pre>
<p>Which matches our expected coefficients closely.</p>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>This post was a straightforward extension of SINDy to accommodate controls.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>See <a href="https://arxiv.org/abs/1605.06682">this paper</a> by Brunton, Proctor, and Kutz. I may use slightly different notation.↩︎</p></li>
<li id="fn2"><p>The <img src="https://latex.codecogs.com/png.latex?*"> here is a placeholder indexing the family of possible controls. That is, <img src="https://latex.codecogs.com/png.latex?%5Cforall%20u%20%5Cin%20U,%20(K_%7Bu%7D%20g)(x_t)%20=%20g(F(x_t,u))">↩︎</p></li>
<li id="fn3"><p>“Persistent excitation” is required (I don’t fully understand this requirement yet but it’s a requirement for identifiability). Brunton, Proctor, and Kutz recommend injecting a sufficiently large white noise signal, or occasionally kicking the system with a large impulse or step in.↩︎</p></li>
<li id="fn4"><p>Also drawn from Brunton, Proctor, and Kutz.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Machine Learning</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/ml/posts/sindyc/</guid>
  <pubDate>Mon, 01 Sep 2025 04:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/ml/posts/sindyc/Theo_van_Doesburg_Counter-CompositionV_(1924).jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Learning Equilibria by Gradient Descent</title>
  <link>https://demonstrandom.com/game_theory/posts/gradient_learning_nash/</link>
  <description><![CDATA[ 





<p><a href="https://commons.wikimedia.org/wiki/File:Albert_Gleizes,_La_Chasse,_1911,_oil_on_canvas,_123.2_x_99_cm.jpg"><img src="https://demonstrandom.com/game_theory/posts/gradient_learning_nash/Albert_Gleizes_La_Chasse_1911_oil_on_canvas.jpg" class="img-fluid" style="width:45.0%" alt="Albert Gleizes. *La Chasse*. (1911)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>Given a set of agents playing a game, how do we determine their optimal strategic behavior?</p>
<p>In <a href="../../../game_theory/posts/canonical_games/index.html">the last post</a> we looked at ways to differentiably identify equivalence classes of games. In this short post, we’ll use gradient descent to identify the Nash equilibria for some simple games.</p>
</section>
<section id="background" class="level1">
<h1>Background</h1>
<p>Identifying Nash equilibria (or other strategic behavior) is difficult in general<sup>1</sup>.</p>
<p>At some point I will get into traditional algorithms like <a href="https://nashpy.readthedocs.io/en/stable/text-book/support-enumeration.html">support enumeration</a> or <a href="https://en.wikipedia.org/wiki/Lemke%E2%80%93Howson_algorithm">Lemke-Howson</a> for finding equilibria. However, for the purposes of this post I will investigate composing parametrized agents using games, and learning the Nash equilibria via gradient descent.</p>
</section>
<section id="implementation" class="level1">
<h1>Implementation</h1>
<p>We’ll build some simple classes to implement this experiment.</p>
<section id="game" class="level2">
<h2 class="anchored" data-anchor-id="game">Game</h2>
<p>First, we need some game representation.</p>
<div id="5efcce2c" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Game:</span>
<span id="cb1-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, payoffs: torch.Tensor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> nn.Parameter, actions: List[List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]], name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb1-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb1-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs</span>
<span id="cb1-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> actions</span>
<span id="cb1-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Unnamed Game"</span></span>
<span id="cb1-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs.shape </span>
<span id="cb1-8"></span>
<span id="cb1-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb1-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> size(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb1-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._size</span>
<span id="cb1-12">    </span>
<span id="cb1-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> payoff(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, action_indices):</span>
<span id="cb1-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoffs[(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">slice</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>),) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(action_indices)]</span>
<span id="cb1-15">    </span>
<span id="cb1-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> to(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, device):</span>
<span id="cb1-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Game(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoffs.to(device), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actions, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.name) </span>
<span id="cb1-18"></span>
<span id="cb1-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> clone(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb1-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoffs, nn.Parameter):</span>
<span id="cb1-21">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Game(nn.Parameter(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoffs.detach().clone()), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actions, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.name)</span>
<span id="cb1-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb1-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Game(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoffs.detach().clone(), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.actions, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.name)</span>
<span id="cb1-24"></span>
<span id="cb1-25">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__repr__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb1-26">        learnable <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoffs, nn.Parameter) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.payoffs.requires_grad</span>
<span id="cb1-27">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'&lt;Game "</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">" size=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>_size<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> learnable=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>learnable<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">&gt;'</span></span></code></pre></div>
</div>
<p>Here, the payoffs are either raw tensors or learnable (if you pass in parameters). Parametrized payoffs are useful for tasks like optimizing welfare, mechanism design, inverse RL, etc.</p>
</section>
<section id="agent" class="level2">
<h2 class="anchored" data-anchor-id="agent">Agent</h2>
<p>Next, we need agents that can play the game.</p>
<div id="5410bb10" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Agent:</span>
<span id="cb2-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, policy, name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb2-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> name</span>
<span id="cb2-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.policy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> policy</span>
<span id="cb2-5"></span>
<span id="cb2-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> act(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, actions: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]):</span>
<span id="cb2-7">        probs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.policy(actions)</span>
<span id="cb2-8">        dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.distributions.Categorical(probs)</span>
<span id="cb2-9">        action_idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dist.sample().item()</span>
<span id="cb2-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> action_idx, actions[action_idx]</span></code></pre></div>
</div>
<p>An agent just samples from the policy distribution and takes an action.</p>
</section>
<section id="policies" class="level2">
<h2 class="anchored" data-anchor-id="policies">Policies</h2>
<p>What kind of policies might the agent have?</p>
<section id="abstract" class="level3">
<h3 class="anchored" data-anchor-id="abstract">Abstract</h3>
<p>We’ll start with the abstraction. We have a <code>_run_once</code> helper to prevent double initializing a policy.</p>
<div id="460ea44c" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _run_once(method):</span>
<span id="cb3-2">    attr_flag <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"__</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>method<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">_has_run"</span></span>
<span id="cb3-3"></span>
<span id="cb3-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@functools.wraps</span>(method)</span>
<span id="cb3-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> wrapper(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>args, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs):</span>
<span id="cb3-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">getattr</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, attr_flag, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>):</span>
<span id="cb3-7">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span>                         </span>
<span id="cb3-8">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">setattr</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, attr_flag, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb3-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> method(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>args, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs)</span>
<span id="cb3-10"></span>
<span id="cb3-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> wrapper</span></code></pre></div>
</div>
<div id="cd0f6da2" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Policy(ABC):</span>
<span id="cb4-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb4-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb4-4"></span>
<span id="cb4-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init_subclass__</span>(cls, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs):</span>
<span id="cb4-6">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init_subclass__</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs)</span>
<span id="cb4-7"></span>
<span id="cb4-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># If the subclass overrides 'initialize', wrap it exactly once.</span></span>
<span id="cb4-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"initialize"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> cls.__dict__:</span>
<span id="cb4-10">            cls.initialize <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _run_once(cls.__dict__[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"initialize"</span>])</span>
<span id="cb4-11"></span>
<span id="cb4-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb4-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> initialize(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, actions: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb4-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb4-15">    </span>
<span id="cb4-16">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@abstractmethod</span></span>
<span id="cb4-17">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, actions: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb4-19">    </span>
<span id="cb4-20">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, actions: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">hasattr</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"initialize"</span>):</span>
<span id="cb4-22">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.initialize(actions)</span>
<span id="cb4-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__call__</span>(actions)     </span></code></pre></div>
</div>
</section>
<section id="logits" class="level3">
<h3 class="anchored" data-anchor-id="logits">Logits</h3>
<p>Our first policy is just a logits policy.</p>
<div id="3e9012bb" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> LogitsPolicy(Policy, nn.Module):</span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, initialization<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'uniform'</span>):</span>
<span id="cb5-3">        Policy.<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>)</span>
<span id="cb5-4">        nn.Module.<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>)</span>
<span id="cb5-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.initialization <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> initialization</span>
<span id="cb5-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>  </span>
<span id="cb5-7">    </span>
<span id="cb5-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> initialize(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, actions: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb5-9">        num_actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(actions)</span>
<span id="cb5-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.initialization <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'uniform'</span>:</span>
<span id="cb5-11">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.zeros(num_actions))  </span>
<span id="cb5-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.initialization <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'random'</span>:</span>
<span id="cb5-13">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.rand(num_actions))</span>
<span id="cb5-14"></span>
<span id="cb5-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, actions: List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.softmax(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.logits, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span></code></pre></div>
</div>
<p>We’ll hold off on other policies until later posts.</p>
</section>
</section>
<section id="arena" class="level2">
<h2 class="anchored" data-anchor-id="arena">Arena</h2>
<p>Let’s compose Agents and Games in “Arena” objects. This is just to cleanly separate Agents from Games.</p>
<div id="00db5305" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Arena:</span>
<span id="cb6-2"></span>
<span id="cb6-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, game: Game, agents: List[Agent]):</span>
<span id="cb6-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(agents) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> game.num_players</span>
<span id="cb6-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> game</span>
<span id="cb6-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agents</span>
<span id="cb6-7"></span>
<span id="cb6-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents):</span>
<span id="cb6-9">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">hasattr</span>(agent.policy, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'initialize'</span>):</span>
<span id="cb6-10">                agent.policy.initialize(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.actions[i])</span>
<span id="cb6-11"></span>
<span id="cb6-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> play(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb6-13">        action_indices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-14">        actions_chosen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb6-15"></span>
<span id="cb6-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> player_idx, agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents):</span>
<span id="cb6-17">            actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.actions[player_idx]</span>
<span id="cb6-18">            action_idx, action <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent.act(actions)</span>
<span id="cb6-19">            action_indices.append(action_idx)</span>
<span id="cb6-20">            actions_chosen.append(action)</span>
<span id="cb6-21"></span>
<span id="cb6-22">        payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.payoff(action_indices)</span>
<span id="cb6-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> actions_chosen, payoffs</span>
<span id="cb6-24"></span>
<span id="cb6-25">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> expected_payoffs(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb6-26">        dists <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [agent.policy(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.actions[i]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.agents)]</span>
<span id="cb6-27">        joint_dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dists[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb6-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> dist <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> dists[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]:</span>
<span id="cb6-29">            joint_dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.einsum(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'i,j-&gt;ij'</span>, joint_dist.flatten(), dist).flatten()</span>
<span id="cb6-30"></span>
<span id="cb6-31">        payoffs_flat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.payoffs.view(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.game.num_players, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-32">        exp_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (joint_dist <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> payoffs_flat).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> exp_payoffs</span></code></pre></div>
</div>
<p>The “play” function runs a round of the game. The “expected payoffs” returns the expected payoffs for each agent.</p>
</section>
</section>
<section id="example" class="level1">
<h1>Example</h1>
<p>Let’s look at an example. This first example is <a href="https://en.wikipedia.org/wiki/Stag_hunt">Stag Hunt</a>.</p>
<div id="0c39c492" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb7-2">    staghunt_actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Stag"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hare"</span>], [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Stag"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hare"</span>]]</span>
<span id="cb7-3">    p1_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb7-4">        [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>],  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># P1 plays Stag vs P2's [Stag, Hare]</span></span>
<span id="cb7-5">        [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># P1 plays Hare vs P2's [Stag, Hare]</span></span>
<span id="cb7-6">    ]</span>
<span id="cb7-7"></span>
<span id="cb7-8">    p2_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb7-9">        [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># P2 plays Stag vs P1's [Stag, Hare]  </span></span>
<span id="cb7-10">        [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># P2 plays Hare vs P1's [Stag, Hare]</span></span>
<span id="cb7-11">    ] </span>
<span id="cb7-12">    payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>  nn.Parameter(torch.tensor([p1_payoffs, p2_payoffs], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>))</span>
<span id="cb7-13">    stag_hunt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Game(payoffs, staghunt_actions, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Stag Hunt"</span>)</span>
<span id="cb7-14">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(stag_hunt)</span>
<span id="cb7-15">    </span>
<span id="cb7-16">    alice <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb7-17">        policy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> actions: torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Stag"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> actions]), </span>
<span id="cb7-18">        name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Alice"</span></span>
<span id="cb7-19">    )</span>
<span id="cb7-20">    bob <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb7-21">        policy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> actions: torch.ones(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(actions))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(actions), </span>
<span id="cb7-22">        name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bob"</span></span>
<span id="cb7-23">    )</span>
<span id="cb7-24">    </span>
<span id="cb7-25">    stag_hunt_arena <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Arena(stag_hunt, [alice, bob])</span>
<span id="cb7-26">    </span>
<span id="cb7-27">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(stag_hunt_arena.expected_payoffs())</span>
<span id="cb7-28">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(stag_hunt_arena.play())</span>
<span id="cb7-29">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(stag_hunt_arena.play())</span></code></pre></div>
</div>
<p>We initialize two deterministic agents, Alice and Bob. Alice always plays Stag. Bob plays are random. We then compute their expected payoffs (and play two rounds).</p>
<pre><code>&lt;Game "Stag Hunt" size=torch.Size([2, 2, 2]) learnable=True&gt;
tensor([4., 2.], grad_fn=&lt;SumBackward1&gt;)
(['Stag', 'Hare'], tensor([0., 1.], grad_fn=&lt;SelectBackward0&gt;))
(['Stag', 'Stag'], tensor([8., 3.], grad_fn=&lt;SelectBackward0&gt;))</code></pre>
<p>We can see the expected payoff for Alice is 4 and the expected payoff for Bob is 2. In the two rounds they play, Bob first plays “Hare” (low payoffs for both players), then plays “Stag” (high payoffs).</p>
<p>Now let’s build differentiable agents:</p>
<div id="88ba5f5c" class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">...</span>
<span id="cb9-2">    diff_alice <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb9-3">        policy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>LogitsPolicy(initialization<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'random'</span>),</span>
<span id="cb9-4">        name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DiffAlice"</span></span>
<span id="cb9-5">    )</span>
<span id="cb9-6">    diff_bob <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb9-7">        policy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>LogitsPolicy(initialization<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'random'</span>),</span>
<span id="cb9-8">        name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DiffBob"</span></span>
<span id="cb9-9">    )</span>
<span id="cb9-10">    diff_agents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [diff_alice, diff_bob]</span>
<span id="cb9-11">    diff_stag_hunt_arena <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Arena(stag_hunt, diff_agents)</span>
<span id="cb9-12">    </span>
<span id="cb9-13">    optimizers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb9-14">        optim.Adam(diff_alice.policy.parameters(), lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>),</span>
<span id="cb9-15">        optim.Adam(diff_bob.policy.parameters(), lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>)</span>
<span id="cb9-16">    ]</span>
<span id="cb9-17"></span>
<span id="cb9-18">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Training loop</span></span>
<span id="cb9-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> step <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>):</span>
<span id="cb9-20">        exp_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> diff_stag_hunt_arena.expected_payoffs()</span>
<span id="cb9-21">        </span>
<span id="cb9-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Player 1 update</span></span>
<span id="cb9-23">        optimizers[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].zero_grad()</span>
<span id="cb9-24">        (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>exp_payoffs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]).backward(retain_graph<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)  </span>
<span id="cb9-25">        optimizers[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].step()</span>
<span id="cb9-26"></span>
<span id="cb9-27">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Player 2 update</span></span>
<span id="cb9-28">        optimizers[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].zero_grad()</span>
<span id="cb9-29">        (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>exp_payoffs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]).backward()</span>
<span id="cb9-30">        optimizers[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].step()</span>
<span id="cb9-31"></span>
<span id="cb9-32">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb9-33">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Step </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>step<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, Expected Payoffs: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>exp_payoffs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>detach()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cpu()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>numpy()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-34"></span>
<span id="cb9-35">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Final Policies</span></span>
<span id="cb9-36">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, agent <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(diff_agents):</span>
<span id="cb9-37">        logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent.policy.logits.detach().numpy()</span>
<span id="cb9-38">        probs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent.policy(stag_hunt.actions[i]).detach().numpy()</span>
<span id="cb9-39">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Agent </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> final logits: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>logits<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-40">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Agent </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> final probabilities: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>probs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-41">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Agent </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> prefers: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Stag'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> probs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> probs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Hare'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb9-42">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>()</span></code></pre></div>
</div>
<p>In the main loop, we optimize Alice and Bob’s policies separately via simultaneous updates (there are other choices, like alternative updates). We see:</p>
<pre><code>Step 0, Expected Payoffs: [2.874151  1.4458582]
Step 20, Expected Payoffs: [7.565231 2.889052]
Step 40, Expected Payoffs: [7.9332013 2.9811559]
Step 60, Expected Payoffs: [7.9630227 2.9893801]
Step 80, Expected Payoffs: [7.972061 2.991976]
Step 100, Expected Payoffs: [7.9772897 2.9934921]
Step 120, Expected Payoffs: [7.9810405 2.994578 ]
Step 140, Expected Payoffs: [7.9839015 2.995403 ]
Step 160, Expected Payoffs: [7.986139  2.9960463]
Step 180, Expected Payoffs: [7.9879236 2.9965587]
Agent 1 final logits: [ 4.3300014 -2.8580525]
Agent 1 final probabilities: [9.9924505e-01 7.5498747e-04]
Agent 1 prefers: Stag

Agent 2 final logits: [ 3.8065035 -3.3711162]
Agent 2 final probabilities: [9.9923706e-01 7.6290034e-04]
Agent 2 prefers: Stag</code></pre>
<p>which is indeed the Nash equilibrium<sup>2</sup>.</p>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>Our differentiable agents successfully discovered that mutual cooperation (both playing Stag) is the Nash equilibrium in the Stag Hunt game. This approach can scale to continuous action spaces and handle n-player games, and this framework is also compositional (we can swap in different policy architectures, loss functions, or optimization algorithms to explore different solution concepts or learning dynamics)<sup>3</sup>.</p>
<p>Gradient descent doesn’t guarantee convergence to Nash equilibria in all games. Zero-sum games may cycle, games with multiple equilibria depend on initialization, and simultaneous updates can lead to instability.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>PPAD-complete. See <a href="https://people.csail.mit.edu/costis/simplified.pdf">here</a> or <a href="https://arxiv.org/abs/1103.2709">here</a>.↩︎</p></li>
<li id="fn2"><p>One of them. Running over and over again you can also see the game converge to [Hare, Hare].↩︎</p></li>
<li id="fn3"><p>In a future post we will hopefully take composition further, and compose game inputs/outputs.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Games</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/game_theory/posts/gradient_learning_nash/</guid>
  <pubDate>Thu, 21 Aug 2025 04:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/game_theory/posts/gradient_learning_nash/Albert_Gleizes_La_Chasse_1911_oil_on_canvas.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Differentiable Game Canonicalization</title>
  <link>https://demonstrandom.com/game_theory/posts/canonical_games/</link>
  <description><![CDATA[ 





<p><a href="https://en.wikipedia.org/wiki/Composition_IX"><img src="https://demonstrandom.com/game_theory/posts/canonical_games/Doesburg-Compositie-IX.jpg" class="img-fluid" style="width:45.0%" alt="Theo van Doesburg. *Composition IX*. (1917)"></a></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>Given two games, how can we tell if they are “strategically equivalent”?</p>
<p>For example, consider the following 2x2 games:</p>
<p>Game A: <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Barray%7D%7Bc%7Ccc%7D%0A&amp;%20L%20&amp;%20R%20%5C%5C%0A%5Chline%0AU%20&amp;%20(3,3)%20&amp;%20(0,5)%20%5C%5C%0AD%20&amp;%20(5,0)%20&amp;%20(1,1)%0A%5Cend%7Barray%7D%0A"></p>
<p>Game B: <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Barray%7D%7Bc%7Ccc%7D%0A&amp;%20X%20&amp;%20Y%20%5C%5C%0A%5Chline%0AA%20&amp;%20(10,10)%20&amp;%20(25,4)%20%5C%5C%0AB%20&amp;%20(4,25)%20&amp;%20(16,16)%0A%5Cend%7Barray%7D%0A"></p>
<p>They have different players, action labels, and payoff values. But strategically, both are “equivalent” (to <a href="https://en.wikipedia.org/wiki/Battle_of_the_sexes_(game_theory)">Battle of the Sexes</a>).</p>
<p>In this post, we build a game “canonicalizer” for 2x2 strict ordinal games. This allows a user to quickly identify the game type based on the payoff matrix<sup>1</sup>. Furthermore, this implementation is end-to-end differentiable, so it can be plugged into a PyTorch deep learning pipeline.</p>
<p>Example use cases might be fast lookup in a “game zoo” database, curriculum learning for agents (train on progressively “harder” games), boundary analysis<sup>2</sup>, or graph search over ordinal neighborhoods.</p>
</section>
<section id="whats-in-a-game" class="level1">
<h1>What’s in a Game?</h1>
<p>We consider a game to be “finite normal form” if the following conditions are all true:</p>
<ol type="1">
<li>There is a finite set of <img src="https://latex.codecogs.com/png.latex?n"> players, indexed by <img src="https://latex.codecogs.com/png.latex?i">.</li>
<li>Each player has <img src="https://latex.codecogs.com/png.latex?k"> possible finite pure strategies they can play, denoted <img src="https://latex.codecogs.com/png.latex?S_i%20=%20%7B1,2,...k%7D">.</li>
<li>There is a payoff function <img src="https://latex.codecogs.com/png.latex?u_i"> for each player such that</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_i:%20S_1%20%5Ctimes%20S_2%20%5Ctimes%20...%20%5Ctimes%20S_%7Bn%7D%20%5Cto%20%5Cmathbb%7BR%7D%0A"></p>
<p>We can stack these to form a tensor:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AU%20%5Cin%20%5Cmathbb%7BR%7D%5E%7BP%20%5Ctimes%20S_1%20%5Ctimes%20...%20%5Ctimes%20S_%7BP%7D%7D%0A"></p>
<p>For a 2-player, 2-strategy game, this is simply two 2x2 matrices, or one 2x2x2 tensor<sup>3</sup>.</p>
<p>We say a game is a “strict ordinal” game if all the payoffs for each player are distinct. For 2×2 games, each player has exactly 4 outcomes, ranked 1st through 4th (or 0-3 in my implementation).</p>
<p>There are <img src="https://latex.codecogs.com/png.latex?4!%20%5Ctimes%204!%20=%20576"> possible strict ordinal 2x2 games, but many are “strategically equivalent”. In their <a href="https://sl4librarian.files.wordpress.com/2016/12/goforthrobinson-the-topology-of-the-2x2-games-a-new-periodic-table.pdf">2005 book</a>, Robinson and Goforth note that accounting for “strategic equivalence” leaves us with 144 strict ordinal 2x2 games. They go on to arrange these in a periodic table<sup>4</sup>:</p>
<p><a href="https://commons.wikimedia.org/wiki/File:2x2games-topology_intro110201.pdf"><img src="https://demonstrandom.com/game_theory/posts/canonical_games/2x2games-topology_intro.jpg" class="img-fluid" style="width:100.0%" alt="*Topology of 2x2 Ordinal Games with Payoff Families*. (2005)"></a></p>
<p>The periodic table organizes the 144 canonical strict ordinal 2x2 games into a structured topology. Each cell represents a unique game type, with well-known games highlighted in colored regions. Moving horizontally changes one player’s preference ordering, while vertical movement affects the other player’s. Adjacent games differ by a single ordinal swap, creating natural “neighborhoods” in game space.</p>
</section>
<section id="strategic-equivalence" class="level1">
<h1>Strategic Equivalence</h1>
<p>Two games are “strategically equivalent” if “rational” players would behave identically in both games. There are different ways to formalize this notion. Commonly, we require that the games have the same Nash equilibria<sup>5</sup>.</p>
<section id="nash-equilibria" class="level2">
<h2 class="anchored" data-anchor-id="nash-equilibria">Nash Equilibria</h2>
<p>A Nash equilibrium is a situation where no player could gain more by changing their own strategy (holding all other players’ strategies fixed).</p>
<p>Formally, a strategy profile <img src="https://latex.codecogs.com/png.latex?s%5E*%20=%20(s_1%5E*,%20...,%20s_n%5E*)"> is a Nash equilibrium if for all players <img src="https://latex.codecogs.com/png.latex?i"> and all alternative strategies <img src="https://latex.codecogs.com/png.latex?s_i%20%5Cin%20S_i">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_i(s_i%5E*,%20s_%7B-i%7D%5E*)%20%5Cgeq%20u_i(s_i,%20s_%7B-i%7D%5E*)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?s_%7B-i%7D"> denotes the strategies of all players except <img src="https://latex.codecogs.com/png.latex?i">.</p>
<p>In other words, each player’s strategy is a best response to the other players’ strategies. For 2x2 games, this can be computed by checking each cell to see if either player wants to deviate unilaterally.</p>
</section>
<section id="equilibria-preserving-transformations" class="level2">
<h2 class="anchored" data-anchor-id="equilibria-preserving-transformations">Equilibria-Preserving Transformations</h2>
<p>It’s well-known<sup>6</sup> that Nash equilibria are preserved under the following three actions:</p>
<ol type="1">
<li>per‑player positive affine transforms.</li>
<li>permuting actions per player.</li>
<li>permuting player order.</li>
</ol>
<section id="positive-affine-transformations" class="level3">
<h3 class="anchored" data-anchor-id="positive-affine-transformations">Positive Affine Transformations</h3>
<p>For each player <img src="https://latex.codecogs.com/png.latex?i">, let <img src="https://latex.codecogs.com/png.latex?%0Au'_i(s)%5C;=%5C;a_i%5C,u_i(s)+b_i,%5Cqquad%20a_i%3E0,%5C;%20b_i%5Cin%5Cmathbb%7BR%7D.%0A"></p>
<p><strong>Claim.</strong> Best responses and Nash equilibria are unchanged by <img src="https://latex.codecogs.com/png.latex?u_i%5Cmapsto%20u'_i">.</p>
<p><strong>Proof.</strong> For fixed each player <img src="https://latex.codecogs.com/png.latex?i">, <img src="https://latex.codecogs.com/png.latex?s_%7B-i%7D">, <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign%7D%0A%5Carg%5Cmax_%7Bs_i%7D%20u'_i(s_i,s_%7B-i%7D)%20&amp;=%20%5Carg%5Cmax_%7Bs_i%7D%5Cbig(a_i%5C,u_i(s_i,s_%7B-i%7D)+b_i%5Cbig)%5C%5C%0A&amp;=%20%5Carg%5Cmax_%7Bs_i%7D%20u_i(s_i,s_%7B-i%7D)%0A%5Cend%7Balign%7D%0A"> because <img src="https://latex.codecogs.com/png.latex?z%5Cmapsto%20a_i%20z+b_i"> is strictly increasing. Thus, the Nash set is invariant.<img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
</section>
<section id="action-permutations" class="level3">
<h3 class="anchored" data-anchor-id="action-permutations">Action Permutations</h3>
<p>Let <img src="https://latex.codecogs.com/png.latex?%5Cpi_i:S_i%5Cto%20S_i"> be a permutation for each player <img src="https://latex.codecogs.com/png.latex?i">. Define the relabeled game by <img src="https://latex.codecogs.com/png.latex?%0Au_i%5E%5Cpi(s_1,%5Cldots,s_n)=u_i%5C!%5Cbig(%5Cpi_1%5E%7B-1%7D(s_1),%5Cldots,%5Cpi_n%5E%7B-1%7D(s_n)%5Cbig).%0A"></p>
<p><strong>Claim.</strong> Nash equilibria are preserved under action relabeling.</p>
<p><strong>Proof.</strong> Consider a strategy profile <img src="https://latex.codecogs.com/png.latex?s%5E*%20=%20(s_1%5E*,%20%5Cldots,%20s_n%5E*)"> that is a Nash equilibrium in the original game. We show that <img src="https://latex.codecogs.com/png.latex?s%5E%5Cpi%20=%20(%5Cpi_1(s_1%5E*),%20%5Cldots,%20%5Cpi_n(s_n%5E*))"> is a Nash equilibrium in the relabeled game.</p>
<p>In the original game, for each player <img src="https://latex.codecogs.com/png.latex?i"> and any alternative strategy <img src="https://latex.codecogs.com/png.latex?s_i">: <img src="https://latex.codecogs.com/png.latex?u_i(s_i%5E*,%20s_%7B-i%7D%5E*)%20%5Cgeq%20u_i(s_i,%20s_%7B-i%7D%5E*)"></p>
<p>In the relabeled game, for any deviation to action <img src="https://latex.codecogs.com/png.latex?t_i%20%5Cin%20S_i">: <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign%7D%0Au_i%5E%5Cpi(t_i,%20%5Cpi_%7B-i%7D(s_%7B-i%7D%5E*))%20&amp;=%20u_i(%5Cpi_i%5E%7B-1%7D(t_i),%20s_%7B-i%7D%5E*)%20%5C%5C%0A&amp;%5Cleq%20u_i(s_i%5E*,%20s_%7B-i%7D%5E*)%20%5C%5C%0A&amp;=%20u_i%5E%5Cpi(%5Cpi_i(s_i%5E*),%20%5Cpi_%7B-i%7D(s_%7B-i%7D%5E*))%0A%5Cend%7Balign%7D%0A"></p>
<p>Thus no player can improve by deviating in the relabeled game.</p>
<p>The mapping <img src="https://latex.codecogs.com/png.latex?s%20%5Cmapsto%20(%5Cpi_1(s_1),%20%5Cldots,%20%5Cpi_n(s_n))"> is a bijection between strategy profiles, establishing a one-to-one correspondence between Nash equilibria in the original and relabeled games.</p>
</section>
<section id="player-permutations" class="level3">
<h3 class="anchored" data-anchor-id="player-permutations">Player Permutations</h3>
<p>Let <img src="https://latex.codecogs.com/png.latex?%5Csigma"> be a permutation of players. Define <img src="https://latex.codecogs.com/png.latex?G%5E%5Csigma"> by reindexing: <img src="https://latex.codecogs.com/png.latex?%0AS'_i:=S_%7B%5Csigma%5E%7B-1%7D(i)%7D,%5Cqquad%0Au'_i(s'):=u_%7B%5Csigma%5E%7B-1%7D(i)%7D%5C!%5Cbig(T_%7B%5Csigma%5E%7B-1%7D%7D(s')%5Cbig),%0A"> where <img src="https://latex.codecogs.com/png.latex?T_%7B%5Csigma%5E%7B-1%7D%7D"> reorders the profile <img src="https://latex.codecogs.com/png.latex?s'"> back to the original coordinate order.</p>
<p><strong>Claim.</strong> Permuting player order preserves Nash equilibria.</p>
<p><strong>Proof.</strong> Similar to action permutations. Consider a strategy profile <img src="https://latex.codecogs.com/png.latex?s%5E*%20=%20(s_1%5E*,%20%5Cldots,%20s_n%5E*)"> that is a Nash equilibrium in the original game. We show that <img src="https://latex.codecogs.com/png.latex?s'"> defined by <img src="https://latex.codecogs.com/png.latex?s'_i%20=%20s%5E*_%7B%5Csigma%5E%7B-1%7D(i)%7D"> is a Nash equilibrium in <img src="https://latex.codecogs.com/png.latex?G%5E%5Csigma">.</p>
<p>In the original game, for each player <img src="https://latex.codecogs.com/png.latex?j"> and any alternative strategy <img src="https://latex.codecogs.com/png.latex?s_j">: <img src="https://latex.codecogs.com/png.latex?u_j(s_j%5E*,%20s_%7B-j%7D%5E*)%20%5Cgeq%20u_j(s_j,%20s_%7B-j%7D%5E*)"></p>
<p>In the reindexed game <img src="https://latex.codecogs.com/png.latex?G%5E%5Csigma">, for player <img src="https://latex.codecogs.com/png.latex?i"> considering deviation to strategy <img src="https://latex.codecogs.com/png.latex?t_i%20%5Cin%20S'_i">: <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign%7D%0Au'_i(t_i,%20s'_%7B-i%7D)%20&amp;=%20u_%7B%5Csigma%5E%7B-1%7D(i)%7D(T_%7B%5Csigma%5E%7B-1%7D%7D(t_i,%20s'_%7B-i%7D))%20%5C%5C%0A&amp;=%20u_%7B%5Csigma%5E%7B-1%7D(i)%7D(t_i,%20s%5E*_%7B-%5Csigma%5E%7B-1%7D(i)%7D)%20%5C%5C%0A&amp;%5Cleq%20u_%7B%5Csigma%5E%7B-1%7D(i)%7D(s%5E*_%7B%5Csigma%5E%7B-1%7D(i)%7D,%20s%5E*_%7B-%5Csigma%5E%7B-1%7D(i)%7D)%20%5C%5C%0A&amp;=%20u'_i(s'_i,%20s'_%7B-i%7D)%0A%5Cend%7Balign%7D%0A"></p>
<p>Thus no player can improve by deviating in the reindexed game.</p>
<p>The mapping <img src="https://latex.codecogs.com/png.latex?s%20%5Cmapsto%20s'"> where <img src="https://latex.codecogs.com/png.latex?s'_i%20=%20s_%7B%5Csigma%5E%7B-1%7D(i)%7D"> is a bijection between strategy profiles, establishing a one-to-one correspondence between Nash equilibria in the original and reindexed games. <img src="https://latex.codecogs.com/png.latex?%5Csquare"></p>
</section>
</section>
<section id="example" class="level2">
<h2 class="anchored" data-anchor-id="example">Example</h2>
<p>Returning to our original example, we can transform Game A into Game B via:</p>
<ol type="1">
<li>Affine transformation: Transform both players payoffs by <img src="https://latex.codecogs.com/png.latex?f(x)%20%5Cto%203x%20+%207">.</li>
<li>Relabel actions: <img src="https://latex.codecogs.com/png.latex?U%20%5Cto%20D"> for Player 1, <img src="https://latex.codecogs.com/png.latex?L%20%5Cto%20R"> for Player 2</li>
<li>Relabel players: Swap Player 1 and Player 2</li>
</ol>
<p>Thus, Game A and Game B are in some sense “equivalent”. We can also think about decomposing the original game into a tuple consisting of the canonical id, permutations on the row and columns, and an affine transformation, wherein Game A and Game B are equal in the first entry.</p>
</section>
</section>
<section id="differentiable-permutations" class="level1">
<h1>Differentiable Permutations</h1>
<p>Our goal is to construct an invariant, differentiable, and deterministic function that maps a given game to it’s canonical representation. To do this, we will need to implement differentiable versions of each type of transformation that preserves the Nash equilibria. Since positive affine transformations are already differentiable, we just need a way to differentiably handle permutations. For this post we solve the differentiability problem by using the Sinkhorn algorithm to generate “soft” permutations<sup>7</sup>.</p>
<section id="doubly-stochastic-matrices" class="level2">
<h2 class="anchored" data-anchor-id="doubly-stochastic-matrices">Doubly-Stochastic Matrices</h2>
<p>Permutation matrices are discrete, but we can approximate them with doubly-stochastic matrices (non-negative matrices where rows and columns sum to 1).</p>
<p>A permutation matrix <img src="https://latex.codecogs.com/png.latex?P"> has exactly one 1 in each row and column, with all other entries being 0. For example, the permutation that swaps two items: <img src="https://latex.codecogs.com/png.latex?%0AP%20=%20%5Cbegin%7Bbmatrix%7D%200%20&amp;%201%20%5C%5C%201%20&amp;%200%20%5Cend%7Bbmatrix%7D%0A"></p>
<p>A doubly-stochastic matrix relaxes this constraint: entries can be any values in <img src="https://latex.codecogs.com/png.latex?%5B0,1%5D">, as long as each row sums to 1 and each column sums to 1. For example: <img src="https://latex.codecogs.com/png.latex?%0AP_%5Ctext%7Bsoft%7D%20=%20%5Cbegin%7Bbmatrix%7D%200.2%20&amp;%200.8%20%5C%5C%200.8%20&amp;%200.2%20%5Cend%7Bbmatrix%7D%0A"></p>
<p>This “soft” permutation mostly swaps the items (0.8 weight) but keeps some probability mass (0.2) on not swapping. As we make the entries more extreme (closer to 0 or 1), we approach a hard permutation.</p>
</section>
<section id="sinkhorn-algorithm" class="level2">
<h2 class="anchored" data-anchor-id="sinkhorn-algorithm">Sinkhorn Algorithm</h2>
<p>The Sinkhorn algorithm iteratively normalizes a matrix to make it doubly-stochastic. We start with a matrix <img src="https://latex.codecogs.com/png.latex?M"> containing positive entries <img src="https://latex.codecogs.com/png.latex?s">.</p>
<p>We first create a cost matrix</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AC_%7Bij%7D%20=%20(s_i%20-%20p_j)%5E2%0A"> where <img src="https://latex.codecogs.com/png.latex?p_j"> are target positions.</p>
<p>Then, we convert to “soft positions” <img src="https://latex.codecogs.com/png.latex?M_%7Bij%7D%20=%20%5Cexp(-C_%7Bij%7D/%5Ctau)"> with temperature <img src="https://latex.codecogs.com/png.latex?%5Ctau%20%3E%200">.</p>
<p>Finally, we alternate normalizing the rows and columns until converged (usually 20-30 iterations).</p>
</section>
<section id="practical-considerations" class="level2">
<h2 class="anchored" data-anchor-id="practical-considerations">Practical Considerations</h2>
<p>The algorithm is <a href="https://projecteuclid.org/journals/annals-of-mathematical-statistics/volume-35/issue-2/A-Relationship-Between-Arbitrary-Positive-Matrices-and-Doubly-Stochastic-Matrices/10.1214/aoms/1177703591.full">guaranteed to converge to a unique solution for strictly positive matrices</a>.</p>
<p>Since we use <img src="https://latex.codecogs.com/png.latex?M%20=%20%5Cexp(-C/%5Ctau)">, all entries are positive. In practice, we work in log-space for numerical stability. As temperature <img src="https://latex.codecogs.com/png.latex?%5Ctau%20%5Cto%200">, the soft permutation approaches a hard permutation while remaining differentiable.</p>
<p>When scores are identical, the cost matrix has ties and the soft permutation becomes ambiguous. We break ties using secondary criteria:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0As_i%5E%7B%5Ctext%7Bfinal%7D%7D%20=%20s_i%5E%7B%5Ctext%7Bmean%7D%7D%20+%20%5Cepsilon_1%20%5Ccdot%20s_i%5E%7B%5Ctext%7Bvar%7D%7D%20+%20%5Cepsilon_2%20%5Ccdot%20s_i%5E%7B%5Ctext%7Bmax%7D%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?s_i%5E%7B%5Ctext%7Bmean%7D%7D"> is the mean score, <img src="https://latex.codecogs.com/png.latex?s_i%5E%7B%5Ctext%7Bvar%7D%7D"> is the variance, <img src="https://latex.codecogs.com/png.latex?s_i%5E%7B%5Ctext%7Bmax%7D%7D"> is the maximum, and <img src="https://latex.codecogs.com/png.latex?%5Cepsilon_1,%20%5Cepsilon_2%20%5Cll%201"> are tiny weights. This ensures a deterministic ordering even for symmetric games.</p>
<p>Finally, we need to handle permutations on multiple axes. To manage this, we compute all permutations from the <em>original</em> ordinal rankings, then apply them in a fixed order. This ensures the canonicalization is consistent and differentiable end-to-end.</p>
</section>
</section>
<section id="implementation" class="level1">
<h1>Implementation</h1>
<p>Now that we have differentiable permutations, here is our general approach for implementing the key functions:</p>
<ol type="1">
<li>Convert payoffs to ordinal rankings.</li>
<li>Apply soft permutations to sort players and actions.</li>
<li>Use temperature annealing to sharpen soft permutations.</li>
<li>Generate a unique hash to identify the game.</li>
</ol>
<p>We’ll organize this code as a <code>GameCanonicalizer</code> class. The remaining functions will be methods.</p>
<div id="82ae2ce4" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torchsort</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch </span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nn</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> hashlib</span>
<span id="cb1-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> scipy.optimize <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> linear_sum_assignment <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> hungarian</span>
<span id="cb1-7"></span>
<span id="cb1-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> GameCanonicalizer(nn.Module):</span>
<span id="cb1-9">    </span>
<span id="cb1-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(</span>
<span id="cb1-11">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, </span>
<span id="cb1-12">            num_players: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, </span>
<span id="cb1-13">            tau_players: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, </span>
<span id="cb1-14">            tau_actions: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, </span>
<span id="cb1-15">            sinkhorn_iters: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>,</span>
<span id="cb1-16">            rank_reg: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-4</span>, </span>
<span id="cb1-17">            tiny_tie: Tuple[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-3</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>)</span>
<span id="cb1-18">            ):</span>
<span id="cb1-19">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb1-20"></span>
<span id="cb1-21">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> num_players</span>
<span id="cb1-22"></span>
<span id="cb1-23">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.rank_reg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rank_reg</span>
<span id="cb1-24">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tau_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tau_players</span>
<span id="cb1-25">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tau_actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tau_actions</span>
<span id="cb1-26">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sinkhorn_iters <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sinkhorn_iters</span>
<span id="cb1-27">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tiny_tie <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tiny_tie</span></code></pre></div>
</div>
<section id="ordination" class="level2">
<h2 class="anchored" data-anchor-id="ordination">Ordination</h2>
<p>First, we create our ordinal values using torchsort’s <code>soft_rank</code> function, which <a href="https://arxiv.org/abs/2002.08871">uses projections onto the permutahedron to generate differentiable ranks</a>.</p>
<div id="6417fd7c" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> ordinate(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, payoffs: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb2-2">        original_shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs.shape</span>
<span id="cb2-3">        flattened <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs.reshape(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_players, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) </span>
<span id="cb2-4">        rankings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torchsort.soft_rank(flattened, regularization_strength<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.rank_reg)</span>
<span id="cb2-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> rankings.view(original_shape)</span></code></pre></div>
</div>
</section>
<section id="soft-permutations" class="level2">
<h2 class="anchored" data-anchor-id="soft-permutations">Soft Permutations</h2>
<p>Next, we need to implement our soft permutations.</p>
<section id="sinkhorn" class="level3">
<h3 class="anchored" data-anchor-id="sinkhorn">Sinkhorn</h3>
<p>We’ll start with the Sinkhorn algorithm itself:</p>
<div id="0717fdce" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sinkhorn(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, log_alpha: torch.Tensor, n_iters: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>, eps: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb3-2">        log_P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> log_alpha</span>
<span id="cb3-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_iters):</span>
<span id="cb3-4">            log_P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> log_P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> torch.logsumexp(log_P, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, keepdim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># rownorm</span></span>
<span id="cb3-5">            log_P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> log_P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> torch.logsumexp(log_P, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, keepdim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># colnorm</span></span>
<span id="cb3-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.exp(log_P).clamp_min(eps)</span></code></pre></div>
</div>
<p>We convert to log-space, then alternate normalizing the rows and columns, as described above. Finally, we exponentiate again.</p>
</section>
<section id="converting-scores-to-permutations" class="level3">
<h3 class="anchored" data-anchor-id="converting-scores-to-permutations">Converting Scores to Permutations</h3>
<p>Now that we have the Sinkhorn method, we need to run it. The <code>soft_perm_from_score</code>s method creates these soft permutations from scores by first normalizing scores to <img src="https://latex.codecogs.com/png.latex?%5B0,1%5D">, then computing a quadratic cost matrix between scores and target positions, then applying Sinkhorn.</p>
<div id="bb2b8188" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> soft_perm_from_scores(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, scores: torch.Tensor, tau: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>, n_iters: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb4-2">        n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> scores.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb4-3">        positions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.linspace(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, n, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>scores.device, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>scores.dtype)</span>
<span id="cb4-4">        s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> scores.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (scores.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> scores.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-12</span>) </span>
<span id="cb4-5">        cost <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (s[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> positions[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>  </span>
<span id="cb4-6">        log_alpha <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>cost <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> tau)  </span>
<span id="cb4-7">        P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sinkhorn(log_alpha, n_iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>n_iters) </span>
<span id="cb4-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> P  </span></code></pre></div>
</div>
</section>
<section id="scoring-functions-with-tie-breaking" class="level3">
<h3 class="anchored" data-anchor-id="scoring-functions-with-tie-breaking">Scoring Functions with Tie-Breaking</h3>
<p>Where do we get the scores from in the previous section? The <code>_action_scores</code> and <code>_player_scores</code> methods compute summary statistics for each action or player from the ordinal tensor. Both use the mean as the primary score, with variance and maximum as tie-breakers weighted by tiny constants. This ensures a deterministic ordering even for symmetric games where multiple permutations could be valid.</p>
<div id="cc5d769c" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _action_scores(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ordinal: torch.Tensor, player_idx: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-2">        axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(ordinal.ndim))</span>
<span id="cb5-3">        reduce_axes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [a <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> axes <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> player_idx]</span>
<span id="cb5-4">        mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ordinal.mean(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>reduce_axes)</span>
<span id="cb5-5">        var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ordinal.var(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>reduce_axes, unbiased<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb5-6">        mx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ordinal.amax(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>reduce_axes)</span>
<span id="cb5-7">        w_var, w_max <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tiny_tie</span>
<span id="cb5-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> w_var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> w_max <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> mx  </span>
<span id="cb5-9"></span>
<span id="cb5-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _player_scores(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ordinal: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb5-11">        P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ordinal.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb5-12">        flat <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ordinal.reshape(P, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)   </span>
<span id="cb5-13">        mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> flat.mean(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-14">        var  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> flat.var(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, unbiased<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb5-15">        mx   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> flat.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).values</span>
<span id="cb5-16">        w_var, w_max <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tiny_tie</span>
<span id="cb5-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> w_var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> w_max <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> mx </span></code></pre></div>
</div>
</section>
<section id="applying-permutations-to-tensors" class="level3">
<h3 class="anchored" data-anchor-id="applying-permutations-to-tensors">Applying Permutations to Tensors</h3>
<p>We also need to be able to apply permutations to tensors. The <code>_mode_matmul</code> method applies a permutation matrix to a specific axis of a tensor. Since matrix multiplication only works on 2D tensors, we permute dimensions to bring the target axis to the front, apply the permutation matrix transpose (which maps items to their sorted positions), then permute back. This allows us to sort along any axis of our multi-dimensional payoff tensor.</p>
<div id="44c3e46e" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb6-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _mode_matmul(t: torch.Tensor, P: torch.Tensor, axis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb6-3"></span>
<span id="cb6-4">        perm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(t.ndim))</span>
<span id="cb6-5">        perm[axis], perm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> perm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], perm[axis]</span>
<span id="cb6-6">        t_perm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> t.permute(perm) </span>
<span id="cb6-7">        n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> t_perm.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb6-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> P.shape <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> (n, n)</span>
<span id="cb6-9">        </span>
<span id="cb6-10">        t_sorted <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensordot(P.T, t_perm, dims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]))  </span>
<span id="cb6-11">        inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(t.ndim))</span>
<span id="cb6-12">        inv[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], inv[axis] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> inv[axis], inv[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb6-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> t_sorted.permute(inv)</span></code></pre></div>
</div>
</section>
</section>
<section id="forward-pass" class="level2">
<h2 class="anchored" data-anchor-id="forward-pass">Forward Pass</h2>
<p>Now that we have the above methods, we can put them together. The forward method orchestrates the soft canonicalization process. First, it converts payoffs to ordinal rankings. Then it computes action permutations for each player from the original ordinals and applies them to their respective axes (axes 1, 2, etc.). Finally, it computes and applies the player permutation on axis 0. The key insight is that action permutations are computed from the original ordinals before any transformations, ensuring consistency—each player’s actions are sorted based on their own payoffs, not influenced by other permutations.</p>
<div id="183285a9" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, payoffs: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb7-2">        P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb7-3">        ordinated_payoffs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ordinate(payoffs)</span>
<span id="cb7-4"></span>
<span id="cb7-5">        P_actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb7-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(P):</span>
<span id="cb7-7">            s_actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._action_scores(ordinated_payoffs[i], player_idx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>i)</span>
<span id="cb7-8">            P_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.soft_perm_from_scores(s_actions, tau<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tau_actions, n_iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sinkhorn_iters)</span>
<span id="cb7-9">            P_actions.append(P_i)</span>
<span id="cb7-10"></span>
<span id="cb7-11">        canon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ordinated_payoffs</span>
<span id="cb7-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, P_i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(P_actions):</span>
<span id="cb7-13">            canon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._mode_matmul(canon, P_i, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i)</span>
<span id="cb7-14"></span>
<span id="cb7-15">        s_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._player_scores(canon)</span>
<span id="cb7-16">        P_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.soft_perm_from_scores(s_players, tau<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tau_players, n_iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sinkhorn_iters)</span>
<span id="cb7-17">        canon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._mode_matmul(canon, P_players, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb7-18"></span>
<span id="cb7-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> canon, P_players, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(P_actions)</span></code></pre></div>
</div>
</section>
<section id="hard-canonicalization" class="level2">
<h2 class="anchored" data-anchor-id="hard-canonicalization">Hard Canonicalization</h2>
<p>We also want a “hard” path to verify everything is working properly. We can map the “soft” permutations back to “hard” permutations and then use those to recover the discrete cases.</p>
<section id="project-soft-permutations-to-hard" class="level3">
<h3 class="anchored" data-anchor-id="project-soft-permutations-to-hard">Project Soft Permutations to Hard</h3>
<p>The <code>_project_soft_to_perm</code> method converts a soft permutation matrix to a hard permutation (using the <a href="https://en.wikipedia.org/wiki/Hungarian_algorithm">Hungarian algorithm</a>). We add tiny tie-breaking noise to ensure deterministic results even when the soft matrix has ambiguous assignments.</p>
<div id="257269ea" class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb8-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _project_soft_to_perm(P_soft: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb8-3">        n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> P_soft.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb8-4">        idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.arange(n, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P_soft.device, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>P_soft.dtype)</span>
<span id="cb8-5">        eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-11</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (idx[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.73</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> idx[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :])</span>
<span id="cb8-6"></span>
<span id="cb8-7">        cost <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>P_soft <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> eps).detach().cpu().numpy()</span>
<span id="cb8-8">        r, c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hungarian(cost)</span>
<span id="cb8-9">        Pi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros_like(P_soft)</span>
<span id="cb8-10">        Pi[r, c] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb8-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Pi</span></code></pre></div>
</div>
</section>
<section id="lexicographic-ordering-for-symmetric-games" class="level3">
<h3 class="anchored" data-anchor-id="lexicographic-ordering-for-symmetric-games">Lexicographic Ordering For Symmetric Games</h3>
<p>The <code>_axis_lexperm</code> method performs lexicographic sorting along a specified axis, which is crucial for handling symmetric games like Matching Pennies. It treats each slice along the axis as a multi-digit number in base (<code>max_rank</code> + 1) and sorts these “numbers” to get a canonical ordering. The <code>_permute_along_axis</code> helper applies the resulting permutation to reorder the tensor. This deterministic tie-breaking ensures that even perfectly symmetric games get a unique canonical form.</p>
<div id="60f23d4d" class="cell" data-execution_count="9">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _axis_lexperm(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ranks: torch.Tensor, axis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb9-2"></span>
<span id="cb9-3">        perm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(ranks.ndim))</span>
<span id="cb9-4">        perm[axis], perm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> perm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], perm[axis]</span>
<span id="cb9-5">        X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ranks.permute(perm) </span>
<span id="cb9-6"></span>
<span id="cb9-7">        n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb9-8">        S <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X.reshape(n, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb9-9"></span>
<span id="cb9-10">        maxv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(S.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>().item()) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> S.numel() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb9-11">        base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> maxv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb9-12"></span>
<span id="cb9-13">        K <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(n, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float64, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>S.device)</span>
<span id="cb9-14">        pow_ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb9-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(S.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb9-16">            K <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> (S[:, j].to(torch.float64)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> pow_</span>
<span id="cb9-17">            pow_ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> base</span>
<span id="cb9-18"></span>
<span id="cb9-19">        order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.argsort(K, stable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb9-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> order</span>
<span id="cb9-21"></span>
<span id="cb9-22">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb9-23">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _permute_along_axis(t: torch.Tensor, order: torch.Tensor, axis: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb9-24">        perm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(t.ndim))</span>
<span id="cb9-25">        perm[axis], perm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> perm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], perm[axis]</span>
<span id="cb9-26">        t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> t.permute(perm)</span>
<span id="cb9-27">        t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> t0.index_select(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, order.to(t0.device))</span>
<span id="cb9-28">        inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(t.ndim))</span>
<span id="cb9-29">        inv[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], inv[axis] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> inv[axis], inv[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb9-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> t0.permute(inv)</span></code></pre></div>
</div>
</section>
<section id="integer-ordinals" class="level3">
<h3 class="anchored" data-anchor-id="integer-ordinals">Integer Ordinals</h3>
<p>The <code>_integerize_ordinals</code> method converts soft ordinal rankings to hard integer ranks (0, 1, 2, 3 for 2×2 games). It adds tiny deterministic noise based on the <a href="https://softwareengineering.stackexchange.com/questions/402542/where-do-magic-hashing-constants-like-0x9e3779b9-and-0x9e3779b1-come-from">golden ratio</a> to break ties, then uses argsort twice to get proper rankings. This ensures each player’s outcomes are mapped to distinct integers while preserving the ordering from the soft ranks.</p>
<div id="83699d5c" class="cell" data-execution_count="10">
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@staticmethod</span></span>
<span id="cb10-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _integerize_ordinals(ord_tensor: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb10-3">        P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ord_tensor.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb10-4">        M <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ord_tensor[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].numel()</span>
<span id="cb10-5">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb10-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(P):</span>
<span id="cb10-7">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ord_tensor[p].flatten()</span>
<span id="cb10-8">            idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.arange(M, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.device, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.dtype)</span>
<span id="cb10-9">            x_eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ((idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.61803398875</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Golden ratio trick</span></span>
<span id="cb10-10">            order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.argsort(x_eps, stable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)      </span>
<span id="cb10-11">            ranks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.empty_like(order)</span>
<span id="cb10-12">            ranks[order] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.arange(M, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.device)</span>
<span id="cb10-13">            out.append(ranks.view_as(ord_tensor[p]))</span>
<span id="cb10-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.stack(out, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).to(torch.int32)</span></code></pre></div>
</div>
</section>
<section id="putting-it-together" class="level3">
<h3 class="anchored" data-anchor-id="putting-it-together">Putting it Together</h3>
<p>The <code>hard_canonical</code> method performs the complete canonicalization with hard permutations. It follows the same flow as the soft version but uses the Hungarian algorithm via <code>_project_soft_to_perm</code> to convert each soft permutation to a hard one. After applying player and action permutations, it performs an additional lexicographic sorting step on the integer ordinals to handle symmetric games. This final step ensures a unique canonical form even when the initial permutations leave multiple valid orderings.</p>
<div id="5e051fe6" class="cell" data-execution_count="11">
<div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hard_canonical(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, payoffs: torch.Tensor):</span>
<span id="cb11-2">        P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb11-3"></span>
<span id="cb11-4">        ordinal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ordinate(payoffs)</span>
<span id="cb11-5"></span>
<span id="cb11-6">        Pi_actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb11-7">        hard <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ordinal</span>
<span id="cb11-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(P):</span>
<span id="cb11-9">            s_actions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._action_scores(ordinal[i], player_idx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>i)</span>
<span id="cb11-10">            P_i_soft <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.soft_perm_from_scores(s_actions, tau<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tau_actions, n_iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sinkhorn_iters)</span>
<span id="cb11-11">            Pi_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._project_soft_to_perm(P_i_soft.detach())</span>
<span id="cb11-12">            hard <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._mode_matmul(hard, Pi_i, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i)</span>
<span id="cb11-13">            Pi_actions.append(Pi_i)</span>
<span id="cb11-14"></span>
<span id="cb11-15">        s_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._player_scores(hard)</span>
<span id="cb11-16">        P_players_soft <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.soft_perm_from_scores(s_players, tau<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tau_players, n_iters<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sinkhorn_iters)</span>
<span id="cb11-17">        Pi_players <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._project_soft_to_perm(P_players_soft.detach())</span>
<span id="cb11-18">        hard <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._mode_matmul(hard, Pi_players, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb11-19"></span>
<span id="cb11-20">        ranks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._integerize_ordinals(hard)</span>
<span id="cb11-21"></span>
<span id="cb11-22">        order0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._axis_lexperm(ranks, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb11-23"></span>
<span id="cb11-24">        E0_h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.eye(P, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>hard.device, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>hard.dtype)[order0]</span>
<span id="cb11-25">        hard  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._mode_matmul(hard, E0_h, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb11-26"></span>
<span id="cb11-27">        ranks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._permute_along_axis(ranks, order0, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb11-28"></span>
<span id="cb11-29">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(P):</span>
<span id="cb11-30">            ord_i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._axis_lexperm(ranks, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i)</span>
<span id="cb11-31"></span>
<span id="cb11-32">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># float path</span></span>
<span id="cb11-33">            n_i  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hard.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i]</span>
<span id="cb11-34">            Ei_h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.eye(n_i, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>hard.device, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>hard.dtype)[ord_i]</span>
<span id="cb11-35">            hard  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._mode_matmul(hard, Ei_h, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i)</span>
<span id="cb11-36"></span>
<span id="cb11-37">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># int path</span></span>
<span id="cb11-38">            ranks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._permute_along_axis(ranks, ord_i, axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i)</span>
<span id="cb11-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> hard, Pi_players, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(Pi_actions)</span></code></pre></div>
</div>
</section>
<section id="hashing" class="level3">
<h3 class="anchored" data-anchor-id="hashing">Hashing</h3>
<p>The <code>class_id</code> method generates a unique identifier for each game’s strategic equivalence class. It runs the hard canonicalization, converts the result to integer ordinals, then computes a SHA-256 hash of the binary representation. This allows us to quickly identify when two games are strategically equivalent.</p>
<div id="b817750d" class="cell" data-execution_count="12">
<div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> class_id(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, payoffs: torch.Tensor):</span>
<span id="cb12-2">        hard_ord, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.hard_canonical(payoffs)</span>
<span id="cb12-3">        ranks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._integerize_ordinals(hard_ord)</span>
<span id="cb12-4">        b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ranks.detach().cpu().numpy().tobytes()</span>
<span id="cb12-5">        digest <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hashlib.sha256(b).hexdigest()</span>
<span id="cb12-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> digest[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>], digest, ranks</span></code></pre></div>
</div>
</section>
</section>
</section>
<section id="complexity-analysis" class="level1">
<h1>Complexity Analysis</h1>
<p>The canonicalization process has the following complexity for 2×2 games: - Ordinal ranking: <img src="https://latex.codecogs.com/png.latex?O(n%20%5Clog%20n)"> where n = 4 (number of outcomes per player) - Sinkhorn iterations: <img src="https://latex.codecogs.com/png.latex?O(m%20%C3%97%20n%5E2)"> where <img src="https://latex.codecogs.com/png.latex?m"> is the number of iterations - Hungarian algorithm for hard permutation: <img src="https://latex.codecogs.com/png.latex?O(n%5E3)"></p>
<p>For 2×2 games, this is effectively constant time. For larger games with <img src="https://latex.codecogs.com/png.latex?n"> players and <img src="https://latex.codecogs.com/png.latex?k"> strategies each: - Space: <img src="https://latex.codecogs.com/png.latex?O(n%20%C3%97%20k%5En)"> for the payoff tensor - Time: <img src="https://latex.codecogs.com/png.latex?O(n%20%C3%97%20k%5En%20%C3%97%20log(k%5En))"> for ranking + <img src="https://latex.codecogs.com/png.latex?O(n%20%C3%97%20m%20%C3%97%20k%5E2)"> for permutations</p>
</section>
<section id="edge-cases-and-limitations" class="level1">
<h1>Edge Cases and Limitations</h1>
<p>This implementation handles several tricky cases:</p>
<ol type="1">
<li><p>Symmetric games (e.g., Matching Pennies): The lexicographic ordering ensures deterministic canonicalization even when multiple permutations could be valid.</p></li>
<li><p>Ties in ordinal rankings: While we assume strict ordinal games, near-ties are handled via the tie-breaking parameters <code>tiny_tie</code>.</p></li>
</ol>
<p>The main limitation is the restriction to strict ordinal games. Games with payoff ties would require a different approach or explicit tie-breaking rules.</p>
</section>
<section id="example-1" class="level1">
<h1>Example</h1>
<p>Let’s look at a simple example.</p>
<div id="1d1a47ca" class="cell" data-execution_count="13">
<div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Our original games from the introduction</span></span>
<span id="cb13-2">game_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([</span>
<span id="cb13-3">    [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>]],</span>
<span id="cb13-4">    [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>]],</span>
<span id="cb13-5">])</span>
<span id="cb13-6"></span>
<span id="cb13-7">game_b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([</span>
<span id="cb13-8">    [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">25.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">16.0</span>]],</span>
<span id="cb13-9">    [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">25.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">16.0</span>]],</span>
<span id="cb13-10">])</span>
<span id="cb13-11"></span>
<span id="cb13-12">canon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> GameCanonicalizer(num_players<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb13-13"></span>
<span id="cb13-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Ordinal conversion</span></span>
<span id="cb13-15">ord_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> canon.ordinate(game_a)</span>
<span id="cb13-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [[2, 0], [3, 1]] for P1, [[2, 3], [0, 1]] for P2</span></span>
<span id="cb13-17"></span>
<span id="cb13-18">ord_b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> canon.ordinate(game_b) </span>
<span id="cb13-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [[1, 3], [0, 2]] for P1, [[1, 0], [3, 2]] for P2</span></span>
<span id="cb13-20"></span>
<span id="cb13-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Canonicalization</span></span>
<span id="cb13-22">canon_a, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> canon.hard_canonical(game_a)</span>
<span id="cb13-23">canon_b, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> canon.hard_canonical(game_b)</span>
<span id="cb13-24"></span>
<span id="cb13-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Verify they're the same</span></span>
<span id="cb13-26">id_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> canon.class_id(game_a)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb13-27">id_b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> canon.class_id(game_b)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb13-28"></span>
<span id="cb13-29"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Game A canonical: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>canon_a<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-30"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Game B canonical: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>canon_b<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-31"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Game A ID: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>id_a<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-32"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Game B ID: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>id_b<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-33"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Same game? </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>id_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> id_b<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># True!</span></span></code></pre></div>
</div>
</section>
<section id="tests" class="level1">
<h1>Tests</h1>
<p>Let’s now look at some tests. We’ll start with some helper functions<sup>8</sup>.</p>
<section id="helper-functions" class="level2">
<h2 class="anchored" data-anchor-id="helper-functions">Helper Functions</h2>
<div id="5c616139" class="cell" data-execution_count="14">
<div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> affine(payoffs: torch.Tensor, a0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, b0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, a1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, b1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb14-2">    P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs.clone().<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>()</span>
<span id="cb14-3">    P[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> a0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> P[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> b0</span>
<span id="cb14-4">    P[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> a1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> P[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> b1</span>
<span id="cb14-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> P</span>
<span id="cb14-6"></span>
<span id="cb14-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> permute_actions(payoffs: torch.Tensor, perm0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), perm1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb14-8">    P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs.clone()</span>
<span id="cb14-9">    P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> P[:, perm0, :]   </span>
<span id="cb14-10">    P <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> P[:, :, perm1]  </span>
<span id="cb14-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> P</span>
<span id="cb14-12"></span>
<span id="cb14-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> swap_players(payoffs: torch.Tensor) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> torch.Tensor:</span>
<span id="cb14-14">    P0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)  </span>
<span id="cb14-15">    P1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> payoffs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb14-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.stack([P1, P0], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb14-17"></span>
<span id="cb14-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> class_id(canon, U):</span>
<span id="cb14-19">    short, full, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> canon.class_id(U)</span>
<span id="cb14-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> full </span></code></pre></div>
</div>
<p>The first three functions will let us create variants of games. The last function returns the full <code>class_id</code> for a given game.</p>
</section>
<section id="game-payoffs" class="level2">
<h2 class="anchored" data-anchor-id="game-payoffs">Game Payoffs</h2>
<p>Here’s a number of “base games” we can test:</p>
<div id="01e2f247" class="cell" data-execution_count="15">
<div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> prisoners_dilemma():</span>
<span id="cb15-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.tensor([</span>
<span id="cb15-3">        [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>]],</span>
<span id="cb15-4">        [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>]],</span>
<span id="cb15-5">    ])</span>
<span id="cb15-6"></span>
<span id="cb15-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> stag_hunt():</span>
<span id="cb15-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.tensor([</span>
<span id="cb15-9">        [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>]],</span>
<span id="cb15-10">        [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>]],</span>
<span id="cb15-11">    ])</span>
<span id="cb15-12"></span>
<span id="cb15-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> battle_of_sexes():</span>
<span id="cb15-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.tensor([</span>
<span id="cb15-15">        [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>]],</span>
<span id="cb15-16">        [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>]],</span>
<span id="cb15-17">    ])</span>
<span id="cb15-18"></span>
<span id="cb15-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> matching_pennies():</span>
<span id="cb15-20">    P1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>],[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>]])</span>
<span id="cb15-21">    P2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>P1</span>
<span id="cb15-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.stack([P1, P2], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb15-23"></span>
<span id="cb15-24"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hawk_dove():</span>
<span id="cb15-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.tensor([</span>
<span id="cb15-26">        [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>]],</span>
<span id="cb15-27">        [[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>],[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>]],</span>
<span id="cb15-28">    ])</span></code></pre></div>
</div>
</section>
<section id="stability-under-transformation" class="level2">
<h2 class="anchored" data-anchor-id="stability-under-transformation">Stability Under Transformation</h2>
<p>We can combine our base payoffs with our transfomation functions to build variants:</p>
<div id="babe4e6b" class="cell" data-execution_count="16">
<div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> pd_variants():</span>
<span id="cb16-2">    U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> prisoners_dilemma()</span>
<span id="cb16-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb16-4">        U,</span>
<span id="cb16-5">        affine(U, a0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, b0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>, a1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, b1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>),                         </span>
<span id="cb16-6">        permute_actions(U, perm0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), perm1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),                       </span>
<span id="cb16-7">        swap_players(U),                                                    </span>
<span id="cb16-8">        permute_actions(affine(U, a0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, b0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.0</span>, a1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7</span>, b1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)),  </span>
<span id="cb16-9">    ]</span>
<span id="cb16-10"></span>
<span id="cb16-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sh_variants():</span>
<span id="cb16-12">    U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> stag_hunt()</span>
<span id="cb16-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb16-14">        U,</span>
<span id="cb16-15">        affine(U, a0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>, b0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>, a1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, b1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>),</span>
<span id="cb16-16">        permute_actions(U, perm0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), perm1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb16-17">        swap_players(U),</span>
<span id="cb16-18">        permute_actions(affine(U, a0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>, b0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, a1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>, b1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb16-19">    ]</span>
<span id="cb16-20"></span>
<span id="cb16-21"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> bos_variants():</span>
<span id="cb16-22">    U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> battle_of_sexes()</span>
<span id="cb16-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb16-24">        U,</span>
<span id="cb16-25">        permute_actions(U, perm0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), perm1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),   </span>
<span id="cb16-26">        swap_players(U),                                  </span>
<span id="cb16-27">        affine(U, a0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, b0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, a1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, b1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>),       </span>
<span id="cb16-28">        permute_actions(affine(U, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10.0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)),</span>
<span id="cb16-29">    ]</span>
<span id="cb16-30"></span>
<span id="cb16-31"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mp_variants():</span>
<span id="cb16-32">    U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> matching_pennies()</span>
<span id="cb16-33">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb16-34">        U,</span>
<span id="cb16-35">        permute_actions(U, perm0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), perm1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),     </span>
<span id="cb16-36">        swap_players(U),                                   </span>
<span id="cb16-37">        affine(U, a0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>, b0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, a1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, b1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7.0</span>),       </span>
<span id="cb16-38">        permute_actions(affine(U, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)),</span>
<span id="cb16-39">    ]</span>
<span id="cb16-40"></span>
<span id="cb16-41"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> hd_variants():</span>
<span id="cb16-42">    U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hawk_dove()</span>
<span id="cb16-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb16-44">        U,</span>
<span id="cb16-45">        permute_actions(U, perm0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), perm1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb16-46">        swap_players(U),</span>
<span id="cb16-47">        affine(U, a0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, b0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>, a1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>, b1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>),</span>
<span id="cb16-48">        permute_actions(affine(U, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.0</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb16-49">    ]</span></code></pre></div>
</div>
<p>Then we can test this as so:</p>
<div id="e71482bc" class="cell" data-execution_count="17">
<div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> test_family_equivalence(canonicalizer):</span>
<span id="cb17-2">    families <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb17-3">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PrisonersDilemma"</span>, pd_variants()),</span>
<span id="cb17-4">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"StagHunt"</span>,         sh_variants()),</span>
<span id="cb17-5">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BattleOfSexes"</span>,    bos_variants()),</span>
<span id="cb17-6">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MatchingPennies"</span>,  mp_variants()),</span>
<span id="cb17-7">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"HawkDove"</span>,         hd_variants()),</span>
<span id="cb17-8">    ]</span>
<span id="cb17-9"></span>
<span id="cb17-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, variants <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> families:</span>
<span id="cb17-11">        ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [class_id(canonicalizer, U) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> U <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> variants]</span>
<span id="cb17-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(ids)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: expected all variants equivalent, got IDs: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ids<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb17-13">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"[OK] </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">. ID </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ids[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (x</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(variants)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)"</span>)</span></code></pre></div>
</div>
</section>
<section id="negative-controls" class="level2">
<h2 class="anchored" data-anchor-id="negative-controls">Negative Controls</h2>
<div id="ae588395" class="cell" data-execution_count="18">
<div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> test_negative_controls(canonicalizer):</span>
<span id="cb18-2">    reps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb18-3">        prisoners_dilemma(),</span>
<span id="cb18-4">        stag_hunt(),</span>
<span id="cb18-5">        battle_of_sexes(),</span>
<span id="cb18-6">        matching_pennies(),</span>
<span id="cb18-7">        hawk_dove(),</span>
<span id="cb18-8">    ]</span>
<span id="cb18-9">    ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [class_id(canonicalizer, U) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> U <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> reps]</span>
<span id="cb18-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(ids)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ids), <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Negative control failed: collisions among base games: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ids<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb18-11">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[OK] Negative controls. Distinct IDs: "</span>, ids)</span></code></pre></div>
</div>
</section>
<section id="stability-under-small-noise" class="level2">
<h2 class="anchored" data-anchor-id="stability-under-small-noise">Stability Under Small Noise</h2>
<div id="87912997" class="cell" data-execution_count="19">
<div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> test_stability_small_noise(canonicalizer):</span>
<span id="cb19-2">    U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> stag_hunt()</span>
<span id="cb19-3">    base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> class_id(canonicalizer, U)</span>
<span id="cb19-4">    eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-9</span></span>
<span id="cb19-5">    U_noisy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> U <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> eps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> torch.randn_like(U)</span>
<span id="cb19-6">    pert <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> class_id(canonicalizer, U_noisy)</span>
<span id="cb19-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> pert, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tiny noise changed ID; consider epsilon tie-handling."</span></span>
<span id="cb19-8">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[OK] Stability to tiny noise."</span>)</span></code></pre></div>
</div>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>We have successfully constructed a differentiable way to produce a unique identifier for each strict ordinal 2x2 game. In the next post in this series, we will expand on this capability (or incorporate it into another pipeline for some practical use case).</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Or vice-versa, to retrieve the game behavior based on some identifier.↩︎</p></li>
<li id="fn2"><p>i.e.&nbsp;“When does Stag Hunt turn into Chicken?” Hopefully more on this in a future post.↩︎</p></li>
<li id="fn3"><p>For an <img src="https://latex.codecogs.com/png.latex?n">-player, <img src="https://latex.codecogs.com/png.latex?k">-strategy game, we would need an <img src="https://latex.codecogs.com/png.latex?n%5Ctimes%20k%20%5Ctimes%20...%20%5Ctimes%20k"> tensor (<img src="https://latex.codecogs.com/png.latex?n"> copies of <img src="https://latex.codecogs.com/png.latex?k">).↩︎</p></li>
<li id="fn4"><p>I will hopefully also further investigate the algebraic structure of games, and this periodic table, in a future post.↩︎</p></li>
<li id="fn5"><p>Other forms of strategic equivalence might revolve around dominance relationships or best-response correspondences.↩︎</p></li>
<li id="fn6"><p>See Myerson (1991), <em>Game Theory: Analysis of Conflict</em>, Chapter 3.↩︎</p></li>
<li id="fn7"><p>There are alternative methods. From Claude: NeuralSort (Grover et al., 2019), SoftSort (Prillo &amp; Eisenschlos, 2020), Fast Differentiable Sorting (Blondel et al., 2020), Optimal Transport Sort (Cuturi et al., 2019), Blackbox Differentiable Ranking (Vlastelica et al., 2020; Rolínek et al., 2020), Relaxed Bubble Sort (Petersen et al., 2021). We could also use non-differentiable methods, like REINFORCE. Sinkhorn should be sufficient for these purposes: it’s well-known, controllable, and stable, even for near-ties (common in symmetric games). If we need a faster algorithm later we can swap Sinkhorn for something else.↩︎</p></li>
<li id="fn8"><p>Disclosure: ChatGPT and Claude helped write the tests.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Games</category>
  <category>Exposition</category>
  <category>Research</category>
  <guid>https://demonstrandom.com/game_theory/posts/canonical_games/</guid>
  <pubDate>Tue, 19 Aug 2025 04:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/game_theory/posts/canonical_games/Doesburg-Compositie-IX.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>SINDy Method for Learning Dynamical Systems</title>
  <link>https://demonstrandom.com/ml/posts/sindy/</link>
  <description><![CDATA[ 





<p><img src="https://demonstrandom.com/ml/posts/sindy/lorenz.png" class="img-fluid" style="width:55.0%" alt="Lorenz Equations"></p>
<section id="introduction" class="level1">
<h1>Introduction</h1>
<p>In <a href="../../../ml/posts/linear_methods_for_dynamical_systems/index.html">the last post</a> I looked at DMD and EDMD, two methods for analyzing dynamical systems. Both methods look at pairs of snapshots <img src="https://latex.codecogs.com/png.latex?(x_k,%20x_%7Bk+1%7D)"> and fit a linear update rule that best predicts the next snapshot. By inspecting that linear map’s eigenvalues and eigenvectors we can learn which patterns dominate and how fast they grow or decay.</p>
<p>Unfortunately, both of these methods rely on a set of features you have chosen in advance. Without the right features you might miss important physics; too many features and the model will become unwieldy. Additionally, while the spectral methods provide some useful insight, they might not have the most easily interpretable output.</p>
<p>What we need is a sparse method, so that we can test a wide number of features and only select the relevant ones for the dynamics, ideally to recover an actual set of interpretable governing equations.</p>
</section>
<section id="sparse-identification-of-nonlinear-dynamical-systems-sindy" class="level1">
<h1>Sparse Identification of Nonlinear Dynamical Systems (SINDy)</h1>
<p>SINDy creates a library of candidate basis functions for the eigenfunctions (like EDMD), then does an L1-regularized<sup>1</sup> regression to determine which ones to use. There are two formulations: discrete-time and continuous-time.</p>
<section id="discrete-time" class="level2">
<h2 class="anchored" data-anchor-id="discrete-time">Discrete Time</h2>
<p>This setup is the same as in DMD and EDMD. We have a discrete-time dynamical system of form:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax_%7Bt+1%7D%20=%20F(x_t)%0A"></p>
<p>We have our data stream, which we use to create our matrix <img src="https://latex.codecogs.com/png.latex?X">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BX%7D%20=%20%5Cbegin%7Bbmatrix%7D%0A%5Cmathbf%7Bx%7D%5ET(t_0)%20%5C%5C%0A%5Cmathbf%7Bx%7D%5ET(t_1)%20%5C%5C%0A%5Cvdots%20%5C%5C%0A%5Cmathbf%7Bx%7D%5ET(t_%7Bm-1%7D)%0A%5Cend%7Bbmatrix%7D%20=%20%5Cbegin%7Bbmatrix%7D%0Ax_1(t_0)%20&amp;%20x_2(t_0)%20&amp;%20%5Ccdots%20&amp;%20x_n(t_0)%20%5C%5C%0Ax_1(t_1)%20&amp;%20x_2(t_1)%20&amp;%20%5Ccdots%20&amp;%20x_n(t_1)%20%5C%5C%0A%5Cvdots%20&amp;%20%5Cvdots%20&amp;%20%5Cddots%20&amp;%20%5Cvdots%20%5C%5C%0Ax_1(t_%7Bm-1%7D)%20&amp;%20x_2(t_%7Bm-1%7D)%20&amp;%20%5Ccdots%20&amp;%20x_n(t_%7Bm-1%7D)%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>and its time-shifted counterpart <img src="https://latex.codecogs.com/png.latex?Y">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BY%7D%20=%20%5Cbegin%7Bbmatrix%7D%0A%5Cmathbf%7Bx%7D%5ET(t_1)%20%5C%5C%0A%5Cmathbf%7Bx%7D%5ET(t_2)%20%5C%5C%0A%5Cvdots%20%5C%5C%0A%5Cmathbf%7Bx%7D%5ET(t_m)%0A%5Cend%7Bbmatrix%7D%20=%20%5Cbegin%7Bbmatrix%7D%0Ax_1(t_1)%20&amp;%20x_2(t_1)%20&amp;%20%5Ccdots%20&amp;%20x_n(t_1)%20%5C%5C%0Ax_1(t_2)%20&amp;%20x_2(t_2)%20&amp;%20%5Ccdots%20&amp;%20x_n(t_2)%20%5C%5C%0A%5Cvdots%20&amp;%20%5Cvdots%20&amp;%20%5Cddots%20&amp;%20%5Cvdots%20%5C%5C%0Ax_1(t_m)%20&amp;%20x_2(t_m)%20&amp;%20%5Ccdots%20&amp;%20x_n(t_m)%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>Now, we need some “library matrix” of relevant basis functions applied to the data (similar to EDMD). This may look something like:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7B%5CTheta%7D(%5Cmathbf%7BX%7D)%20:=%20%5B%5Ctheta_1(X),%20%5Ctheta_2(X),...,%5Ctheta_%7B%5Cell%7D(X)%5D%0A"></p>
<p>This matrix can be quite large. For example, if the basis functions are drawn from combinations of the monomials <img src="https://latex.codecogs.com/png.latex?x_1">, <img src="https://latex.codecogs.com/png.latex?x_2">, and <img src="https://latex.codecogs.com/png.latex?x_3"> (up to quadratic), we would have something like:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CTheta(X)%20=%20%5Cbegin%7Bbmatrix%7D%0A%7C%20&amp;%20%7C%20&amp;%20%7C%20&amp;%20%7C%20&amp;%20%7C%20&amp;%20%7C%20&amp;%20&amp;%20%7C%20%5C%5C%0A1%20&amp;%20x_1%20&amp;%20x_2%20&amp;%20x_3%20&amp;%20x_1%5E2%20&amp;%20x_1x_2%20&amp;%20%5Ccdots%20&amp;%20x_3%5E2%20%5C%5C%0A%7C%20&amp;%20%7C%20&amp;%20%7C%20&amp;%20%7C%20&amp;%20%7C%20&amp;%20%7C%20&amp;%20&amp;%20%7C%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>We are looking for a sparse set of coefficients <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7B%5CXi%7D"> such that</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7B%5CXi%7D%20=%20%5Cbegin%7Bbmatrix%7D%0A%7C%20&amp;%20%7C%20&amp;%20&amp;%20%7C%20%5C%5C%0A%5Cxi_1%20&amp;%20%5Cxi_2%20&amp;%20%5Ccdots%20&amp;%20%5Cxi_n%20%5C%5C%0A%7C%20&amp;%20%7C%20&amp;%20&amp;%20%7C%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>This leads us to the SINDy equation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BY%7D%20=%20%5Cmathbf%7B%5CTheta%7D(%5Cmathbf%7BX%7D)%20%5Cmathbf%7B%5CXi%7D%0A"></p>
<p>Each basis function is weighted by a <img src="https://latex.codecogs.com/png.latex?%5Cxi_i">. To find the <img src="https://latex.codecogs.com/png.latex?%5Cxi_i">, we run a sparse <img src="https://latex.codecogs.com/png.latex?L1">-regularized regression:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CXi_%7Bopt%7D%20=%20%5Ctext%7Barg%20%7D%5Ctext%7Bmin%7D_%7B%5CXi%7D%20%7C%7C%5Cmathbf%7BY%7D%20-%20%5Cmathbf%7B%5CTheta%7D(%5Cmathbf%7BX%7D)%5Cmathbf%7B%5CXi%7D%20%7C%7C_%7B2%7D%20+%20%5Cmu%7C%7C%5Cmathbf%7B%5CXi%7D%7C%7C_%7B1%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmu"> is a parameter controlling the strength of the regularization.</p>
<p>To see the mapping at a single time-step, take the <img src="https://latex.codecogs.com/png.latex?k">-th row of <img src="https://latex.codecogs.com/png.latex?%5CTheta(X)">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cboxed%7B%5C;x%5E%7B(k+1)%5Ctop%7D%20=%20%5CTheta%5C!%5Cbigl(x%5E%7B(k)%7D%5Cbigr)%5C,%5CXi%5C;%7D,%0A%5Cquad%20k=0,%5Cdots,m-1,%0A"></p>
<p>where</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CTheta(x%5E%7B(k)%7D)=%5B%5Ctheta_1(x%5E%7B(k)%7D),%5Cdots,%5Ctheta_L(x%5E%7B(k)%7D)%5D%5Cin%5Cmathbb%20R%5E%7B1%5Ctimes%20L%7D%0A"></p>
</section>
<section id="continuous-time" class="level2">
<h2 class="anchored" data-anchor-id="continuous-time">Continuous Time</h2>
<p>A more typical formulation is to assume a dynamical system of the form</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%7Bx%7D%20=%20%5Cfrac%7Bdx%7D%7Bdt%7D%20=%20f(x(t))%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?x"> is the state (possibly a vector) and <img src="https://latex.codecogs.com/png.latex?t"> is the time. We have moved from discrete-time to continuous time. We now seek to learn the vector field <img src="https://latex.codecogs.com/png.latex?f">, which relates the current state to the rate of change in the state, rather than the next state. Luckily, as we describe in <a href="../../../ml/posts/linear_methods_for_dynamical_systems/index.html">the previous post</a> the Koopman eigenfunctions satisfy:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Cphi(x)%20=%20K%5Cphi(x)%20=%20%5Clambda%20%5Cphi(x)%0A"></p>
<p>By the chain rule, we also have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bd%7D%7Bdt%7D%5Cphi(x)%20=%20%5Cnabla%20%5Cphi(x)%20%5Ccdot%20%5Cdot%7Bx%7D%20=%20%5Cnabla%20%5Cphi(x)%20%5Ccdot%20f(x)%0A"></p>
<p>Combining the two, we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla%20%5Cphi(x)%20%5Ccdot%20f(x)%20=%20%5Clambda%20%5Cphi(x)%0A"></p>
<p>So we can approximate the eigenfunctions via regression<sup>2</sup>. Approximate <img src="https://latex.codecogs.com/png.latex?f(x)"> by a sparse weighted sum of dictionary elements:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Af(x)%5C;=%5C;%5Csum_%7Bj=1%7D%5E%7BL%7D%5Cxi_%7Bj%7D%5C,%5Ctheta_%7Bj%7D(x)%5C;=%5C;%5Cmathbf%7B%5CTheta%7D(x)%5C,%5Cmathbf%7B%5CXi%7D%20%20%20%20%0A"></p>
<p>This gives:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla%5Cphi(x)%5C,%5Ccdot%5C,%5Cmathbf%7B%5CTheta%7D(x)%5C,%5Cmathbf%7B%5CXi%7D%0A%20%20%5C;=%5C;%5Clambda%5C,%5Cphi(x)%20%20%0A"></p>
<p>Evaluating at the sample states:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla%5Cphi%5C!%5Cbigl(%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cbigr)%5C,%0A%20%20%20%20%5Ccdot%5C,%5Cmathbf%7B%5CTheta%7D%5C!%5Cbigl(%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cbigr)%5C,%0A%20%20%20%20%5Cmathbf%7B%5CXi%7D%0A%20%20%5C;=%5C;%0A%20%20%5Clambda%5C,%0A%20%20%5Cphi%5C!%5Cbigl(%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cbigr),%0A%20%20%5Cquad%20i=1,%5Cdots,m%20%20%20%20%20%0A"></p>
<p>Now we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7B%5Cdot%20X%7D%20%5C;=%5C;%20%5Cmathbf%7B%5CTheta%7D(%5Cmathbf%7BX%7D)%5C,%5Cmathbf%7B%5CXi%7D,%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%25%20(5)%0A%5Cquad%5Ctext%7Bwhere%7D%0A%5C;%0A%5Cmathbf%7B%5Cdot%20X%7D%20=%0A%5Cbegin%7Bbmatrix%7D%0A%20%20%5Cdot%7B%5Cmathbf%20x%7D%5E%7B(1)%5C!%5Ctop%7D%5C%5C%0A%20%20%5Cdot%7B%5Cmathbf%20x%7D%5E%7B(2)%5C!%5Ctop%7D%5C%5C%0A%20%20%5Cvdots%5C%5C%0A%20%20%5Cdot%7B%5Cmathbf%20x%7D%5E%7B(m)%5C!%5Ctop%7D%0A%5Cend%7Bbmatrix%7D,%5C;%0A%5Cmathbf%7B%5CTheta%7D(%5Cmathbf%7BX%7D)%20=%0A%5Cbegin%7Bbmatrix%7D%0A%20%20%5Cmathbf%7B%5CTheta%7D(x%5E%7B(1)%7D)%5C%5C%0A%20%20%5Cmathbf%7B%5CTheta%7D(x%5E%7B(2)%7D)%5C%5C%0A%20%20%5Cvdots%5C%5C%0A%20%20%5Cmathbf%7B%5CTheta%7D(x%5E%7B(m)%7D)%0A%5Cend%7Bbmatrix%7D%20%20%20%20%0A"></p>
<p>and so we can solve the continuous-time problem with the following regression</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7B%5CXi%7D_%7Bopt%7D%0A%20%20%20%5C;=%5C;%0A%20%20%20%5Coperatorname*%7Barg%5C,min%7D_%7B%5Cmathbf%7B%5CXi%7D%7D%0A%20%20%20%5CBigl%5C%7C%0A%20%20%20%20%20%20%20%5Cmathbf%7B%5Cdot%20X%7D%20-%20%5Cmathbf%7B%5CTheta%7D(%5Cmathbf%7BX%7D)%5C,%5Cmathbf%7B%5CXi%7D%0A%20%20%20%5CBigr%5C%7C_%7B2%7D%0A%20%20%20%5C;+%5C;%0A%20%20%20%5Cmu%5C,%5C%7C%5Cmathbf%7B%5CXi%7D%5C%7C_%7B1%7D%20%20%20%20%0A"></p>
<p>Conveniently, exactly the same as the previous regression equation, except here we are approximating:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cfrac%7Bdx%7D%7Bdt%7D%20=%20f(x(t))%20=%20%5Cmathbf%7B%5CTheta%7D(x)%5Cmathbf%7B%5CXi%7D%0A"></p>
</section>
</section>
<section id="implementation" class="level1">
<h1>Implementation</h1>
<p>To implement SINDy, we need a library matrix function and a method of handling the regression. We may also want a finite differences method, to calculate derivatives.</p>
<section id="finite-differences" class="level2">
<h2 class="anchored" data-anchor-id="finite-differences">Finite Differences</h2>
<p>We will use finite differences<sup>3</sup> to approximate the time derivative by dividing small state increments by the timestep <img src="https://latex.codecogs.com/png.latex?%5CDelta%20t">.</p>
<p>At an interior point <img src="https://latex.codecogs.com/png.latex?t_i"> we use the second-order central stencil<br>
<img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%20x(t_i)%5C;%5Capprox%5C;%5Cfrac%7Bx(t_%7Bi+1%7D)%20-%20x(t_%7Bi-1%7D)%7D%7B2%5C,%5CDelta%20t%7D,%0A"></p>
<p>which cancels the first-order truncation error, yielding <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BO%7D(%5CDelta%20t%5E%7B2%7D)"> accuracy.</p>
<p>At the boundaries we fall back to the first-order forward and backward stencils: <img src="https://latex.codecogs.com/png.latex?%0A%5Cdot%20x(t_0)%5C;%5Capprox%5C;%5Cfrac%7Bx(t_1)-x(t_0)%7D%7B%5CDelta%20t%7D,%0A%5Cqquad%0A%5Cdot%20x(t_%7Bm-1%7D)%5C;%5Capprox%5C;%5Cfrac%7Bx(t_%7Bm-1%7D)-x(t_%7Bm-2%7D)%7D%7B%5CDelta%20t%7D.%0A"></p>
<div id="7434b9f3" class="cell" data-execution_count="1">
<div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> finite_difference(X, dt):</span>
<span id="cb1-2">    n_vars, n_samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X.shape</span>
<span id="cb1-3">    </span>
<span id="cb1-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Use central differences where possible, forward/backward at boundaries</span></span>
<span id="cb1-5">    dXdt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((n_samples, n_vars))</span>
<span id="cb1-6">    </span>
<span id="cb1-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Forward difference at first point</span></span>
<span id="cb1-8">    dXdt[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, :] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> dt</span>
<span id="cb1-9">    </span>
<span id="cb1-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Central differences for interior points</span></span>
<span id="cb1-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n_samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb1-12">        dXdt[i, :] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (X[:, i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X[:, i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dt)</span>
<span id="cb1-13">    </span>
<span id="cb1-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Backward difference at last point</span></span>
<span id="cb1-15">    dXdt[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, :] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (X[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> X[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> dt</span>
<span id="cb1-16">    </span>
<span id="cb1-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> dXdt</span></code></pre></div>
</div>
<p>The helper above implements exactly this scheme: it accepts a state matrix of shape <img src="https://latex.codecogs.com/png.latex?(n_%7B%5Ctext%7Bvars%7D%7D,%20n_%7B%5Ctext%7Bsamples%7D%7D)">, returns the derivative matrix <img src="https://latex.codecogs.com/png.latex?(n_%7B%5Ctext%7Bsamples%7D%7D,%20n_%7B%5Ctext%7Bvars%7D%7D)">, and also transposes <img src="https://latex.codecogs.com/png.latex?X"> so rows correspond to time snapshots for the subsequent regression.</p>
</section>
<section id="library-matrix" class="level2">
<h2 class="anchored" data-anchor-id="library-matrix">Library Matrix</h2>
<p>Next we build the candidate feature matrix <img src="https://latex.codecogs.com/png.latex?%5CTheta"> by stacking a constant term, all monomials up to poly_order, and optionally sine/cosine transforms of each state variable. Returns that matrix along with a parallel descriptions list so the sparse coefficients can later be mapped back to human-readable terms.</p>
<div id="0b56e50a" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sindy_library(X, poly_order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, include_sine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, include_cosine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>):</span>
<span id="cb2-2">    n_vars, n_samples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X.shape</span>
<span id="cb2-3">    </span>
<span id="cb2-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Start with constant term</span></span>
<span id="cb2-5">    library_functions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [np.ones(n_samples)]</span>
<span id="cb2-6">    descriptions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'1'</span>]</span>
<span id="cb2-7">    </span>
<span id="cb2-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add polynomial terms</span></span>
<span id="cb2-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> order <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, poly_order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb2-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> combo <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combinations_with_replacement(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_vars), order):</span>
<span id="cb2-11">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> order <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb2-12">                var_idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> combo[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb2-13">                library_functions.append(X[var_idx, :])</span>
<span id="cb2-14">                descriptions.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>var_idx<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span>
<span id="cb2-15">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb2-16">                term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.ones(n_samples)</span>
<span id="cb2-17">                term_desc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-18">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> var_idx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combo:</span>
<span id="cb2-19">                    term <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> X[var_idx, :]</span>
<span id="cb2-20">                    term_desc.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>var_idx<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span>
<span id="cb2-21">                library_functions.append(term)</span>
<span id="cb2-22">                descriptions.append(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'*'</span>.join(term_desc))</span>
<span id="cb2-23">    </span>
<span id="cb2-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add trigonometric terms if requested</span></span>
<span id="cb2-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> include_sine:</span>
<span id="cb2-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_vars):</span>
<span id="cb2-27">            library_functions.append(np.sin(X[i, :]))</span>
<span id="cb2-28">            descriptions.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'sin(x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)'</span>)</span>
<span id="cb2-29">            </span>
<span id="cb2-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> include_cosine:</span>
<span id="cb2-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_vars):</span>
<span id="cb2-32">            library_functions.append(np.cos(X[i, :]))</span>
<span id="cb2-33">            descriptions.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'cos(x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">)'</span>)</span>
<span id="cb2-34">    </span>
<span id="cb2-35">    Theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.column_stack(library_functions)</span>
<span id="cb2-36">    </span>
<span id="cb2-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Theta, descriptions</span></code></pre></div>
</div>
</section>
<section id="sequential-threshold-least-squares" class="level2">
<h2 class="anchored" data-anchor-id="sequential-threshold-least-squares">Sequential Threshold Least Squares</h2>
<p>We will use Sequential Threshold Least Squares to obtain the full coefficient matrix.</p>
<p>At each iteration <img src="https://latex.codecogs.com/png.latex?k">:</p>
<ol type="1">
<li><p>Set every entry with <img src="https://latex.codecogs.com/png.latex?%7C%5Cxi_%7Bij%7D%5E%7B(k)%7D%7C%20%3C%20%5Clambda_%7Breg%7D"> to zero, forcing small terms out of the model<sup>4</sup>.</p></li>
<li><p>Refit for each state component <img src="https://latex.codecogs.com/png.latex?j">, solve a new least–squares problem using only the surviving (non-zero) columns of <img src="https://latex.codecogs.com/png.latex?%5CTheta"> to get updated weights.</p></li>
<li><p>Repeat until convergence or for at most <code>max_iter</code>cycles; the final <img src="https://latex.codecogs.com/png.latex?%5CXi"> contains only the terms that remain large after repeated shrink-and-refit, giving a sparse governing equation.</p></li>
</ol>
<div id="06d1e0fa" class="cell" data-execution_count="3">
<div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sequential_threshold_least_squares(Theta, dXdt, lambda_reg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>, max_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>):</span>
<span id="cb3-2">    n_states <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dXdt.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb3-3">    </span>
<span id="cb3-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initialize coefficient matrix</span></span>
<span id="cb3-5">    Xi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.lstsq(Theta, dXdt, rcond<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb3-6">    </span>
<span id="cb3-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Iterative thresholding</span></span>
<span id="cb3-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_iter):</span>
<span id="cb3-9"></span>
<span id="cb3-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Find small coefficients to remove</span></span>
<span id="cb3-11">        small_inds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(Xi) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> lambda_reg</span>
<span id="cb3-12">        </span>
<span id="cb3-13">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Set small coefficients to zero</span></span>
<span id="cb3-14">        Xi[small_inds] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb3-15">        </span>
<span id="cb3-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Identify active (non-zero) coefficients for each state</span></span>
<span id="cb3-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_states):</span>
<span id="cb3-18">            big_inds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>small_inds[:, i]</span>
<span id="cb3-19">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(big_inds):</span>
<span id="cb3-20">                </span>
<span id="cb3-21">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Recompute non-zero coefficients using least squares</span></span>
<span id="cb3-22">                Xi[big_inds, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.lstsq(</span>
<span id="cb3-23">                    Theta[:, big_inds], dXdt[:, i], rcond<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb3-24">                )[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb3-25">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb3-26">                Xi[:, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb3-27">    </span>
<span id="cb3-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Xi</span></code></pre></div>
</div>
</section>
<section id="print_equations" class="level2">
<h2 class="anchored" data-anchor-id="print_equations"><code>print_equations</code></h2>
<p>I’ll add one bonus helper function, which will be useful later</p>
<div id="c8b46707" class="cell" data-execution_count="4">
<div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> print_equations(Xi, descriptions, feature_names<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb4-2">    n_functions, n_features <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xi.shape</span>
<span id="cb4-3">    </span>
<span id="cb4-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> feature_names <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb4-5">        feature_names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'x_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_features)]</span>
<span id="cb4-6">        </span>
<span id="cb4-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_features):</span>
<span id="cb4-8"></span>
<span id="cb4-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build equation string</span></span>
<span id="cb4-10">        terms <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb4-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n_functions):</span>
<span id="cb4-12">            coef <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Xi[j, i]</span>
<span id="cb4-13">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(coef) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-10</span>:  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Only include non-zero terms</span></span>
<span id="cb4-14">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(coef <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-10</span>:</span>
<span id="cb4-15">                    terms.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>descriptions[j]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-16">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>(coef <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-10</span>:</span>
<span id="cb4-17">                    terms.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"-</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>descriptions[j]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-18">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb4-19">                    terms.append(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>coef<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.6f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">*</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>descriptions[j]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-20">        </span>
<span id="cb4-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> terms:</span>
<span id="cb4-22">            equation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" + "</span>.join(terms).replace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" + -"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" - "</span>)</span>
<span id="cb4-23">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"d</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>feature_names[i]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">/dt = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>equation<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb4-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb4-25">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"d</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>feature_names[i]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">/dt = 0"</span>)</span>
<span id="cb4-26">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>()</span></code></pre></div>
</div>
</section>
<section id="integration" class="level2">
<h2 class="anchored" data-anchor-id="integration">Integration</h2>
<p>Putting the piece together:</p>
<div id="c80640b9" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sindy(X, dXdt, poly_order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, lambda_reg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>, include_sine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, </span>
<span id="cb5-2">          include_cosine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>, max_iter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>):</span>
<span id="cb5-3">    </span>
<span id="cb5-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build library of candidate functions</span></span>
<span id="cb5-5">    Theta, descriptions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sindy_library(</span>
<span id="cb5-6">        X, </span>
<span id="cb5-7">        poly_order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>poly_order, </span>
<span id="cb5-8">        include_sine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>include_sine, </span>
<span id="cb5-9">        include_cosine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>include_cosine</span>
<span id="cb5-10">    )</span>
<span id="cb5-11">    </span>
<span id="cb5-12">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sparse regression using sequential thresholded least squares</span></span>
<span id="cb5-13">    Xi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sequential_threshold_least_squares(Theta, dXdt, lambda_reg, max_iter)</span>
<span id="cb5-14">    </span>
<span id="cb5-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Xi, descriptions</span></code></pre></div>
</div>
<p>For a production version you may want to factor the library production out of the SINDy method.</p>
</section>
</section>
<section id="experiments" class="level1">
<h1>Experiments</h1>
<p>Let’s generate data from few known systems and see if SINDy can recover their governing equations.</p>
<section id="simple-harmonic-oscillator" class="level2">
<h2 class="anchored" data-anchor-id="simple-harmonic-oscillator">Simple Harmonic Oscillator</h2>
<section id="definition" class="level3">
<h3 class="anchored" data-anchor-id="definition">Definition</h3>
<p>The simple harmonic oscillator is described by the following second-order ODE:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ax''%20+%20%5Comega%5E2%20x%20=%200%0A"></p>
<p>If we let: <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign%7D%0Ax_0%20&amp;:=%20x%20%5C%5C%0Ax_1%20&amp;:=%20%5Cfrac%7Bdx%7D%7Bdt%7D%0A%5Cend%7Balign%7D%0A"></p>
<p>We can then derive</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign%7D%0A%5Cfrac%7Bdx_0%7D%7Bdt%7D%20&amp;=%20%5Cfrac%7Bdx%7D%7Bdt%7D%20=%20x_1%20%5C%5C%0A%5Cfrac%7Bdx_1%7D%7Bdt%7D%20&amp;=%20-%20%5Comega%5E2%20x_0%0A%5Cend%7Balign%7D%0A"></p>
<p>So it has an alternative formulation as a set of coupled first-order ODEs.</p>
</section>
<section id="approximation" class="level3">
<h3 class="anchored" data-anchor-id="approximation">Approximation</h3>
<p>The simple harmonic oscillator has a well-known analytic solution: <img src="https://latex.codecogs.com/png.latex?%0Ax%20=%20%5Ccos(%5Comega%20t)%0A"></p>
<p>Let</p>
<div id="a8512f3f" class="cell" data-execution_count="6">
<div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_simple_harmonic_oscillator_data(omega<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span>, t_final<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>):</span>
<span id="cb6-2">    t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, t_final, dt)</span>
<span id="cb6-3">    </span>
<span id="cb6-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Analytical solution</span></span>
<span id="cb6-5">    x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.cos(omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t)</span>
<span id="cb6-6">    xdot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.sin(omega <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> t)</span>
<span id="cb6-7">    </span>
<span id="cb6-8">    X_train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.vstack([x, xdot])</span>
<span id="cb6-9">    </span>
<span id="cb6-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> X_train</span></code></pre></div>
</div>
<p>This will give us data for the harmonic oscillator<sup>5</sup>.</p>
<p>Now we run SINDy on this data:</p>
<div id="dc1d3349" class="cell" data-execution_count="7">
<div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_simple_harmonic_oscillator_data()</span>
<span id="cb7-2">dXdt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> finite_difference(X, dt)                </span>
<span id="cb7-3">Xi, desc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sindy(X, dXdt, poly_order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, lambda_reg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>)</span>
<span id="cb7-4">print_equations(Xi, desc, feature_names<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x_0'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x_1'</span>])</span></code></pre></div>
</div>
<p>We discover the following equations:</p>
<pre><code>dx_0/dt = 0.999925*x_1
dx_1/dt = -3.999764*x_0</code></pre>
<p>Which is close to the true generating equations:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign%7D%0A%5Cfrac%7Bdx_0%7D%7Bdt%7D%20&amp;=%20x_1%20%5C%5C%0A%5Cfrac%7Bdx_1%7D%7Bdt%7D%20&amp;=%20-4%20x_0%0A%5Cend%7Balign%7D%0A"></p>
</section>
</section>
<section id="lorenz-system" class="level2">
<h2 class="anchored" data-anchor-id="lorenz-system">Lorenz System</h2>
<section id="definition-1" class="level3">
<h3 class="anchored" data-anchor-id="definition-1">Definition</h3>
<p>The Lorenz-63 model is a three-dimensional ODE derived from a truncated Fourier expansion of the Boussinesq equations for thermal convection. In its nondimensional form the state <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D=(x,y,z)%5E%7B%5C!%5Ctop%7D"> evolves as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5Cdot%20x%20&amp;=%20%5Csigma%20%5C,(y%20-%20x),%20%5C%5C%0A%5Cdot%20y%20&amp;=%20x%20%5C,(%5Crho%20-%20z)%20-%20y,%20%5C%5C%0A%5Cdot%20z%20&amp;=%20x%5C,y%20-%20%5Cbeta%5C,z,%0A%5Cend%7Baligned%7D%0A"></p>
<p>Let us recover this equation from data.</p>
</section>
<section id="approximation-1" class="level3">
<h3 class="anchored" data-anchor-id="approximation-1">Approximation</h3>
<div id="77de8259" class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_lorenz_data(initial_conditions, sigma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, rho<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">28</span>, beta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, t_final<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>):</span>
<span id="cb9-2">    </span>
<span id="cb9-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lorenz_rhs(state, sigma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sigma, rho<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>rho, beta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>beta):</span>
<span id="cb9-4">        x, y, z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state</span>
<span id="cb9-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> np.array([</span>
<span id="cb9-6">            sigma <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> x),</span>
<span id="cb9-7">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (rho <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> z) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> y,</span>
<span id="cb9-8">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> beta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> z</span>
<span id="cb9-9">        ])</span>
<span id="cb9-10">    </span>
<span id="cb9-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Generate training data</span></span>
<span id="cb9-12">    t_train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, t_final, dt)</span>
<span id="cb9-13">    n_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(t_train)</span>
<span id="cb9-14">    </span>
<span id="cb9-15">    X_train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, n_steps))</span>
<span id="cb9-16">    X_train[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> initial_conditions </span>
<span id="cb9-17">    </span>
<span id="cb9-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n_steps):</span>
<span id="cb9-19">        k1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lorenz_rhs(X_train[:, i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb9-20">        k2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lorenz_rhs(X_train[:, i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k1)</span>
<span id="cb9-21">        k3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lorenz_rhs(X_train[:, i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k2)</span>
<span id="cb9-22">        k4 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lorenz_rhs(X_train[:, i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> k3)</span>
<span id="cb9-23">        X_train[:, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X_train[:, i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> dt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (k1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>k3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> k4)</span>
<span id="cb9-24"></span>
<span id="cb9-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> X_train</span></code></pre></div>
</div>
<p>Following the same steps:</p>
<div id="5fd5b654" class="cell" data-execution_count="9">
<div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">initial_conditions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">27</span>] </span>
<span id="cb10-2">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_lorenz_data(initial_conditions)</span>
<span id="cb10-3"></span>
<span id="cb10-4">dXdt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> finite_difference(X, dt)                </span>
<span id="cb10-5">Xi, desc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sindy(X, dXdt, poly_order<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, lambda_reg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>)</span>
<span id="cb10-6"></span>
<span id="cb10-7">print_equations(Xi, desc, feature_names<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'x'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'y'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'z'</span>])</span></code></pre></div>
</div>
<p>We discover the following equations:</p>
<pre><code>dx/dt = -9.971816*x_0 + 9.972789*x_1
dy/dt = 27.823397*x_0 - 0.970096*x_1 - 0.994849*x_0*x_2
dz/dt = -2.658203*x_2 + 0.996862*x_0*x_1</code></pre>
<p>Which is once again pretty close to the actual generating equations (rewritten): <img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Balign%7D%0A%5Cfrac%7Bdx_0%7D%7Bdt%7D%20&amp;=%20-10x_0%20+%2010x_1%20%5C%5C%0A%5Cfrac%7Bdx_1%7D%7Bdt%7D%20&amp;=%2028x_0%20-%20x_1%20-%20x_0x_2%20%5C%5C%0A%5Cfrac%7Bdx_2%7D%7Bdt%7D%20&amp;=%20-%5Cfrac%7B8%7D%7B3%7Dx_2%20+%20x_0x_1%0A%5Cend%7Balign%7D%0A"></p>
</section>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>We used SINDy to recover governing equations for two toy problems.</p>
</section>
<section id="further-reading" class="level1">
<h1>Further Reading</h1>
<ol type="1">
<li><p><a href="https://databookuw.com">Data-Driven Science and Engineering: Machine Learning, Dynamical Systems, and Control</a>.</p></li>
<li><p><a href="https://doi.org/10.1073/pnas.1517384113">Discovering governing equations from data by sparse identification of nonlinear dynamical systems</a>.</p></li>
<li><p><a href="https://doi.org/10.1073/pnas.1906995116">Data-driven discovery of coordinates and governing equations</a>.</p></li>
<li><p><a href="https://github.com/dynamicslab/pysindy">PySINDy – Sparse Identification of Nonlinear Dynamics in Python</a> — open-source implementation.</p></li>
</ol>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Also known as “LASSO” in the ML literature. I’ve seen some sources on SINDy do an L<img src="https://latex.codecogs.com/png.latex?0"> regularization here, instead; there are many variants of SINDy. You’ll want to follow the typical machine learning best practices (hold-out data, etc.) to fit the best model.↩︎</p></li>
<li id="fn2"><p>Assumes the dynamics are both continuous and differentiable. Some systems are more easily represented in the continuous time format than the discrete-time. In the Brunton &amp; Kutz book they also give an alternative method using Laurent series, and some extensions.↩︎</p></li>
<li id="fn3"><p>In production, the error from finite differences is too large and may cause SINDy to blow up. Alternatives like smoothed finite differences are available.↩︎</p></li>
<li id="fn4"><p>Despite the similar notation <img src="https://latex.codecogs.com/png.latex?%5Clambda_%7Breg%7D"> is unrelated to the Koopman eigenvalues.↩︎</p></li>
<li id="fn5"><p>This is toy data. In real life, you should probably whiten your data, nondimensionalize, etc.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Machine Learning</category>
  <category>Exposition</category>
  <guid>https://demonstrandom.com/ml/posts/sindy/</guid>
  <pubDate>Sun, 06 Jul 2025 04:00:00 GMT</pubDate>
  <media:content url="https://demonstrandom.com/ml/posts/sindy/lorenz.png" medium="image" type="image/png" height="108" width="144"/>
</item>
</channel>
</rss>
