<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>ex.rs</title>
    <description>Arthur Breitman&apos;s blog. Occasionally right.</description>
    <link>http://ex.rs/</link>
    <atom:link href="http://ex.rs/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 02 Oct 2026 14:07:59 +0000</pubDate>
    <lastBuildDate>Fri, 02 Oct 2026 14:07:59 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>A Genus-3 Polyhedron, Relaxed</title>
        <description>&lt;p&gt;&lt;em&gt;my work, AI-generated overview&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/genus3_morph.jpg&quot; alt=&quot;The polyhedron relaxing into a smooth triple torus&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ruslan Mizhaev’s &lt;a href=&quot;https://arxiv.org/abs/2609.17700&quot;&gt;Integer Realization of an Equivelar Octahedron of Genus 3&lt;/a&gt; does something I like: it takes an object previously known only abstractly and pins it down with exact integer coordinates, so every claim about it is checkable without a single floating-point comparison. Eight planar nonagonal faces, 24 vertices, 36 edges, three faces meeting at every vertex — a {9,3} equivelar map — with C₄ symmetry. Twenty pairs of faces share an edge; the remaining eight pairs share two.&lt;/p&gt;

&lt;p&gt;The genus follows from counting: 24 − 36 + 8 = −4, and χ = 2 − 2g gives g = 3.&lt;/p&gt;

&lt;p&gt;But counting is not seeing. The faces are big thin nonagons that weave past one another, and no still picture made it obvious to me that this spiky thing is a triple torus. So I built an animation that relaxes the polyhedron into a smooth genus-3 surface under surface tension at constant volume. Every keyframe, and every linear interpolation between keyframes, is certified free of self-intersection, so the surface never cheats by passing through itself. Each triangle keeps the color of the flat face it started on. By the end the three tunnels are hard to miss.&lt;/p&gt;

&lt;p style=&quot;margin:1.5em 0&quot;&gt;
&lt;iframe src=&quot;/assets/poly/&quot; title=&quot;Animation: a genus-3 polyhedron relaxing into a smooth triple torus&quot; loading=&quot;lazy&quot; style=&quot;width:100%;height:min(70vh,560px);border:1px solid rgba(0,0,0,.12);border-radius:6px;display:block&quot;&gt;&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Drag to rotate, scroll to zoom, and use the opacity slider to see through the surface. The F1–F8 checkboxes show and hide individual faces: switch off all but two and you can see exactly where those two meet, for any pair you care to pick — easiest a little way into the morph, once the razor-thin nonagons have fattened into ribbons. Every one of the 28 pairs touches, which is the 20 + 8 above: each face meets every other, so the map is neighborly. It loads about 4 MB on demand, so nothing downloads until you press the button. If the embed is too cramped, &lt;a href=&quot;/assets/poly/&quot;&gt;open it on its own page&lt;/a&gt;; the viewer and the animation data are on GitHub at &lt;a href=&quot;https://github.com/murbard/genus3-morph&quot;&gt;murbard/genus3-morph&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you to Bence Hervay for discussion on the topic.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 29 Sep 2026 00:00:00 +0000</pubDate>
        <link>http://ex.rs/a-genus-3-polyhedron-relaxed/</link>
        <guid isPermaLink="true">http://ex.rs/a-genus-3-polyhedron-relaxed/</guid>
        
        
        <category>math</category>
        
      </item>
    
      <item>
        <title>Why post-quantum, why now?</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/post_quantum.jpg&quot; alt=&quot;Unravelling curve, standing lattice&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Today marks the 12th anniversary of the &lt;a href=&quot;https://tezos.com/whitepaper.pdf&quot;&gt;Tezos white paper&lt;/a&gt;, and today, Nomadic Labs, Trilitech, and other contributors are proud to unveil the upcoming preview net for a fully post-quantum version of Tezos. This previewnet is currently being tested internally and will be made publicly available during September.&lt;/p&gt;

&lt;p&gt;Simply speaking, like every other blockchain, Tezos is secured with cryptographic techniques. These cryptographic techniques typically use a mathematical object known as an “elliptic curve”. As far as we know, classical computers cannot break these techniques without a literally astronomical amount of computation. However, once quantum computers become sufficiently large, they will be able to break them with ease. Fortunately, not all cryptographic techniques are vulnerable to quantum computers. So-called “&lt;a href=&quot;https://csrc.nist.gov/projects/post-quantum-cryptography&quot;&gt;post-quantum cryptography&lt;/a&gt;” uses algorithms that remain out of reach of quantum computers. These techniques haven’t been widely adopted yet because they are typically a bit more costly in terms of computer usage, and there hasn’t been a pressing need for them. This is changing.&lt;/p&gt;

&lt;p&gt;When Tezos began in 2014, quantum computers were a distant concern. Back then, the most advanced quantum computing experiments operated with just a few qubits. Researchers demonstrated, for instance, quantum error correction on a &lt;a href=&quot;https://arxiv.org/abs/1402.4848&quot;&gt;five-qubit superconducting device&lt;/a&gt;. NIST had not even begun its post-quantum cryptography standardization process, which &lt;a href=&quot;https://csrc.nist.gov/Projects/post-quantum-cryptography/post-quantum-cryptography-standardization&quot;&gt;formally&lt;/a&gt; began in 2016.&lt;/p&gt;

&lt;p&gt;However, from its inception, the project saw the need to evolve as critical for resilience. The &lt;a href=&quot;https://tezos.com/position-paper.pdf&quot;&gt;Tezos position paper&lt;/a&gt; explicitly anticipated having “new cryptographic primitives included in the scripting language as the need develops”, while the &lt;a href=&quot;https://tezos.com/whitepaper.pdf&quot;&gt;white paper&lt;/a&gt; emphasized that Tezos protocols could evolve by amending their own code.&lt;/p&gt;

&lt;p&gt;Quantum computing is now in a completely different state than it was 12 years ago. Google’s &lt;a href=&quot;https://blog.google/innovation-and-ai/technology/research/google-willow-quantum-chip/&quot;&gt;Willow processor&lt;/a&gt; holds 105 physical qubits and has demonstrated below-threshold quantum error correction: as the size of its encoded logical qubits increased from 3×3 to 5×5 to 7×7 physical qubits, the logical error rate halved each time. Willow by itself doesn’t present a threat to cryptography, but it shows that scalable error correction is a real, experimentally demonstrated phenomenon.&lt;/p&gt;

&lt;p&gt;At the same time, the estimated resources required to attack elliptic-curve cryptography have been falling sharply. In March 2026, &lt;a href=&quot;https://research.google/blog/safeguarding-cryptocurrency-by-disclosing-quantum-vulnerabilities-responsibly/&quot;&gt;Google published quantum circuits&lt;/a&gt; for solving the 256-bit elliptic-curve discrete logarithm problem using fewer than 1,200 logical qubits and 90 million Toffoli gates, or fewer than 1,450 logical qubits and 70 million Toffoli gates. Under Google’s hardware assumptions, this corresponds to fewer than 500,000 physical qubits, roughly a 20-fold reduction from its previous estimate.&lt;/p&gt;

&lt;p&gt;Google has set a 2029 deadline for itself to fully migrate to post-quantum cryptography. In its &lt;a href=&quot;https://blog.google/innovation-and-ai/technology/safety-security/cryptography-migration-timeline/&quot;&gt;own words&lt;/a&gt;, “We’re setting a timeline for post-quantum cryptography migration to 2029.” This is just around the corner.&lt;/p&gt;

&lt;p&gt;The first step to make Tezos post-quantum was taken with the &lt;a href=&quot;https://docs.tezos.com/architecture/governance/amendment-history&quot;&gt;Ushuaia protocol amendment&lt;/a&gt;, activated on June 30, 2026, which introduced testnet-only support, behind a feature flag, for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tz5&lt;/code&gt; accounts using ML-DSA-44, the NIST-standardized form of CRYSTALS-Dilithium under &lt;a href=&quot;https://csrc.nist.gov/pubs/fips/204/final&quot;&gt;FIPS 204&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As I’ve said before, allowing users to start safeguarding their funds with a post-quantum key is priority number one, as this can take a long time. Most people who hold cryptocurrencies do not regularly monitor technological changes, and they cannot easily be contacted. Hopefully, as progress in quantum computing becomes increasingly mainstream, more people will pay attention and take the necessary steps. Fortunately, the vast majority of keys used on Tezos are derived from seed words. If users fail to use a post-quantum key by a fencepost date, the protocol may still require submission of a post-quantum zero-knowledge proof of knowledge of the seed words.&lt;/p&gt;

&lt;p&gt;But the challenges of moving to post-quantum cryptography do not stop at user keys. The Tezos protocol relies on quantum-vulnerable primitives for at least five different functions. These are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The computation of a &lt;a href=&quot;https://octez.tezos.com/docs/alpha/randomness_generation.html&quot;&gt;VDF&lt;/a&gt;, as a source of randomness, which uses class groups.&lt;/li&gt;
  &lt;li&gt;The aggregation of consensus operations (attestations), which saves crucial space in blocks and depends on BLS signatures.&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;https://octez.tezos.com/docs/api/odoc/_html/octez-libs/Tezos_crypto_dal/Cryptobox/&quot;&gt;KZG polynomial commitments and proofs used by the DAL&lt;/a&gt;, or Data Availability Layer.&lt;/li&gt;
  &lt;li&gt;The Michelson primitive for confidential transactions, which uses the &lt;a href=&quot;https://octez.tezos.com/docs/active/sapling.html&quot;&gt;Sapling&lt;/a&gt; circuit.&lt;/li&gt;
  &lt;li&gt;The Michelson primitive for &lt;a href=&quot;https://octez.tezos.com/docs/alpha/timelock.html&quot;&gt;timelock puzzles&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, Tezos core developers are unveiling a previewnet for a post-quantum version of Tezos.&lt;/p&gt;

&lt;p&gt;Confidential transactions have already been addressed by &lt;a href=&quot;https://tzel.tezos.com/&quot;&gt;TzEL&lt;/a&gt;, and this will likely see further iterations. Timelock puzzles are an interesting primitive, but they are currently unused, so they haven’t been a priority; there are, however, various ways of providing a post-quantum version of that primitive.&lt;/p&gt;

&lt;p&gt;The VDF was an addition to the protocol that hardened our RANDAO / commit-reveal mechanism. While there are STARK-based candidate post-quantum VDFs, we instead opted to simply remove randomness from the protocol using a smooth weighted round-robin, which guarantees fairness.&lt;/p&gt;

&lt;p&gt;The biggest pieces of the puzzle were the aggregation of consensus operations and the DAL. For the former, we turned to the &lt;a href=&quot;https://github.com/leanEthereum/leanVM&quot;&gt;LeanEthereum project&lt;/a&gt;, which has developed a very efficient post-quantum STARK that can aggregate hash-based signatures extremely fast. On an M4 Max, its published CPU-only benchmark processes &lt;a href=&quot;https://github.com/leanEthereum/leanVM#benchmarks&quot;&gt;1,426 XMSS signatures per second&lt;/a&gt; in its “proven regime”, producing a 327 KiB proof. It is particularly efficient on hardware with high memory bandwidth, such as Apple’s M4-family chips.&lt;/p&gt;

&lt;p&gt;In this previewnet, we are using a version that uses Poseidon hashes, but we will soon migrate to the new version being pushed by the LeanEthereum project, &lt;a href=&quot;https://github.com/leanEthereum/leanVM-b&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;leanVM-b&lt;/code&gt;&lt;/a&gt;, which operates over a 192-bit binary field and uses the standard BLAKE2s hash function. In its current published benchmark, also on an M4 Max, it aggregates 900 XMSS signatures in 0.797 seconds, or about &lt;a href=&quot;https://github.com/leanEthereum/leanVM-b#xmss-aggregation&quot;&gt;1,129 signatures per second&lt;/a&gt;, producing a 295 KiB proof that verifies in 16.5 milliseconds. The project currently describes this implementation as highly experimental.&lt;/p&gt;

&lt;p&gt;For the DAL, after looking at several options, we opted for &lt;a href=&quot;https://eprint.iacr.org/2025/034&quot;&gt;ZODA&lt;/a&gt;. ZODA is a data-availability protocol built on two-dimensional tensor codes that is plausibly post-quantum secure (an understatement) and extremely efficient. The ZO stands for “Zero Overhead”: the protocol is designed to add essentially no communication overhead beyond the underlying encoding. It was developed by Alex Evans, Nicolas Mohnblatt, and Guillermo Angeris at Bain Capital Crypto.&lt;/p&gt;

&lt;p&gt;And there we have it: the pieces are in place and will be submitted over time as protocol amendments for Tezos to become fully post-quantum. This is a piece of protocol development we knew would come and, 12 years after the white paper, it’s finally time!&lt;/p&gt;

&lt;p&gt;We encourage bakers, enthusiasts, and all Tezos fans to stay tuned for the previewnet, coming this September. It remains experimental software and will likely break in a variety of interesting ways, but that’s precisely why we need it.&lt;/p&gt;
</description>
        <pubDate>Wed, 02 Sep 2026 00:00:00 +0000</pubDate>
        <link>http://ex.rs/why-post-quantum-why-now/</link>
        <guid isPermaLink="true">http://ex.rs/why-post-quantum-why-now/</guid>
        
        
        <category>tezos</category>
        
        <category>cryptography</category>
        
      </item>
    
      <item>
        <title>Stars Are Hard, Comparisons Are Easy</title>
        <description>&lt;p&gt;&lt;em&gt;my work, AI-generated overview&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Somewhere around 2015 I admitted to myself that my IMDB ratings were garbage: the numbers meant nothing. Asked to give a movie 7 or 8 stars, I produce noise: it depends on my mood, on the last thing I rated, on whether I’m anchoring against masterpieces or against the mediocre thing I watched on a plane. A 7 from 2013-me and a 7 from 2015-me are not the same 7. What I wanted was for my ratings to be &lt;em&gt;calibrated&lt;/em&gt;: an 8 should be a statement with content, comparable across years and genres.&lt;/p&gt;

&lt;p&gt;But here’s the thing: while I can’t rate a movie in isolation to save my life, ask me which of two movies is &lt;em&gt;better&lt;/em&gt; and I usually answer quickly, and consistently. This is an old observation — psychophysicists have known for a century that absolute judgments drift and paired comparisons are stable. So the plan wrote itself: stop rating. Compare, and let the machine infer the numbers.&lt;/p&gt;

&lt;p&gt;The machine works like this. Every item gets a hidden score, and a comparison is a noisy coin flip whose bias depends on the score difference — the same family of models behind Elo in chess. The twist is to be properly Bayesian about it: instead of a single number per movie, keep a full posterior distribution over every score, so the system knows not only the ranking but how sure it is of each part of it. That uncertainty then gets put to work: the system picks the &lt;em&gt;next&lt;/em&gt; question to ask — which two movies to show me — to learn as much as possible from each answer. And at the very end, stars fall out for free: map the posterior scores through their quantiles and an 8 finally means “better than this fraction of everything you’ve compared,” calibrated by construction.&lt;/p&gt;

&lt;p&gt;In 2022 I finally sat down to build it. The reason it turned into a project rather than an afternoon is a single integral. The Bayesian fit needs the expected value of a certain smooth loss under a Gaussian, and that integral has no closed form — the standard move is to approximate it numerically, everywhere, millions of times. But it turns out the logistic curve is almost an error-function curve, and error functions have a lovely property: the approximation &lt;em&gt;commutes with the expectation&lt;/em&gt;. Approximate first or average first, you get the same expression. Add a small correction — a handful of Gaussian bumps, fitted once — and the approximation error drops to three parts in ten million while every quantity you need for optimization stays in closed form. The whole posterior fits with textbook Newton iterations, no sampling, no quadrature. As I wrote in the paper, the closed form has no business working out as cleanly as it does. But it does, and chasing that kind of unreasonable tidiness is most of why the project is fun.&lt;/p&gt;

&lt;p&gt;The empirical side had its own surprise. Of the strategies for picking the next comparison, the greedy one — directly minimize the expected number of ranking mistakes, the very metric being scored — turns out to be the &lt;em&gt;worst&lt;/em&gt;, losing even to picking pairs at random. It keeps probing the current photo-finishes instead of spreading its questions around. The strategies that target uncertainty itself, like expected information gain, win comfortably. Optimizing your objective one step at a time is not the same as optimizing it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/ranker_active_selection.png&quot; alt=&quot;Active selection: information gain beats random beats greedy&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The project then sat mostly dormant for a few years, the way side projects do. This year I picked it back up — coding agents have lowered the activation energy of “finally do it right” to roughly zero — and finished the job: a proper write-up of the math, a test suite, and support for ties, because not every comparison has a winner. Ties brought one more pleasant surprise: a 1970 model by Davidson slots into the same closed-form machinery exactly, down to the integrals, with the tie-proneness of the domain (rare for movies, common for chess, unknown a priori) learned from the data alongside everything else.&lt;/p&gt;

&lt;p&gt;The code (a reference Python implementation and a fast C++ one) is at &lt;a href=&quot;https://github.com/murbard/ranker&quot;&gt;github.com/murbard/ranker&lt;/a&gt;, and the full write-up is in &lt;a href=&quot;https://github.com/murbard/ranker/blob/main/doc/ranker.pdf&quot;&gt;the paper (PDF)&lt;/a&gt;. As for the original itch: a decade later I still haven’t re-rated my movies. But the difference between “my ratings are garbage” and “my ratings are garbage, and I have a machine that fixes this” matters to me, and the second one is now true.&lt;/p&gt;
</description>
        <pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
        <link>http://ex.rs/stars-are-hard-comparisons-are-easy/</link>
        <guid isPermaLink="true">http://ex.rs/stars-are-hard-comparisons-are-easy/</guid>
        
        
        <category>machine-learning</category>
        
        <category>statistics</category>
        
      </item>
    
      <item>
        <title>Even a Bag of Words Has a World Model</title>
        <description>&lt;p&gt;&lt;em&gt;my work, AI-generated overview&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In October 2023, Gurnee and Tegmark published &lt;a href=&quot;https://arxiv.org/abs/2310.02207&quot;&gt;&lt;em&gt;Language Models Represent Space and Time&lt;/em&gt;&lt;/a&gt;: linear probes on Llama activations recover, among other things, the latitude and longitude of places the model has read about. The paper was received as ammunition in the “do LLMs have world models?” debate, and the skeptics’ rebuttal was predictable: recovering coordinates with a linear probe is mere statistics, correlations between surface features, not a &lt;em&gt;model&lt;/em&gt; of the world.&lt;/p&gt;

&lt;p&gt;I always found that rebuttal strange, because it gets the burden of proof backwards. Of course LLMs have world models. Modeling the world is not an exotic capability that emerges at some parameter count; it is what learning from data about the world does by default. Compression &lt;em&gt;is&lt;/em&gt; the discovery of structure. The interesting questions are about how rich and how faithful the model is, not whether one exists.&lt;/p&gt;

&lt;p&gt;Back then I started a small experiment to make the point by reductio, and, in the age of coding agents, finally finished it. Strip everything away — no transformer, no embeddings, no attention, no pretraining. Take the English Wikipedia article of every country and territory, compute tf-idf, and fit a linear ridge regression from word frequencies to the country’s location. If a linear readout of geography counts as evidence of a world model, then a bag of words has one too.&lt;/p&gt;

&lt;p&gt;To keep it honest: 238 of the 249 articles literally contain the country’s coordinates (Wikipedia footers), so coordinate strings are stripped and tokens containing digits are excluded from the vocabulary. And rather than regressing latitude and longitude directly — which punishes predictions near the antimeridian with an absurd squared loss — the model predicts a point in 3D that gets projected back onto the sphere. Everything is evaluated with 10-fold cross-validation, vectorizer refit on the training folds.&lt;/p&gt;

&lt;p&gt;The result: a median error of about 1,050 km across 248 countries and territories.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/tfidf_world_map.jpg&quot; alt=&quot;Country centroids predicted from Wikipedia tf-idf&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The mistakes are the best part, because they have geographic semantics rather than the flavor of noise. Réunion is pulled toward France and Timor-Leste toward Portugal; Kiribati, which straddles the antimeridian, lands on the wrong side of the Pacific; Canada and Chile get dragged toward their population centers. The regression is wrong the way you would be wrong if everything you knew about the world came from reading about it.&lt;/p&gt;

&lt;p&gt;Two details surprised me. In 2023, my quick attempts had gradient boosting comfortably ahead of linear regression; once the target lives on the sphere, the linear model wins (1,058 km vs 1,344 km median). And cross-validation selects the smallest ridge penalty on the grid in every single fold: with ~30,000 features and ~220 training examples, the best regime is near-interpolation. The geographic signal isn’t concentrated in a handful of gazetteer tokens that regularization would preserve; it is smeared diffusely across the entire vocabulary.&lt;/p&gt;

&lt;p&gt;If a linear map from word counts recovers the shape of the globe, then “the probe is just statistics” fails as a rebuttal — not because the probe is more than statistics, but because there is no distinction to draw. Statistics about the world, compressed, is a model of the world. The bag of words has a crude one. LLMs have vastly better ones. The claim that they have none was always outlandish.&lt;/p&gt;

&lt;p&gt;Code, data, and the experiment log: &lt;a href=&quot;https://github.com/murbard/tfidflatlong&quot;&gt;github.com/murbard/tfidflatlong&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
        <link>http://ex.rs/even-a-bag-of-words-has-a-world-model/</link>
        <guid isPermaLink="true">http://ex.rs/even-a-bag-of-words-has-a-world-model/</guid>
        
        
        <category>ai</category>
        
        <category>machine-learning</category>
        
      </item>
    
      <item>
        <title>A Piano in 900 Characters</title>
        <description>&lt;p&gt;&lt;em&gt;my work, AI-generated overview&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/golfed_piano.jpg&quot; alt=&quot;Spectrogram of the golfed piano playing five chords, quiet to loud&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I wanted to see how small a convincing piano could be. Here is the whole voice:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;synth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.43&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.84&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],[.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;04&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;088&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;094&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],[.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;035&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;06&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])]&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;440&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;69&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;65&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;7.79&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0653&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;12e3&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;6e3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maximum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;835&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0277&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;44100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;44100&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_rng&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rfftfreq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;44100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;85&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zeros&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;D&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[:,&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uniform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[:,&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])).&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;004&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;irfft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rfft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1e-9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;006&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;irfft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rfft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;polyval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;564&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;814&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.385&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1e3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;synth(midi, velocity, seconds)&lt;/code&gt; returns 44.1 kHz audio. There are no samples, only a sum of sine waves, and every constant in it was measured from the &lt;a href=&quot;https://theremin.music.uiowa.edu/MIS.html&quot;&gt;University of Iowa&lt;/a&gt; piano recordings at pp, mf and ff.&lt;/p&gt;

&lt;p&gt;&lt;audio controls=&quot;&quot; preload=&quot;none&quot; src=&quot;/assets/audio/piano_scale.mp3&quot; style=&quot;width:100%&quot;&gt;&lt;/audio&gt;&lt;br /&gt;
&lt;audio controls=&quot;&quot; preload=&quot;none&quot; src=&quot;/assets/audio/piano_chords.mp3&quot; style=&quot;width:100%&quot;&gt;&lt;/audio&gt;&lt;/p&gt;

&lt;p&gt;You can tell it isn’t a real piano. For its complexity, though, it’s surprisingly good. The upper range is the weakest part. Here it is playing Chopin’s Nocturne Op. 9 No. 2:&lt;/p&gt;

&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; poster=&quot;/assets/video/piano_nocturne.jpg&quot; src=&quot;/assets/video/piano_nocturne.mp4&quot; style=&quot;width:100%;border-radius:6px;background:#000&quot; aria-label=&quot;The golfed piano playing Chopin&apos;s Nocturne Op. 9 No. 2&quot;&gt;&lt;/video&gt;&lt;/p&gt;

&lt;h3 id=&quot;what-the-lines-do&quot;&gt;What the lines do&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f=n*F*(1+B*n*n)**.5&lt;/code&gt;&lt;/strong&gt;: stiff strings are inharmonic, so their partials run progressively sharp. log B is linear in pitch.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A=n**-slope&lt;/code&gt;&lt;/strong&gt;: partial amplitudes fall off as a power of the partial number. The slope depends on pitch and velocity, which makes the bass rich, the treble pure, and loud notes brighter. Velocity is continuous: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interp&lt;/code&gt; blends between the three measured dynamics.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;E&lt;/code&gt;&lt;/strong&gt;: each partial decays at its own rate, faster for higher notes and higher partials. The decay has two stages, a quick initial drop and then a long tail.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;D&lt;/code&gt;&lt;/strong&gt;: one, two or three strings depending on register, detuned by a few cents. That produces the slow beating you hear in a real piano.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;z&lt;/code&gt;&lt;/strong&gt;: the hammer knock, a 6 ms burst of low-passed noise.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The last line&lt;/strong&gt;: a fixed soundboard EQ, quadratic in log-frequency. I started with a nine-point lookup table and swapped in a fitted quadratic to save characters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The golfed function is bit-identical (max error about 1e-13) to the fitted model it came from. I also wrote a C port, about 1500 characters with only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;math.h&lt;/code&gt; and no FFT, and a Julia port. The Julia version came out &lt;em&gt;longer&lt;/em&gt; than the numpy one, mostly because numpy has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interp&lt;/code&gt; built in.&lt;/p&gt;

&lt;h3 id=&quot;measure-dont-fit&quot;&gt;Measure, don’t fit&lt;/h3&gt;

&lt;p&gt;The constants were the hard part. I first fit everything by gradient descent through a spectrogram loss, and it was a disaster. Inharmonicity went negative, the decays collapsed, and brightness came out inverted. A magnitude-spectrogram loss barely constrains any of those quantities.&lt;/p&gt;

&lt;p&gt;What worked was a lock-in analysis of every recorded note: heterodyne each expected partial down to DC and low-pass it. That gives each partial’s exact frequency and amplitude envelope directly. B, the decay laws and the unison detune were read off those measurements and pinned. Only the excitation shape, the one thing the audio loss can actually see, was left to the optimizer.&lt;/p&gt;

&lt;h3 id=&quot;the-oboe&quot;&gt;The oboe&lt;/h3&gt;

&lt;p&gt;The oboe isn’t too bad either. Here it is with the piano accompanying:&lt;/p&gt;

&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; poster=&quot;/assets/video/oboe.jpg&quot; src=&quot;/assets/video/oboe.mp4&quot; style=&quot;width:100%;border-radius:6px;background:#000&quot; aria-label=&quot;The synthesized oboe with piano accompaniment&quot;&gt;&lt;/video&gt;&lt;/p&gt;

&lt;p&gt;&lt;audio controls=&quot;&quot; preload=&quot;none&quot; src=&quot;/assets/audio/oboe_scale.mp3&quot; style=&quot;width:100%&quot;&gt;&lt;/audio&gt;&lt;/p&gt;

&lt;p&gt;It needs a different model. A reed is driven, not struck, so the tone sustains and the partials are almost perfectly harmonic. Its character comes from fixed resonances in the bore rather than from how it decays. I fit a formant envelope to 35 Iowa oboe notes: a +28 dB peak at 1.29 kHz and a second one near 3 kHz, on a −10 dB/octave tilt. On top of that sit vibrato and some breath noise.&lt;/p&gt;

&lt;p&gt;The trick that makes it sound alive is that the formant stays fixed in absolute frequency. As the vibrato moves the pitch, each partial slides up and down the formant curve and its loudness shimmers, the way it does on a real oboe.&lt;/p&gt;

&lt;p&gt;This was part of a larger project on &lt;a href=&quot;https://sethares.engr.wisc.edu/consemi.html&quot;&gt;Sethares&lt;/a&gt;-style spectrum tuning: nudging partials so that equal-tempered intervals become dissonance minima. The piano’s spectrum is too steep for that to work, about 3 of 12 intervals. The oboe’s brighter spectrum gets 9 of 12, tritone included.&lt;/p&gt;

&lt;p&gt;The code is on GitHub at &lt;a href=&quot;https://github.com/murbard/golfed-synths&quot;&gt;murbard/golfed-synths&lt;/a&gt;. It’s numpy only, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python demo.py&lt;/code&gt; renders the clips above.&lt;/p&gt;
</description>
        <pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate>
        <link>http://ex.rs/a-piano-in-900-characters/</link>
        <guid isPermaLink="true">http://ex.rs/a-piano-in-900-characters/</guid>
        
        
        <category>audio</category>
        
      </item>
    
      <item>
        <title>The Frayed Edge of Bespoke Software in the Age of Agents</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/frayed_edge.jpg&quot; alt=&quot;The Frayed Edge&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In 2021, I purchased a property in Tuscany, sight unseen. This was in the midst of COVID restrictions, and UK residents were not allowed to travel outside of the country. The realtor did a tour of the place with a phone camera, and the deal was struck. The property had been renovated completely in the early 2000s, but was mostly left abandoned. With just a bit of work, it would be ready next year. Oh, sweet summer child.&lt;/p&gt;

&lt;p&gt;Renovation in Italy is not for the faint of heart. The pristine scenery of Tuscany, its authenticity, comes at a cost. Permitting is … difficult. I am now five years into this renovation project, and I think I see light at the end of the tunnel. This post is not about my woes as the owner of international real estate; it’s about using coding agents, like Claude code, for project management, so bear with me.&lt;/p&gt;

&lt;p&gt;The renovation work turned complex for a few reasons. The design was outdated and needed a refresh, but design plans (including interior design) must be filed with the municipality as part of an overall renovation plan. The pool turned out never to have been filled, and if it had, it would have slid down the hill, which is largely made of clay. This meant it had to be re-permitted and reinforced with dozens of deep concrete pillars. All in all, the work involves architects, contractors, lawyers, surveyors, banks, permits, land purchases, and a steady stream of email in English and Italian. For years, the actual state of the renovation ended up scattered across Gmail, attachments, shared folders, some calendar reminders, and whatever I happened to remember.&lt;/p&gt;

&lt;p&gt;The amount of communication itself was quite manageable: on average, over the past five years, it’s amounted to about an email a day, with long periods of quiet and sudden bursts when permits, contracts, or financing were moving. The management challenge lay in how the project spanned years and involved many parties who were waiting on one another. A surveyor would promise something in October that became relevant again in February. A number buried in a PDF attachment would resurface months later in a discussion with a bank. A planning question that seemed settled would quietly reopen because nobody had a clean record of what had already been agreed.&lt;/p&gt;

&lt;p&gt;When I figured out something was blocked, I could look things up in Gmail if I roughly remembered the date or the sender, or had a decent guess at the right keyword. Most of the time, the problem was broader than that. I needed to recover the current state of the project: what had happened, what was still open, who owed what, what had slipped, and which loose ends were likely to matter later. Reconstructing that by reopening threads and attachments each time was slow, and the result was never very satisfying because the useful context lived across too many places.&lt;/p&gt;

&lt;p&gt;I have been an early adopter of AI for coding. I started using GitHub Copilot in 2021, invested in magic.dev’s pre-seed round… I was blown away by the step change when OpenAI’s o1 first came on the scene. This was the first model to pass my favorite interview questions, a disarmingly simple question that many CS graduates shockingly fail because they are trained to answer these questions with pattern recognition instead of reasoning. I first used Claude Code around May 2025.&lt;/p&gt;

&lt;p&gt;My first reaction was that the interface was finally right, a CLI blends the conversational aspect of AI with the natural workflows of building software, and that the model was finally decent at planning. Tool use had been around for a while – the toolformer paper came in February 2023 – but this was the first time I was really interacting with models that had significant RL training in tool use. I immediately cranked up the difficulty of what I was trying to accomplish with it and quickly noted limitations; the model would simply cheat… replace the unit test it had written with one it could pass, change the winning condition. It couldn’t admit defeat, didn’t know how to ask for help, and didn’t understand how to conduct experiments to root out bugs. Those are still issues today, but to a lesser extent. I didn’t play with coding agents until later that year, when I caught word that they had finally gotten to the point where they were getting useful. I started completing a host of small personal projects I had needed, and launched experiments to generalize their use across the software companies I founded. Towards the end of 2025, I started hearing about people using coding agents to manage their emails, and it got me curious. I didn’t want to give Claude Code unfettered access to my email, but the idea that the model’s planning and tool-use abilities could extend to tasks beyond programming felt intriguing.&lt;/p&gt;

&lt;p&gt;In February 2026, I set out to use Claude Code to build a system to help me manage the renovation. The privacy concerns were modest; all the emails involved are banal and do not contain particularly sensitive information, so I could easily filter them out. The scope of the project was also small, and it would help me build up the experience to scale this approach to much larger project management tasks.&lt;/p&gt;

&lt;p&gt;A mistake would have been to connect Claude or OpenAI by MCP to my inbox and ask it to “manage” the project or to suggest what to do. The models are not good enough for that. In many ways, they optimize to give you a satisfying-seeming answer right away, not to do the right thing. Ask an MCP connected model for “what’s urgent right now,” and it will do something like search for the keywords &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;urgent | time sensitive | important&lt;/code&gt; in your inbox, look at a few messages, and hurry back with a polished but superficial answer.&lt;/p&gt;

&lt;p&gt;I had to build a “harness”. Noam Brown, a researcher at OpenAI and an expert on planning, holds the view that all custom-built harnesses will become irrelevant and be replaced by better reinforcement learning training. He’s probably right, but that is at least months from now! This is the harness I had Claude Code build first:&lt;/p&gt;

&lt;p&gt;The system starts by syncing project email from Gmail into a local archive of raw messages and attachments. Local files are easier to grep, cache, inspect, and process repeatedly than a live inbox reached through an API. It also means the agent doesn’t have unfettered access to the inbox, only what the script, which is run externally, is programmed to download. The pipeline then walks those emails for shared links and downloads the corresponding files from Google Drive, Dropbox, and WeTransfer, because a large share of the actual project lives in drawings, contracts, invoices, surveys, permits, and estimates rather than in the message body. It also extracts text from documents and useful descriptions from images, so information does not get trapped in attachments.&lt;/p&gt;

&lt;p&gt;Once that corpus is assembled, batches of email and attachment text are processed through Claude’s API to extract structured events: commitments, decisions, issues, financial facts, deliverables, deadlines, milestone changes, and anything else that deserves to be a first-class item in the record. This is appended as JSON lines to a log file. Why use the API when the agent environment uses the same models and offers cheaper tokens? Because long repetitive runs need software around the model, the program handles batching, retries, checkpointing, deduplication, validation, and recovery from malformed output. Claude provides the judgment inside a narrow task; the surrounding code keeps the process stable over thousands of messages. You could try asking your coding agent to go over 5 years of emails one by one and build a coherent timeline, but it will most likely veer off track, change its approach midway through, or delete the data. There’s still untapped potential in putting AI models in a well-controlled for-loop.&lt;/p&gt;

&lt;p&gt;Those events feed a multi-scale timeline. At the bottom, there is the append-only event log. Above that sit monthly summaries and a current project context that can be queried or rendered into a readable timeline. A reminder layer compares open commitments against Google Calendar and proposes follow-ups.&lt;/p&gt;

&lt;p&gt;So far, it has processed about 2,000 emails spanning 2021 to 2026, extracted 1,338 structured events, identified 102 people, and organized the work across 10 workstreams over 62 months. The number of decisions and resolutions since February is five times the previous rate, indicating that the system has been quite effective at getting things moving. I attribute a big part of it to tracking small commitments, those little promises that go unfulfilled and later pile up as delays: revised drawings next week, a document to be sent tomorrow, a correction to a deed, a cost figure to be confirmed, a map someone is still waiting on. They rarely look important in isolation, but they add up and become blockers when nobody records them.&lt;/p&gt;

&lt;p&gt;A key design choice was to represent the work as events on a timeline rather than ask a model to rummage through an inbox whenever I had a question. In a multi-year project, the email is usually the wrong unit of thought. What matters operationally is that a contractor promised revised drawings by Friday, a lawyer said a deed amendment was required, a bank requested supporting documentation, or a permit moved from draft to filing. Once those facts exist as dated records with people, workstreams, and links back to source material, the project becomes much easier to reason about.&lt;/p&gt;

&lt;p&gt;Search still plays an important role; I regularly search the timeline and the underlying archive. But the search now runs over a representation that already carries context. Gmail search can retrieve fragments if I remember enough to ask the right question. A timeline preserves the dependencies and unresolved issues that give those fragments meaning.&lt;/p&gt;

&lt;p&gt;Claude Code was valuable because it gave me a working environment rather than a chat window. It could read and write files, run commands, inspect failures, patch code, rerun jobs, and keep iterating until the workflow behaved properly. That made it cheap to develop and iteratively test tooling around the project. The first version came together in just a few hours, but over the next two months, it steadily improved with use: better recovery from malformed model output, improved file handling, headless authentication for remote runs, a unified CLI, and prompts and schemas that more closely matched the project – normal product work, but in a much tighter loop than would have been practical before.&lt;/p&gt;

&lt;p&gt;Humans use tools because they take away physical or cognitive load and provide a repeatable way to perform a task. It is natural for agents to use tools and even build them for their own purposes. So why not connect the agent to a project management tool, and have it file Jira tickets or track progress in Asana? Partly because the project was small enough, I did not want the added complexity of interacting with a project management tool. But more interestingly, the structure that worked well here looked slightly different from that of ordinary project-management software. Existing tools are built for people who stare at dashboards, drag cards across boards, and update fields by hand. The agent could work better with something plainer: files, markdown, JSON, directories, scripts, and a timeline with stable links back to source material. I suspect a lot of software built primarily for agents will end up looking like that. It may be less polished to the eye and more useful in practice. It’s also clear to me that the future of much enterprise software is to build software for agents, at least in the short run, before they just write what they need on the fly.&lt;/p&gt;

&lt;p&gt;Today, I mostly “use” the system by talking to Claude Code, but a lot of the value now lives in code written earlier, in scripts that call the API, and in workflows that began as conversations and later hardened into commands. The boundary between software, agent behavior, and conversation has become fairly porous, and that has proven helpful. Useful workflows often begin as one-off interactions; once they prove valuable, I pin them down in code. When the project shifts, I can loosen part of the workflow again or add another layer around it. A packaged product would have forced more structure up front than this project wanted. I call this software with a “&lt;strong&gt;frayed edge&lt;/strong&gt;”: a tightly woven core that, after a while, doesn’t change much; an interface at the periphery that gets rewritten as the workflow evolves; and a chat interface that can handle the unexpected. Packaging “project management with an AI agent” as a product would miss much of the point. Software crystallizes workflows into a polished but inflexible interface, whereas much of the value I’m getting comes from the system’s overall fluidity.&lt;/p&gt;

&lt;p&gt;Whilst the agent conveniently did all the coding, I still had to understand what I was building from an architectural perspective. No, this doesn’t point to some long-arching synergy between human and AI; the centaur era (where man + machine is greater than man or machine alone) will not last long, but we are in it, and it’s thus relevant to talk about it. Among the decisions I had to make were whether the timeline should be append-only, what belonged in the schema, how large a batch should be, which data should be cached locally, which tasks belong in deterministic code, and which ones benefit from model judgment. Those questions do not disappear because a model writes the code.&lt;/p&gt;

&lt;p&gt;That should still be encouraging for non-programmers. The bar is no longer “can you implement the whole thing by hand?” It is closer to “do you understand the workflow well enough to shape the system, inspect its outputs, and notice when it is going wrong?” You still have to get your hands dirty, but you do not need to be a professional developer to build something useful.&lt;/p&gt;

&lt;p&gt;Seen from a distance, a fair amount of middle management consists of maintaining institutional memory and doing follow-up. Who promised what and when is it due? What does it depend on, and who needs reminding? Which disagreement is being reopened because nobody remembers how it was settled? Strategy, judgment, negotiation, hiring, politics, and taste remain human problems for the foreseeable future. But the connective tissue of coordination is much more legible to software than many organizations admit.&lt;/p&gt;

&lt;p&gt;The cost of bespoke internal software has now dropped sharply. Someone who understands a workflow well, working in an agentic coding environment and with a short iteration cycle, can assemble something that previously would have required a small internal tools team. This will also reduce the importance of middle management, as more leverage can be gained when information is surfaced and managed easily. This is not the first time this has happened; computers and IT have delivered on some of that promise and extended the scale at which individuals could operate, but this stretches it even further.&lt;/p&gt;

&lt;p&gt;A new type of software is emerging, one that exists on a continuum between robust, repeatable tooling code, high turnover features rewritten by an agent, and an agent orchestrating their use. The agent becomes useful not just as a developer but also as a somewhat competent planner, capable of using tools and making sense of structured information collated from disparate sources.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. We should bear in mind AI will likely make the vast majority of intellectual labor obsolete in the coming years, then recursively self-improve at an increasing pace. This will not stop at the digital world; molecular nanotechnology, as envisioned by Drexler, offers the prospect of doubling the stock of productive capital every hour; the surface of the earth will be covered with solar panels, and planets will be disassembled to surround the sun with a Dyson sphere. The odds are stacked against humanity’s survival in this process, without a collective realization of what’s happening. Even if humans do survive, the world will be largely unrecognizable. In this context, the future of middle management and custom software is quaint, and the wisdom of pouring time and energy into a renovation project in its fifth year is questionable. But there is little sense in living our lives as if we were right on the cusp of the singularity, and at least we can finally enjoy getting a handle on things for a brief moment.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 16 Apr 2026 00:00:00 +0000</pubDate>
        <link>http://ex.rs/frayed-edge-bespoke-software-age-of-agents/</link>
        <guid isPermaLink="true">http://ex.rs/frayed-edge-bespoke-software-age-of-agents/</guid>
        
        
        <category>ai</category>
        
        <category>software</category>
        
      </item>
    
      <item>
        <title>Consumption inequality</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/consumption_inequality.jpg&quot; alt=&quot;Consumption inequality&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Economic inequality lies less in wealth than in consumption, at least for billionaires, and at least in the sense that consumption is what actually commands real resources. A pile of stock in productive companies, or even a pile of cash that never becomes spending, doesn’t directly divert today’s output away from anyone else.&lt;/p&gt;

&lt;p&gt;And it isn’t inert. Being willing to sit on long-duration equity at a low required return bids up valuations and, at the margin, lowers the cost of capital. Likewise, keeping cash as a bid for liquidity means some money is being held instead of spent, so there is less immediate pressure on prices, and interest rates can be lower than they otherwise would be, even if that cash is literally just stashed under a mattress. Arguments about the assets being productive are a red herring.&lt;/p&gt;

&lt;p&gt;What bites is spending that actually pulls on scalable capacity and scarce inputs. When billionaire wealth is spent on resource-intensive goods and services, productive capacity and bottlenecked inputs get pulled toward satisfying that demand, leaving less capacity for other uses. If big chunks of capacity are going into making yachts, they’re not going into healthcare.&lt;/p&gt;

&lt;p&gt;Billionaires do obviously consume far more than most people, but they consume a much smaller fraction of their wealth, because there’s only so much you can buy. And even that spending is often positional and doesn’t translate cleanly into a large diversion of real resources. Buying a very expensive Renaissance painting doesn’t cause the economy to allocate more labor and materials to producing Renaissance paintings. It mostly reallocates ownership, with the real resource burn concentrated in intermediation, promotion, authentication, auctions, lawyers, security, and there are diminishing returns there. Same story with beachfront property and trophy assets, you’re bidding up scarce slots, not placing a production order.&lt;/p&gt;

&lt;p&gt;Once you put those two facts together, low consumption relative to wealth, and a big chunk of that consumption being positional, the conclusion is pretty stark. If you look at the actual flow of goods and services the economy produces, very little of it ends up going to billionaires. Their lifestyle footprint can be loud and visible, but it’s a microscopic layer on top of a massive base of production that overwhelmingly serves everyone else.&lt;/p&gt;

&lt;p&gt;People often get this exactly backwards and praise billionaire spending because “it creates jobs” or “stimulates the economy.” That’s the wrong frame in a mostly capacity-constrained economy, demand gets accommodated by reallocating resources and by prices moving. It’s also why trickle-down economics, in its popular form, is nonsense. The idea that letting the rich keep more money helps because they will spend it and “create jobs” is backwards, they don’t spend much relative to their wealth, and a lot of what they do spend is positional.&lt;/p&gt;

&lt;p&gt;When the rich raise everyone’s standard of living, it is mostly through the opposite channel, saving and investment. A higher propensity to save lowers the economy’s cost of capital, and that makes it easier to fund new capacity, new technology, and new firms. Over time, that raises productivity, and in competitive markets the gains show up as better goods, lower quality-adjusted prices, and higher real wages.&lt;/p&gt;

&lt;p&gt;The first implication is that there isn’t much to be gained by “grabbing the pot of money,” if what you do with it is just spend it. If that wealth wasn’t being turned into consumption, it wasn’t directly diverting real resources. Convert it into pure spending and you don’t magically increase the economy’s productive capacity, you mostly just push more demand through the same bottlenecks.&lt;/p&gt;

&lt;p&gt;The second implication is that the recent obsession around the tax strategy of borrowing against appreciated assets is mostly a nothing-burger as a lifestyle-finance story. There isn’t that much billionaire consumption to finance relative to billionaire wealth to begin with. And if borrowing were treated as a realization, taxable event, I’d expect many to scale back consumption sharply. For people who’ve worked hard to build wealth, there’s something uniquely offensive about pissing it out to the government, plenty would rather cut down already-discretionary spending than hand it to the bastards.&lt;/p&gt;
</description>
        <pubDate>Tue, 30 Dec 2025 19:38:00 +0000</pubDate>
        <link>http://ex.rs/comsumption-inequality/</link>
        <guid isPermaLink="true">http://ex.rs/comsumption-inequality/</guid>
        
        
        <category>economics</category>
        
      </item>
    
      <item>
        <title>Thoughts on inscriptions</title>
        <description>&lt;p&gt;“Inscriptions” became a popular way to create NFTs on Bitcoin earlier in 2023 and have picked up significantly in the past month. However, they are, in many ways, a step backward.&lt;/p&gt;

&lt;p&gt;To understand inscriptions, we must first understand some technical limitations of the Bitcoin blockchain and the historical emergence of solutions around it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/bitcoin_inscription.jpg&quot; alt=&quot;Early Bitcoin inscription&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;data-availability-and-sovereign-rollup&quot;&gt;Data-availability and sovereign rollup&lt;/h2&gt;

&lt;p&gt;Bitcoin generally lacks support for the statefulness and logic required to deploy all but the most basic smart contracts. It is, however, possible to write arbitrary data to the Bitcoin blockchain. As such, Bitcoin can serve as a “data-availability layer” for a chain following arbitrary logic. You can create arbitrarily complex blockchain logic that takes as input data from the Bitcoin blocks and maintains the state of a ledger. This is the approach taken by systems like &lt;a href=&quot;https://www.omnilayer.org/&quot;&gt;Mastercoin&lt;/a&gt;, &lt;a href=&quot;https://counterparty.io/&quot;&gt;Counterparty&lt;/a&gt;, etc. The logic can be arbitrarily complex and include smart contracts. If you are so inclined, think of the chain as a linked list and of the logic as a “&lt;a href=&quot;https://en.wikipedia.org/wiki/Fold_(higher-order_function)&quot;&gt;fold&lt;/a&gt;” operation where the accumulator is the ledger state. This approach has recently been rebranded as “&lt;a href=&quot;https://blog.celestia.org/sovereign-rollup-chains/&quot;&gt;sovereign rollups&lt;/a&gt;.” There are several issues with it.&lt;/p&gt;

&lt;p&gt;First, Bitcoin data-availability is fairly limited and expensive. Transactions can only use a small fraction of their content as a data payload, and the data itself is limited to 1MB - 4MB per 10 minutes. There isn’t much capacity to go by. While this can price out small transactions, it’s silly to try and police how a permissionless network will be used. Moreover, the Bitcoin network’s security is meant to be subsidized by fees, not inflation. As scaling solutions build up, Bitcoin purists should welcome, not rail against, any fee they can get, however, it’s far from certain that they can contribute meaningfully to the budget in the long run. Bitcoin has one thing to offer to these applications: its brand and luster&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. The market for collectibles, and frankly, memecoins, is driven by many intangible factors, not necessarily cost-efficiency. Nonetheless, the cost does create a strong current to swim against, and applications will tend to migrate where fees are cheaper. Part of the problem is that Bitcoin &lt;em&gt;only&lt;/em&gt; has its brand to offer; these systems do not meaningfully interact with bitcoin, the asset. This gets us to our second point.&lt;/p&gt;

&lt;p&gt;Second, these systems cannot securely share their state with other systems, including the base chain they are built upon. In the case of a so-called “sovereign rollup” on top of Bitcoin, this means, ironically, that they cannot use bitcoin as a native asset. It’s perfectly possible to deposit bitcoin into such a rollup — the nodes computing the state of the rollup can take note of such deposits — but it’s not possible to take it out. This is because the Bitcoin chain itself is unaware of the state of the rollup; it does not and cannot know whether or not a valid withdrawal is taking place. Relatedly, Bitcoin light clients do not and cannot provide information about the state of the rollup. The same goes in general for so-called “sovereign rollups”; they cannot prove their state to any other rollup, meaning they are condemned to interact with other rollups via trusted bridges.&lt;/p&gt;

&lt;p&gt;To conclude this section, I’ll add a note on &lt;a href=&quot;https://en.bitcoin.it/wiki/Colored_Coins&quot;&gt;colored coins&lt;/a&gt;, which are in some way a lighter version of the above. They typically do not implement extraneous logic but use data to “tag” certain transaction outputs (UTXO) with nominal value as representing assets. With careful wallet implementations, it’s possible to segregate these outputs from regular coins and treat them as special.&lt;/p&gt;

&lt;h2 id=&quot;ordinals-and-inscriptions&quot;&gt;Ordinals and inscriptions&lt;/h2&gt;

&lt;p&gt;“Ordinals” for digital collectibles became popular in the middle of 2023, somewhat went away, and are now making a comeback. They do not implement complex logic as outlined above. As such, it’s not really possible to implement decentralized exchanges for ordinals, nor is it possible to implement auction contracts or a host of other important primitives for collectible ecosystems.&lt;/p&gt;

&lt;p&gt;They differ from what has been typically seen in the NFT space in two particular ways:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;They are issued on the Bitcoin chain.&lt;/li&gt;
  &lt;li&gt;The media content of the collectible is written in the transactions themselves.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those two aspects are completely independent. It would be possible for ordinals to be issued on Bitcoin without inscribing the entire media content in transactions (by inscribing instead an &lt;a href=&quot;https://ipfs.tech/&quot;&gt;IPFS&lt;/a&gt; link for instance), and of course, people have taken to inscribe collectibles on other chains.&lt;/p&gt;

&lt;p&gt;On the first aspect, as discussed above, Bitcoin is not fit for purpose as most interesting primitives for a collectible ecosystem cannot be implemented. However, it does lend its &lt;em&gt;brand&lt;/em&gt;, which has been enough to create an interest in the phenomenon.&lt;/p&gt;

&lt;p&gt;The second aspect is weird, and a proper discussion involves dispelling technological misconceptions while reflecting on… reflexivity. Let’s start with the strict technical requirements for storing NFT media content.&lt;/p&gt;

&lt;h2 id=&quot;storing-nft-media-content&quot;&gt;Storing NFT media content&lt;/h2&gt;

&lt;p&gt;An NFT represents a title, a digital right to a piece of media. Those rights can vary. In many cases, the NFT only represents a right to hold the NFT, and nothing else. The fact that a piece of media was attached to it is meaningful to collectors, regardless of them holding any rights. Collecting is not about obtaining some IP right to display a piece, otherwise, no one would collect old masters; it can be about bragging rights, it can be about supporting artists (yes, collecting dead artists is a way to retrocausally support them, as the expectation of future collection is what enables speculators to support them during their lifetime). It can also be about explicit IP-related rights, but I think people who focus on some concrete form of utility, be it real-world utility or IP rights, are somewhat missing the point.&lt;/p&gt;

&lt;h3 id=&quot;hashes-and-ipfs&quot;&gt;Hashes and IPFS&lt;/h3&gt;

&lt;p&gt;So what it is to be done about the piece of media? A simple hash of the content is sufficient in my opinion, it indicates without ambiguity that the piece was attached to the NFT, and that is all that matters. But what about the media itself? The collector can right-click and save it if they care to. This is a function that ought to be automated by most wallets.&lt;/p&gt;

&lt;p&gt;IPFS represents a fairly good solution to the problem. It is as lightweight as a hash, but also specifies a fairly general way to retrieve the media if someone cares to make it available. The “natural” solution to the matter is wallets saving the content, running IPFS nodes and pinning it there. This can introduce practical problems, of course, marketplaces need to access the data, and it may be inconvenient to corral it from a bunch of sometimes offline nodes, but this is something that can be solved by caching. Sellers are incentivized to make the content broadly accessible.&lt;/p&gt;

&lt;h3 id=&quot;ledger-state&quot;&gt;Ledger state&lt;/h3&gt;

&lt;p&gt;It’s been proposed to store data in the state of the ledger. Smart contracts can hold data, which they rely on for business logic, and that data can represent various pieces of media. The advantage of this approach is that the validators of the chain typically have to store that data because they need to assess the validity of any incoming transaction, and a transaction &lt;em&gt;could&lt;/em&gt; in theory depend on that data. The initial design of Ethereum does not price state storage correctly; it tries to limit the amount of data stored on-chain via gas fees. This has the unfortunate consequence that, when demand for transactions and thus gas fees are low, it can be cheap to permanently expand the size of that state. The huge size of the Ethereum state tree has been one of its major impediments to scaling, as most of the time spent by validators is waiting for IO in that large tree. Predictably, allowing stateless validation, or introducing state rent has been a major area of R&amp;amp;D in the Ethereum community. Tezos, in contrast, uses a simple but fairly robust approach to managing state size by charging a fixed burn cost for every byte added to the storage. This means that the cost of increasing the state tree size does not depend on gas costs. This offers much simpler UX and implementation than state rent but has worked well in practice.&lt;/p&gt;

&lt;p&gt;What am I getting at with all this? I’m getting at the fact that it’s important for the state tree to remain somewhat light, and increasing it should be priced accordingly. This state will have to be replicated across all the validators and all the nodes, with economic incentives. This makes it extremely overkill and also particularly expensive. The convenience factor is high, any chain RPC will reply with the data, but at what cost?&lt;/p&gt;

&lt;h3 id=&quot;block-history&quot;&gt;Block history&lt;/h3&gt;

&lt;p&gt;A third approach, the one taken by inscriptions/ordinals, is not to store the data in the ledger state, but in the blocks themselves.&lt;/p&gt;

&lt;p&gt;Blocks provide “data availability” guarantees, although, as Dankrad Feist points out, “data-availability” is a terrible name that obscures what is going on. He proposes the much better term “proof of publication.” Including data in blocks is a way to prove that the data was, at one point, made public. This means that anyone who cared to pay attention at that time, and run a node, was able to download that piece of data. Proof of publication is critical to ensure that all the participants wishing to maintain the state of the ledger, ascertain its validity, or even produce blocks can do so.&lt;/p&gt;

&lt;p&gt;Proof-of-publication by itself does not guarantee that the data can be retrieved at a later time. There is no incentive in the Bitcoin protocol, the Tezos protocol, Ethereum, or others to store blocks for longer than the length of a typical chain reorganization. Fortunately, as long as there is at all times at least one altruistic, honest archivist online, then in practice the chain will be saved. Typically, I don’t think it’s worth really sweating incentives when you only need a single honest, altruistic participant, but that depends on the cost of this altruism. If the cost is on the order of thousands of dollars a year, this will just happen; if the cost is on the order of millions, you need incentives, funding mechanisms, etc.&lt;/p&gt;

&lt;p&gt;There is no “crypto-economic” mechanism that makes inscriptions in blocks stored more safely and reliably than IPFS pins; in both cases, you are dependent on honest, altruistic participants storing the data. The only mechanism at play is bitcoin miners and node operators not really caring about optimizing their operations enough to prune old blocks. Note that it’s also possible for these miners to selectively prune inscriptions from old blocks with no adverse consequence, as blocks pruned of inscriptions can be used by new participants to validate the chain and recreate the ledger from genesis.&lt;/p&gt;

&lt;p&gt;To be completely fair, I have zero concerns about Bitcoin archives becoming unavailable. The cost of storage is minimal, and Bitcoin is a huge social movement. In other words, Bitcoin’s block history isn’t going anywhere. But likewise, when the creator of Hic &amp;amp; Nunc, a leading NFT marketplace on Tezos suddenly pulled the plug on his site, a mirror was up within hours, and all the content on IPFS was repinned. Nothing was lost. IPFS can be brittle if not enough people care about the content. The benefit Bitcoin blocks is that someone is realistically always care about the content enough to save it.&lt;/p&gt;

&lt;p&gt;Storing in blocks offers some convenience, but it’s just another form of storage, with a different social dynamic, there is no “crypto-economic” incentive at play. Sadly, there is more to it.&lt;/p&gt;

&lt;h2 id=&quot;reflexivity&quot;&gt;Reflexivity&lt;/h2&gt;

&lt;p&gt;From a technical perspective, we’ve established there isn’t much of a case for storing media in the ledger state or in the blocks. However, that may not matter. The fact that the data is not stored “on-chain” is unpopular. Maybe this stems from a lack of familiarity with the above arguments, maybe it’s just a coping argument against digital collectibles in general, and maybe it is mere aesthetic preference. No matter, if enough people are convinced that this matters, and if they represent a market of buyers, then the incentive is to cater to that preference. If Elon Musk goes out and complains that “the image isn’t even on-chain,” the merit of technical arguments becomes moot. Reflectively, a Schelling Point can form around the idea that inscriptions are intrinsically more valuable, like a crystal growing around a seed. It does not matter in the least if the seed is a silly misconception, once the crystal is there, it’s there.&lt;/p&gt;

&lt;p&gt;To be clear, this is an unfortunate state of affairs. Peacocks got trapped in making huge feathers because of a runaway Fisherian sexual selection phenomenon, making them easier prey, but they can’t escape the circular logic leading to it.&lt;/p&gt;

&lt;p&gt;A world where block space is spent on inscriptions because people decided they mattered is slightly worse off than a world in which they recognized it didn’t. But the world is not always as we wish.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;I think it’s the brand dilution that fundamentally upsets the Bitcoin purists. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sun, 07 Jan 2024 13:54:00 +0000</pubDate>
        <link>http://ex.rs/thoughts-on-inscriptions/</link>
        <guid isPermaLink="true">http://ex.rs/thoughts-on-inscriptions/</guid>
        
        
        <category>tezos</category>
        
        <category>blockchain</category>
        
      </item>
    
      <item>
        <title>Parallel scans</title>
        <description>&lt;p&gt;This post is inspired by the recent paper on &lt;a href=&quot;https://arxiv.org/ftp/arxiv/papers/2312/2312.00752.pdf&quot;&gt;Mamba&lt;/a&gt;. Mamba introduces a simplified, linear RNN and shows that it can be computed in \(\mathcal{O}(\log n)\) time using a parallel scan. It’s not immediately obvious how the parallel scan algorithm can be applied to this recurrence, so I set out to understand the approach and see if it could be generalized.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/parallel-scans.png&quot; alt=&quot;parallel scan&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-parallel-scan&quot;&gt;The parallel scan&lt;/h2&gt;

&lt;p&gt;We have a sequence \(x_0, \ldots, x_{n-1}\) and we want to compute the sequence \(y_0, \ldots, y_n\) where \(y_0\) is given and for \(i &amp;gt; 0\), \(y_i = f_i(x_i, y_{i-1})\).&lt;/p&gt;

&lt;p&gt;If \(y_0 = 0\) and \(f_i(x,y) = x + y\), the scan computes the prefix sums, and due to the associativity of addition, it can be computed in parallel. Compute, in parallel, the prefix sums for the first half and for the second half. Then add the last element of the first half (the sum) to each element of the second half, which can be done entirely in parallel. With enough parallel processors, this means the prefix sum can be computed in \(\mathcal{O}(\log n)\).&lt;/p&gt;

&lt;h2 id=&quot;a-more-complicated-example&quot;&gt;A more complicated example&lt;/h2&gt;

&lt;p&gt;Let’s consider a more complicated recurrence. We will make the \(x_i\) and the \(y_i\) vectors, and \(f_i(x,y) = A_i x_i + B_i y_i\) for index-dependent matrices \(A_i\) and \(B_i\).&lt;/p&gt;

&lt;p&gt;A priori, this is not an associative operation… But let’s try anyway. We’ll process the first half starting with some \(y_0\), and the second half starting with some \(y_{n/2-1}\). If we knew the real \(y_{n/2-1}\), it would be easy, but we don’t. This doesn’t really help us; if we change \(y_{n/2-1}\), we have to recompute the scan for the second half.&lt;/p&gt;

&lt;p&gt;The trick is to lift the sequence \(y\). Notice that given the \(A_i\), \(B_i\), and \(X_i\), each \(y_i\) is a linear function of \(y_j\) for \(j &amp;lt; i\). But we know how to represent linear functions between vectors: they are matrices. So if, instead of calculating the sequence of \(y_i\), we compute the sequence of \(Y_i\) which represents the function that gives \(y_i\) as a function of \(y_0\), then we are getting somewhere!&lt;/p&gt;

&lt;p&gt;Indeed, we can compute the second half of the scan as \(Y_{n/2}, Y_{n/2+1}, \ldots\) and, when the first half has completed, multiply those matrices by \(y_{n/2-1}\), which can be done in parallel.&lt;/p&gt;

&lt;h2 id=&quot;generalization&quot;&gt;Generalization&lt;/h2&gt;

&lt;p&gt;In fact, the transform we have applied applies generically to any function \(f_i\)! The algorithm is the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Compute the first half of the sequence.&lt;/li&gt;
  &lt;li&gt;For the second half of the sequence, compute \(F_i : y \rightarrow f_i(x_i, F_{i-1}(y))\).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the linear state-space model, the functions collapse and simplify, and we have a neat representation for them. However, in the general case, those functions do not simplify. However, we can perhaps approximate them?&lt;/p&gt;

&lt;p&gt;For instance, in the one-dimensional case, we could posit that \(F_i\) is well approximated by a Chebyshev polynomial and maintain the \(F_i\) as the values taken around the Chebyshev nodes.&lt;/p&gt;

&lt;p&gt;The Chebyshev trick can be extended to the multivariate case, but the size of the representation unfortunately blows up with dimensionality \(d\) as \(\binom{n+d}{d}\) for polynomials of degree \(n\).&lt;/p&gt;

&lt;p&gt;Alternatively, we could start with a random cloud of points, propagate it using \(f_i\), but then assume that \(F\) is approximated by a Gaussian process and move the points to somehow provide a better representation of the function, for example by moving them slightly towards zones of higher curvature.&lt;/p&gt;

&lt;h2 id=&quot;gradients-and-conclusion&quot;&gt;Gradients and conclusion&lt;/h2&gt;

&lt;p&gt;Another matter is how to deal with gradients. Since the \(f_i\) are known, it’s straightforward to compute the gradient with respect of their parameters. However, these are the gradients of the exact scan. Should we use those gradients, or should we use the gradient of our approximation? We know the cloud of points at \(i-1\), so we know how a change in our approximation of \(y_{i-1}\) and a change in \(f_i\) would cause a change in our approximation of \(y_{i}\).&lt;/p&gt;

&lt;p&gt;Ultimately, this whole fields seems to be more of an experimental science than anything, and mathematics are just here to suggest reasonable things which are worth trying. I believe this is.&lt;/p&gt;

&lt;p&gt;It also gives a framework to detect parallel scans… do the functions \(F\) belong to some set with a simple parametrization which is stable by composition with \(f_i\)? Linear functions and simple categorical functions are the only examples I can think of, but there could be more.&lt;/p&gt;
</description>
        <pubDate>Sun, 31 Dec 2023 18:26:00 +0000</pubDate>
        <link>http://ex.rs/parallel-scans/</link>
        <guid isPermaLink="true">http://ex.rs/parallel-scans/</guid>
        
        
        <category>math</category>
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>Decentralization, what is it good for?</title>
        <description>&lt;p&gt;Decentralization is a means to an end. In the case of blockchains, decentralization typically achieves two ends: double spending prevention and censorship resistance — these map, respectively, to the properties of safety and liveness of a consensus algorithm. Consensus algorithms do not require decentralization, but they do require an honest majority. Decentralization helps maintain an honest majority insofar as it forces smaller bad actors to collude with each other or corrupt many honest participants to attack the network.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/decentralization.jpg&quot; alt=&quot;decentralization&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These are relevant functions of decentralization. The goal is not to create a “participative system” for the sake of it but to create a safe and resilient system. Unfortunately, decentralization is a pretty poor tool for the job:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;decentralization implies redundancy and thus higher costs&lt;/li&gt;
  &lt;li&gt;economic efficiency pushes the system towards centralization, and decentralization generally only survives due to friction&lt;/li&gt;
  &lt;li&gt;relying on parties not colluding is not great as far as security assumptions go. I trust the difficulty of the discrete logarithm problem in elliptic groups far more than I trust a bunch of random people not to be tricked or coerced into attacking the network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yet, and yet… &lt;strong&gt;decentralization is all we have&lt;/strong&gt;. It would be fantastic to obtain safety and liveness properties from mere cryptographic assumptions, but that is not the case. Mathematical primitives live in the abstract work of statelessness; ledgers and blockchains live in the concrete world of statefulness, hence the omnipresence of the human element in their security models.&lt;/p&gt;

&lt;p&gt;Some good news: double-spending attacks by validators are a limited concern. These attacks must generally target exchanges or bridges because these are fast and automated. It’s tough to commit other types of fraud by corrupting the chain to cause a double spend. Exchanges have means of protecting themselves, especially if the chain offers quick finality, which only leaves cross-chain bridges as sitting ducks.&lt;/p&gt;

&lt;p&gt;To be sure, if merchants couldn’t rely on transactions being final, this would render the use of these systems moot, but the vast majority of transactions are too small to be worth launching a double-spend attack on the entire chain. Moreover, in a world where most people are connected most of the time, the presence of forked blocks more than a few hours in the past is confusing to essentially no one. To be clear, double spending attacks are not a major concern, &lt;strong&gt;because of the security properties of the protocol&lt;/strong&gt;. The chain would be useless if double spending were easy, costless, and could happen willy-nilly. In fact, if you do not have a double spending problem, you generally do not need a blockchain at all.&lt;/p&gt;

&lt;p&gt;Moreover, these attacks, if and when they happen, can be quickly detected and mitigated. In proof-of-stake systems, the perpetrators can be eliminated from the pool of participants, they can be severely punished, and the entire economy can benefit due to the deflationary pressure of the slashing event. From that standpoint, the security properties of proof-of-stake are far more desirable than those of proof-of-work, despite the popular wish to see tradeoffs in everything.&lt;/p&gt;

&lt;p&gt;Realistically, complete liveness attacks that result in halting the chain aren’t a major concern either. They, too, can be remedied with slashing and restarting. The main arguments against this are aesthetic, not pragmatic.&lt;/p&gt;

&lt;p&gt;The big, thorny issue is the &lt;strong&gt;selective censorship of transactions&lt;/strong&gt;. That’s the one to worry about, the property most crucially dependent on decentralization. More on this in a little bit.&lt;/p&gt;

&lt;p&gt;These observations lead to a few conclusions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Cross-chain bridges are brittle things, even when they use light-client security&lt;/li&gt;
  &lt;li&gt;It’s best to not depend on decentralization if you can at all avoid it&lt;/li&gt;
  &lt;li&gt;Not depending on decentralization for censorship resistance would be great&lt;/li&gt;
  &lt;li&gt;Turning safety attacks and censorship attacks into full-blown liveness faults is a good thing, actually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first point is an argument for rollups, which offer the possibility for multiple independent ledgers to bridge with each other without any loss of security (no honest majority assumption is required, unlike light-client bridges in, say, Cosmos)&lt;/p&gt;

&lt;p&gt;The second point is an argument against depending on decentralization to preserve the ledger’s integrity. Optimistic rollups rely on including challenges in the chain for their security, which means validators can collude to censor those challenges and steal assets from rollups. Fortunately, such a censorship attack can be readily observed. If the finalization period for rollups is long enough, &lt;strong&gt;and&lt;/strong&gt; the rollups are enshrined in the protocol, this gives the possibility to hard fork out of an unlikely concerted attack. Again, arguments against such approaches are primarily aesthetic, not pragmatic. Polkadot parachains, similar to rollup in many ways, can transfer assets to each other very quickly, but this comes at the cost of trusting the validators not to collude to steal assets because there is no period in which censorship can be mitigated.&lt;/p&gt;

&lt;p&gt;The third point is an interesting open research problem. Can we stop censorship on a ledger &lt;em&gt;controlled&lt;/em&gt; by bad actors? Perhaps. As mentioned above, censorship can be readily observed. A user posting a transaction can make it quite clear to everyone running a node that they have indeed assigned this transaction, and if that transaction fails to be included in a block, they can infer that censorship took place. This is because, in practice, we have synchronous channels for honest actors to coordinate for sufficiently large synchronization times. But is it possible? One scheme to convert a censorship attack into a liveness attack takes the following form:&lt;/p&gt;

&lt;p&gt;Assume a transaction is emitted at time \(t\), and seen by all nodes at time \(t + dt\), where \(dt\) is suitably large to not worry about network latency, partitions, etc. Node \(i\) sees the transaction at time \(t_i \in [t, t + dt]\). If the transaction is not included at time \(t_i + T\), for some grace period \(T\), then nodes will probabilistically reject blocks that do not contain the censored transaction for each subsequent block. They do so based on a common draw, meaning they will all reject at once or all accept, so long as they agree that time \(T\) has passed.&lt;/p&gt;

&lt;p&gt;If the probability of rejection is high, there is a risk that nodes will reject a block at a time when there is disagreement on whether \(T\) has elapsed. If the probability is low, then the risk of disagreement is lower, but it will take a long time before the chain halts for lack of inclusion. Typically, the probability should be inversely proportional to \(dt\). To be sure, this is not a very good solution. It’s slow and cumbersome, and it would likely only make sense to apply it to fraud proofs or similar critical operations. However, it is a nice proof of existence that there indeed are schemes that can turn censorship by block producers into liveness faults.&lt;/p&gt;

&lt;p&gt;In summary, despite its cost, decentralization remains an essential tool for blockchains, particularly for ensuring censorship resistance. While maintaining or improving decentralization is a great thing, there are promising research avenues for minimizing the reliance on decentralization, and both should be conducted in parallel.&lt;/p&gt;
</description>
        <pubDate>Sun, 22 Oct 2023 21:36:00 +0000</pubDate>
        <link>http://ex.rs/validators-and-decentralization/</link>
        <guid isPermaLink="true">http://ex.rs/validators-and-decentralization/</guid>
        
        
        <category>tezos</category>
        
      </item>
    
  </channel>
</rss>
