<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://thebreadishard.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://thebreadishard.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-11-05T08:01:14+01:00</updated><id>https://thebreadishard.github.io/feed.xml</id><title type="html">thebreadishard</title><subtitle>Notes from a student project on predicting the infrared spectra of large carbon molecules in space, written so that non-specialists can follow along.</subtitle><author><name>Frederic Petrignani</name></author><entry><title type="html">A propeller among springs</title><link href="https://thebreadishard.github.io/2026/11/05/a-propeller-among-springs/" rel="alternate" type="text/html" title="A propeller among springs" /><published>2026-11-05T08:00:00+01:00</published><updated>2026-11-05T08:00:00+01:00</updated><id>https://thebreadishard.github.io/2026/11/05/a-propeller-among-springs</id><content type="html" xml:base="https://thebreadishard.github.io/2026/11/05/a-propeller-among-springs/"><![CDATA[<!-- Written 4 Nov 2026 at the user's request ("Schrijf het concept van de methylpost maar"; TASKS 181 (2)); published 5 Nov on the
     user's word ("Publiceer morgenochtend"). Numbers trace to
     (CapstonePlan repo, plans/05_delta-probed-ir-pipeline):
     ledger 3 Nov 20:4x / 21:5x (TASKS 156 (c), probes/results_m1/gridcheck_methyl_2026-11-03/read.md): imidazole+CH3 route disagreement
       5.63 cm⁻¹ at 75/302, 5.26 at 99/590, share 0.93 (LARGE AMPLITUDE: ≥ 3/4; GRID NOISE: ≤ 1/2; styrene 0.19); the torsion's pairs with the
       methyl C–H stretches stay, its pairs with ring modes fall; torsion φ_iiii ≈ 19,600 cm⁻¹; decoupling moves the methyl bends at 1508 and
       1521 cm⁻¹ by up to 5.9;
     literature report 4 Nov (reports/Methylrotor in anharmonische IR.md): Mendolicchio, Bloino & Barone, JCTC 2022 (acetic acid: curvilinear
       couplings 10–100-fold smaller, CH3 fundamentals move 0–4 cm⁻¹);
     ledger 4 Nov 07:4x (TASKS 173, Psience 1.6.1): decoupling moves mode 20 by 6.50 (Cartesian) / 7.46 (Z-matrix internals), mode 21 3.29 / 4.43;
     ledger 4 Nov 10:3x (TASKS 177, scan_read.json): relaxed scan 13 points, V3 = 227.6 cm⁻¹, F = 5.65, 1D fundamental 91.3 (VPT2 94–97,
       harmonic 108.0); microwave V3 = 317.20(14) cm⁻¹, Gougoula, Medcraft, Heitkämper & Walker, J. Chem. Phys. 151, 144301 (2019); −28 %;
     PreRegistration_2026-11-04_Methyl_Rotor_Variational.md (part B, amendments 1–2, outcomes; commits 4b598861, a294398c, 792304b2):
       24 × 11 × 11 = 2,904 points, 12-point subset moves Δ_var by 0.97 > 0.3; 48 × 11 × 11 = 5,808 points, 2,904 reused, run 17:13–20:39;
       Δ_var +1.40 / +0.10 at 48, +1.37 / +0.10 on the 24-point subset, convergence 0.033; line (iii); limits as stated;
     Design_2026-10-13_Spectral_Shape_Anharmonic_Step.md §8 (commit 9e0387ee): the policy. -->

<p>Yesterday’s post was about axes. This one is about a small part of a molecule that refuses to stay put: the methyl group. It took two
days, five calculations and rules written down in advance before we knew how to treat it. This post explains what a methyl group does,
why our usual method struggles with it, and what we decided.</p>

<h2 id="a-propeller-on-a-ring">A propeller on a ring</h2>

<p>Many of the molecules in this project are flat rings of carbon atoms. Sometimes one hydrogen on the ring is replaced by a <strong>methyl group</strong>:
one carbon atom carrying three hydrogen atoms, written CH₃. Seen from the ring, the methyl group looks like a small propeller with three
blades.</p>

<p>That propeller can turn. As it turns, each hydrogen passes close to the atoms next to it on the ring, which pushes back a little. So the
propeller has three comfortable positions per full turn, with a small hill between each pair of them. The height of that hill is called
the <strong>barrier</strong>. A methyl group with a high barrier mostly rocks back and forth in one valley. A methyl group with almost no barrier spins
nearly freely.</p>

<p>Our test molecule is 4-methylimidazole, a five-membered ring with two nitrogen atoms and one methyl group. Its barrier is low compared with
a chemical bond, but high compared with the energy needed to set the propeller rocking. In the language of the field it is a <em>hindered</em>
rotor: it rocks, and only occasionally hops over a hill.</p>

<h2 id="why-a-propeller-is-hard-for-our-method">Why a propeller is hard for our method</h2>

<p>To predict the positions of the bands in an <a href="/glossary/#infrared-spectrum">infrared spectrum</a>, we treat every vibration as a spring.
Each <a href="/glossary/#normal-mode">normal mode</a> is one pattern of motion in which all atoms move together. The first guess, the harmonic one,
treats each spring as ideal. Real springs are not ideal: they stiffen or soften as they stretch, and they pull on each other. That is
<a href="/glossary/#anharmonicity">anharmonicity</a>, and it shifts each band by a few to tens of <a href="/glossary/#wavenumber">wavenumbers</a> (cm⁻¹, the
unit of the spectrum).</p>

<p>Our method for these shifts is <a href="/glossary/#vpt2">VPT2</a>. It starts from the springs and adds small corrections. Its central assumption is
that every atom makes small motions around one resting position. For a ring that holds well. For the propeller it does not: turning the
methyl group is a large motion, around a circle, with three resting positions instead of one.</p>

<p>The propeller also touches other vibrations. The three hydrogens can fold together and apart like an umbrella; these are the <strong>CH₃ bends</strong>,
two bands near 1508 and 1521 cm⁻¹, a wavelength of about 6.6 micrometres, inside the part of the spectrum we predict. When the propeller
turns, those bends change a little. VPT2 sees this as a <strong>coupling</strong> between the turning and the bending, and it shifts the bends for it.
The question of this post is whether that shift is real.</p>

<h2 id="the-first-sign-two-routes-disagree">The first sign: two routes disagree</h2>

<p>We compute some of VPT2’s numbers twice, by two independent routes. If both routes give the same number, it is probably right. If they
disagree, something in the calculation is noisy. Two of the molecules in our study of these shifts, both with a methyl group, had a disagreement of 5.6 and
7.2 cm⁻¹, far above what we accept.</p>

<p>A disagreement like that has two possible causes. The first is numerical noise from the
<a href="/glossary/#integration-grid">integration grid</a>, the set of points on which the program evaluates its formulas. A finer grid removes that
kind of noise. The second is that the motion is too large for the method. A finer grid does not help then.</p>

<p>On 3 November we repeated the calculation for 4-methylimidazole on a finer grid. We had written down beforehand how to tell the two causes
apart: if the disagreement fell to half or less, it was grid noise; if three quarters or more remained, it was the large motion. For a
similar case in October, a vinyl group on styrene, only 0.19 of the disagreement had remained: grid noise. For the methyl group 0.93
remained. The worst pairs were all the propeller with the methyl group’s own C–H bonds. The verdict: the propeller’s large motion, not
the grid.</p>

<p>That left a practical question. A common remedy in the literature is to take the turning motion out of VPT2 altogether, so that it cannot
shift anything else. Doing that moved the CH₃ bends by up to 5.9 cm⁻¹. That is about twice the accuracy we promise per family of bands
(3 cm⁻¹). So the choice mattered, and we did not know which choice was right.</p>

<h2 id="two-coordinates-one-answer">Two coordinates, one answer</h2>

<p>A literature search on 4 November gave a reason to doubt the remedy. Atomic motion can be described in two ways. One way uses straight-line
displacements of every atom along x, y and z. The other uses bond lengths, bond angles and twist angles. A turning propeller is naturally
an angle: in straight lines, its hydrogens would leave their circle. A 2022 study on acetic acid (Mendolicchio, Bloino and Barone) found
that in angle-based coordinates the couplings of a methyl group become 10 to 100 times smaller, while the bands themselves move by
only 0 to 4 cm⁻¹. That suggested the large couplings might be an artefact of straight lines that cancels in the full result.</p>

<p>We tested this with a second, independent program, Psience. We took the turning motion out of VPT2 in both kinds of coordinates. The
bends moved by 6.5 cm⁻¹ in straight-line coordinates and by 7.5 cm⁻¹ in angle coordinates. Both descriptions said the same thing, so this
test did not decide. (The same comparison also exposed an error in our own code, the subject of yesterday’s post.)</p>

<h2 id="checking-the-hill">Checking the hill</h2>

<p>Next we measured the barrier itself. We turned the propeller in 13 steps through a third of a turn and let the rest of the molecule relax
at each step. Our level of theory, B3LYP, puts the barrier at 228 cm⁻¹. A microwave measurement, which reads a molecule’s rotation very
precisely, gives 317 cm⁻¹ (Gougoula and co-workers, 2019). So our method makes the hill 28 % too low. A real molecule is therefore more
hindered than our model of it. The propeller’s own rocking frequency follows from the same scan: 91 cm⁻¹, far below the window of the
spectrum we predict.</p>

<h2 id="solving-it-without-the-shortcut">Solving it without the shortcut</h2>

<p>VPT2 is a shortcut: it replaces the exact problem by springs plus small corrections. For three motions only, the propeller and the two
bends, we can avoid the shortcut and solve the problem directly. This is called a <strong>variational</strong> calculation.</p>

<p>It works like this. We place the atoms at many combinations of three settings: the propeller’s angle, and how far each of the two bends
is folded. At every combination we compute the energy. Together those energies form a map of the landscape the atoms move in. Then we solve
the quantum mechanics of the three motions on that map, without assuming small motions. To isolate the coupling, we solve it twice: once
on the real map, and once on a map from which the link between turning and bending has been removed. The difference in the bend’s band is
the true effect of the coupling. Then we compare it with what VPT2 says.</p>

<p>Before computing anything, we wrote down three rules:</p>

<ol>
  <li><strong>Converged.</strong> The answer must not change by more than 0.3 cm⁻¹ when we use half as many propeller angles. Otherwise the map is too
coarse, and there is no verdict.</li>
  <li><strong>VPT2 is right</strong> if the variational shift and VPT2’s shift agree within 1.5 cm⁻¹ for both bends.</li>
  <li><strong>VPT2’s coupling is not real</strong> if the variational shift is at most 1.5 cm⁻¹ while VPT2’s is 3 cm⁻¹ or more.</li>
</ol>

<p>The first map had 24 propeller angles, 15 degrees apart, and 11 settings for each bend: 2,904 energies. It was not converged. Halving the
angles moved the answer by 0.97 cm⁻¹, more than three times the allowed 0.3. So there was no verdict.</p>

<p>We then doubled the angles to 48, 7.5 degrees apart. Every second angle of the new map was an angle of the old one, so we reused those
2,904 energies and computed 2,904 new ones, about three and a half hours on the laptop. This time the map was converged: halving the
angles moved the answer by 0.03 cm⁻¹.</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Variational</th>
      <th>VPT2, straight lines</th>
      <th>VPT2, angles</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Bend near 1508 cm⁻¹</td>
      <td>+1.4</td>
      <td>+6.5</td>
      <td>+7.5</td>
    </tr>
    <tr>
      <td>Bend near 1521 cm⁻¹</td>
      <td>+0.1</td>
      <td>+3.3</td>
      <td>+4.4</td>
    </tr>
  </tbody>
</table>

<p><em>The shift of each CH₃ bend caused by its coupling to the turning propeller, in cm⁻¹.</em></p>

<p>Rule 2 fails: 1.4 and 6.5 are far apart. Rule 3 holds: 1.4 is under 1.5, and both VPT2 values are above 3. So by the rule written in
advance, the coupling VPT2 puts between turning and bending is not really there for this molecule.</p>

<h2 id="how-firm-is-that">How firm is that?</h2>

<p>We wrote three limits down with the verdict.</p>

<ul>
  <li><strong>The margin is small.</strong> 1.4 lies only 0.1 under the line of 1.5. The convergence check moved the answer by 0.03, so the verdict stands,
but not by much.</li>
  <li><strong>The map holds only the direct link.</strong> It moves the propeller and the two bends. VPT2 also lets the propeller influence the bends
indirectly, through all the other vibrations of the molecule. That indirect part was not tested.</li>
  <li><strong>One molecule, one method.</strong> Everything rests on 4-methylimidazole at the B3LYP level, whose barrier is too low. A real methyl group
here is more hindered, which points the same way, but we have not tested other molecules.</li>
</ul>

<h2 id="what-we-decided">What we decided</h2>

<p>The project now has a rule for hindered methyl groups. The CH₃ bends get their anharmonic shift from VPT2 with the
turning motion taken out. The propeller’s own rocking band comes from the scan, not from VPT2. The methyl group’s C–H stretches, near
3 micrometres, lie outside the window we predict and stay as they are.</p>

<p>The rule says how to compute a methyl band. It does not yet say how far such a band can be trusted. The certificate that comes with every
predicted spectrum will keep marking methyl bands as outside the measured accuracy, until a test on more than one molecule, again
registered in advance, gives them an error bar of their own. A nearly free methyl group, like the one on toluene, has an even lower hill
and is not covered by the rule at all.</p>

<p>The code for these calculations was written and tested with an AI assistant, Claude, under my direction. The variational read is in
commit <a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/792304b2">792304b2</a>, and the rule in
<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/9e0387ee">9e0387ee</a>.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="anharmonic" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Which way is the molecule facing?</title><link href="https://thebreadishard.github.io/2026/11/04/which-way-is-the-molecule-facing/" rel="alternate" type="text/html" title="Which way is the molecule facing?" /><published>2026-11-04T19:29:20+01:00</published><updated>2026-11-04T19:29:20+01:00</updated><id>https://thebreadishard.github.io/2026/11/04/which-way-is-the-molecule-facing</id><content type="html" xml:base="https://thebreadishard.github.io/2026/11/04/which-way-is-the-molecule-facing/"><![CDATA[<!-- Written 4 Nov 2026 at the user's request ("Vandaag 2, morgen 1? Let ... goed op de lekentaal"). Published 4 Nov on the user's word ("Publiceer"). Numbers trace to
     (CapstonePlan repo, plans/05_delta-probed-ir-pipeline):
     ledger 28 Oct 08:2x (the energy route as a second route on benzene, pyscf and psi4; production quartics come from displaced
       analytic pyscf Hessians, src/dpir/qff.py);
     ledger 28 Oct 12:1x (the lead: psi4 1.10 orients its grid by the nuclear-charge principal axes, libfock/cubature.cc OrientationMgr;
       benzene's in-plane moments 189.944 / 189.953; frame turn at q = ±0.4: mode 5 6.6°/5.2°, mode 11 3.2°/3.3°, mode 10 0.07°;
       psi4 mode 5 φ_iiii 849.7 at 99/590 against pyscf's 265.0);
     ledger 3 Nov 19:4x / 19:5x and probes/results_m1/frame_proof_142a_2026-11-03/read.md (commit c84d4ca8): pyscf 2.14.0, B3LYP/STO-3G,
       99/590, lab frame against charge frame: mode 5 −3.5 → 1092.8, mode 11 12.4 → 132.2, mode 10 132.3 → 132.5; three registered rules hold;
     ledger 3 Nov 20:3x / 20:4x (750 of 972 pool molecules on analytic labels; 222 still on psi4 finite differences, queued: TASKS 171;
       TASKS 142 (c), a psi4 issue, parked on the user's word);
     TASKS_closed 174 and ledger 4 Nov 08:4x, commits 648a001e (fix) and 63ab66bf (redo; probes/results_m1/coriolis_redo_2026-11-04/compare.md):
       found by TASKS 173's Psience bridge; water +23.0 cm⁻¹ on two modes against Psience; 4-methylimidazole ≤ 0.04, S2's 18–20-atom
       molecules ≤ 0.005, benzene 0.000; code since 21 Sep; the one Coriolis test used psi4 data in psi4's principal orientation;
       rotation test 1e-6; water within 0.003 / 0.011 / 0.012 cm⁻¹ of Psience 1.6.1; 72 reports and 28 label sets rebuilt; reads move
       ≤ 0.04 cm⁻¹; 0 verdict fields changed; one GVPT2 label (A_90df3e4aa9 mode 29) 1284.51 → 1279.00, a near 50/50 polyad member. -->

<p>A molecule floating in space does not care which way it faces. Turn it a quarter turn, or upside down, and it is still the same molecule
with the same energy and the same <a href="/glossary/#infrared-spectrum">infrared spectrum</a>. A computer program, however, has to write the
positions of the atoms down as numbers along three axes, x, y and z, so it must choose which way those axes point. A good program gives
the same answer whatever it chooses.</p>

<p>This week we found two places where that went wrong. The first was in Psi4, someone else’s program. The second was in our own code.
Neither of them changed a result of this project. Both are worth telling, because they show how a small choice about axes can hide in
a calculation for weeks.</p>

<h2 id="part-1-a-grid-that-turned-with-the-molecule">Part 1: a grid that turned with the molecule</h2>

<h3 id="what-an-integration-grid-is">What an integration grid is</h3>

<p>Our cheaper calculations use <a href="/glossary/#dft">density functional theory</a>, DFT for short. DFT finds a molecule’s energy by adding up a
formula over all of the space around the atoms. A computer cannot add up infinitely many points, so it picks a finite set of points
around every atom and adds up the formula only there. That set of points is called the
<a href="/glossary/#integration-grid">integration grid</a>.</p>

<p>You can compare this with estimating the area of a pond. You lay a net of evenly spaced pins over the map and count how many pins fall
in the water. The answer is close, but not exact, and it changes slightly if you shift or turn the net. A DFT grid has the same kind of
small error, and that error also changes slightly when the grid turns against the atoms.</p>

<h3 id="why-psi4-turns-its-grid">Why Psi4 turns its grid</h3>

<p>The makers of <a href="/glossary/#psi4">Psi4</a> wanted the energy to stay the same when a user turns a molecule. Their solution: before
calculating, Psi4 lines its grid up with the molecule itself. If the molecule turns, the grid turns along with it, so the pins land on
the same spots and the small error stays the same.</p>

<p>To do that, Psi4 needs a direction that belongs to the molecule. It uses what we can call the molecule’s <strong>charge axes</strong>. Imagine every
atomic nucleus as a small weight, heavier the more charge it has. Every object made of weights has three natural axes, the way a book
has a long side, a short side and a thickness. Psi4 points its grid along those three axes.</p>

<h3 id="where-that-goes-wrong-benzene-is-almost-round">Where that goes wrong: benzene is almost round</h3>

<p>Benzene is a flat ring of six carbon atoms, a regular hexagon. Within its plane it is almost round. Its two in-plane axes are nearly
equal: in our numbers 189.944 against 189.953, a difference of less than one part in ten thousand. For a book that would mean the long
side and the short side are the same length. Which direction counts as “long” is then decided by a hair.</p>

<p>A molecule vibrates. In each <a href="/glossary/#normal-mode">normal mode</a>, all atoms move together in a fixed pattern. When benzene moves a
little along one of these patterns, the hair changes and the charge axes can swing round by several degrees, while the atoms themselves
have hardly moved. For vibration number 5, a small push turns the axes by 6.6 degrees one way and 5.2 degrees the other way. The grid
turns with them. For vibration number 10, the same push turns the axes by only 0.07 degrees.</p>

<h3 id="why-that-matters-for-a-spectrum">Why that matters for a spectrum</h3>

<p>A vibration behaves like a spring. A spectrum needs two things from that spring: how stiff it is, and how its stiffness changes when
the atoms move further apart. The second part is called the fourth-power term. It is small, but it shifts the bands of the spectrum.</p>

<p>There are two ways to get these numbers. The direct way, <a href="/glossary/#analytic-derivative">analytic derivatives</a>, lets the program
calculate the stiffness from its formulas in one go. The indirect way, <a href="/glossary/#finite-differences">finite differences</a>, pushes the
atoms a little along the vibration, calculates the energy at each step, and fits a curve through those energies. The project mostly uses
the direct way. On 28 October we used the indirect way on purpose, as a second route to check benzene’s numbers, once in PySCF and once
in Psi4.</p>

<p>The indirect way is very sensitive to bumps in the curve. If the grid turns between two steps, each energy carries a slightly different
grid error, and the curve gets small bumps that have nothing to do with the molecule. For vibration number 5 of benzene, Psi4’s curve
gave a fourth-power term of 849.7, where PySCF, another program, gave 265.0 (both in <a href="/glossary/#wavenumber">wavenumbers</a>, cm⁻¹, the
unit of the spectrum).</p>

<h3 id="from-suspicion-to-proof">From suspicion to proof</h3>

<p>On 28 October we had a suspicion, not a proof: the order matched. The vibrations whose axes turned most were the ones where Psi4 missed
most, and vibration 10, whose axes hardly turned, was the one where Psi4 agreed with PySCF.</p>

<p>On 3 November we tested the suspicion inside one program, so that nothing else could differ. <a href="/glossary/#pyscf">PySCF</a> normally keeps
its grid fixed in space. We made PySCF calculate the same 25 energies twice. The first time it used its normal fixed grid. The second
time we first turned each geometry onto its own charge axes, the way Psi4 does. Turning a whole molecule does not change its true energy,
so any difference between the two runs had to come from the grid.</p>

<p>Before running, we wrote down what would count as proof: vibrations 5 and 11 should change a lot, vibration 10 should hardly change,
and vibration 5 should change more than 11, because its axes turn further. The results:</p>

<table>
  <thead>
    <tr>
      <th>Vibration</th>
      <th>Axes turn by</th>
      <th>Fourth-power term, fixed grid</th>
      <th>Fourth-power term, grid on charge axes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5</td>
      <td>6.6° / 5.2°</td>
      <td>−3.5</td>
      <td>1,092.8</td>
    </tr>
    <tr>
      <td>11</td>
      <td>3.2° / 3.3°</td>
      <td>12.4</td>
      <td>132.2</td>
    </tr>
    <tr>
      <td>10</td>
      <td>0.07°</td>
      <td>132.3</td>
      <td>132.5</td>
    </tr>
  </tbody>
</table>

<p>All three rules held. The same program, with the same method and the same grid, gives a very different curve once its grid turns with
the charge axes. Psi4’s misses on benzene have the size and the direction you expect from this effect alone.</p>

<h3 id="what-this-means">What this means</h3>

<p>Psi4’s energies are not wrong in the usual sense. The turning trick was meant to remove a small error, and for most molecules it does.
For almost round molecules such as benzene, the trick makes the error jump from one step to the next instead.</p>

<p>For our project this was already handled, for two reasons. First, the project’s first collection of molecules was calculated in Psi4
by finite differences, so those numbers could carry this effect. Since then most molecules that train our network get their stiffness
from PySCF by the direct way, without steps. That is true for 750 of the 972 molecules in the training set. The last 222 are queued on a
server. Second, where we do need steps, for the fourth-power terms, PySCF calculates the full stiffness directly at each pushed
geometry, and PySCF keeps its grid fixed in space, so the grid does not turn between the steps. We have not reported this to Psi4
yet. Our evidence is indirect: Psi4 has no switch to turn the orientation off, so we could only copy its behaviour in another program.</p>

<h2 id="part-2-our-own-turn">Part 2: our own turn</h2>

<h3 id="what-the-coriolis-term-is">What the Coriolis term is</h3>

<p>A molecule does not only vibrate. It also tumbles through space. When atoms move while the molecule rotates, they feel a sideways push.
This push is the same Coriolis effect that makes a ball rolled across a turning merry-go-round seem to curve. In a molecule, that push
couples the vibrations to each other a little, and it shifts the bands by a small amount.</p>

<p>Our method for these shifts is called <a href="/glossary/#vpt2">VPT2</a>. It corrects the “ideal spring” picture of a vibration for the ways real
bonds stiffen and soften. One of its terms is the <a href="/glossary/#coriolis-coupling">Coriolis term</a>. That term multiplies two ingredients
for each of the three axes:</p>

<ul>
  <li>how strongly the rotation around that axis couples two vibrations;</li>
  <li>how easily the molecule spins around that axis (its rotational constant).</li>
</ul>

<p>Both ingredients must refer to the same axis. Otherwise the term multiplies the coupling around one axis with the spin around another.</p>

<h3 id="the-mistake">The mistake</h3>

<p>Our code calculated the couplings around the x, y and z axes of the file the molecule happened to be stored in. It took the rotational
constants around the molecule’s own axes, sorted from the easiest spin to the hardest. These two sets of axes are the same only when
the stored molecule already lies along its own axes, in exactly that order.</p>

<p>The code had been like this since 21 September. Our only test of the Coriolis term used data from Psi4, and Psi4 always turns a
molecule onto its own axes before saving it. So in the one test we had, the molecule lay exactly right, and the mistake could not show.
Psi4’s habit of turning molecules, the subject of Part 1, had hidden our own error.</p>

<h3 id="how-we-found-it">How we found it</h3>

<p>On 4 November we ran our own calculations through a second, independent program, Psience (version 1.6.1), to check something else. For
water, two of the three bands differed from Psience by 23.0 cm⁻¹. That is far too large to be rounding, and it led straight to the
mixed-up axes.</p>

<h3 id="why-our-results-barely-moved">Why our results barely moved</h3>

<p>The size of the Coriolis term depends on the rotational constant, and that constant depends on the molecule’s size. Water is small and
light, and it spins easily, so the term is large. The molecules of this project are big rings of carbon, and they turn slowly, so the
term is small. A wrong pairing of axes therefore mattered a lot for water, and very little for our molecules. For
4-methylimidazole (12 atoms) the bands moved by at most 0.04 cm⁻¹. For our molecules of 18 to 20 atoms they moved by at most
0.005 cm⁻¹. For benzene they did not move at all.</p>

<h3 id="the-repair">The repair</h3>

<p>The repaired code calculates the couplings around the same axes, in the same order, as the rotational constants. A new test turns a
molecule through space and checks that every band stays the same to one millionth of a wavenumber. That is exactly the property the old
code lacked. Water now agrees with Psience to within 0.012 cm⁻¹ on all three bands.</p>

<p>Then we redid everything the term had touched: 72 stored calculations and 28 sets of band labels, followed by every reading we had made
from them, once with the old inputs and once with the new ones. The largest change in a score was 0.04 cm⁻¹. Not one verdict changed.</p>

<p>One band label did move by 5.5 cm⁻¹. That band is a near-even mix of two vibrations, and which of the two gets the label can flip on a
tiny change. Its underlying value did not move.</p>

<h2 id="the-common-lesson">The common lesson</h2>

<p>Both problems were about axes. A molecule’s real answer does not depend on which way it faces, so there is a simple check: turn the
molecule, and see that nothing changes. Our new test does exactly that. The pyscf experiment of Part 1 is the same check, applied to
another program’s grid.</p>

<p>The code in both parts was written and tested with an AI assistant, Claude, under my direction, like the rest of the project’s
software. The fix is in commit <a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/648a001e">648a001e</a>, the redo in
<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/63ab66bf">63ab66bf</a>, and the grid experiment in
<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/c84d4ca8">c84d4ca8</a>.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="software" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Six stations</title><link href="https://thebreadishard.github.io/2026/11/02/six-stations/" rel="alternate" type="text/html" title="Six stations" /><published>2026-11-02T17:08:32+01:00</published><updated>2026-11-02T17:08:32+01:00</updated><id>https://thebreadishard.github.io/2026/11/02/six-stations</id><content type="html" xml:base="https://thebreadishard.github.io/2026/11/02/six-stations/"><![CDATA[<!-- Written 2 Nov 2026 at the user's request ("Ja, schrijf het concept maar"; TASKS 167). Published 2 Nov on the user's word ("Publiceer maar"). Numbers trace to
     (CapstonePlan repo, plans/05_delta-probed-ir-pipeline):
     probes/pipeline_show_summary.py and its record probes/results_m1/pipeline_show_summary_2026-11-02.md (commit efd796cb): the 38 runs the
       show links to, per role: hold-out (a) 8 runs, 12–26 atoms, median rms 1.64, largest 3.22; hold-out (b) 4 runs, 23–28 atoms, median
       2.39, largest 2.73; catalogue only 26 runs, 22–29 atoms, median 1.48, largest 4.60; median overlap 0.993 / 0.984 / 0.993; laptop
       0.8–2.5 s per run; corpus steps 3–4 7–10,914 s, median 5,669 s; four runs above 3 cm⁻¹ (3.22, 3.09, 3.90, 4.60);
     modules/08_industry_synthesis/m08/show.py and website/export/pipeline_show.py (commit 63063265): the six stations and their tools;
     probes/results_m1/pipeline_holdout_a_2026-11-02 (commit 344ff57b): hold-out (a) through the pipeline;
     ledger 30 Oct 01:0x (atom order: another SMILES writing moves phenanthrene's corrected bands by up to 2.92 cm⁻¹, B3LYP by 1e-11),
       30 Oct 02:3x (phenanthrene at pipeline 0.2: 4,545 s, 66 bands; all modes network 1.17 / B3LYP 23.48 cm⁻¹ against ωB97X),
       30 Oct 02:4x (the dihedral wrap: ≤ 0.12 cm⁻¹ of rms on hold-out (a)),
       30 Oct 15:4x (the seen check: 2 of hold-out (a)'s 10 molecules trained on as their layer-B twins; hold-out (b) clean, 0 of 39);
     decision 65 (licence: a family is licensed where the error is ≤ 3 cm⁻¹ on both hold-outs; 'other' 3.62 / 3.89, not licensed). -->

<p>The <a href="https://thebreadishard.github.io/spectrum-atlas/">Spectrum Atlas</a> has a new page:
<a href="https://thebreadishard.github.io/spectrum-atlas/pipeline/">the pipeline show</a>. It follows one molecule from its name to its predicted
<a href="/glossary/#infrared-spectrum">infrared spectrum</a>, step by step, and ends with a certificate that says how far the prediction can be
trusted. This post explains what you see on that page and why each step is there.</p>

<h2 id="what-the-page-shows">What the page shows</h2>

<p>The page has three columns. On the left you pick a molecule, sorted by its number of carbon atoms. In the middle the pipeline is drawn
as a metro line with six stations, and the stations light up as the molecule passes through them. On the right the spectrum appears,
with the certificate under it.</p>

<p>Every number on the page is read from the files of a real run. The page itself contains no numbers that someone typed in. If a run had
gone wrong, the page would show the wrong result, not a nicer one.</p>

<p>Molecule pages in the Atlas now link to the show. Where the Atlas lists a molecule that went through the pipeline, its page has a card
“Through the pipeline” that opens the show at that molecule. Phenanthrene is
<a href="https://thebreadishard.github.io/spectrum-atlas/molecule/A_541c53d1a5/">one example</a>.</p>

<h2 id="the-six-stations">The six stations</h2>

<p><strong>1. Molecule.</strong> The input is a short line of text that describes the molecule, a SMILES string. The pipeline first checks whether the
molecule is one it may answer for: neutral, with paired electrons, and made only of elements the network has seen in training. That
check happens before any computation, so a molecule outside the network’s range costs nothing.</p>

<p><strong>2. Start geometry.</strong> A free chemistry library, RDKit, turns the text into a first three-dimensional shape.</p>

<p><strong>3. Optimisation.</strong> A quantum chemistry program, <a href="/glossary/#psi4">psi4</a>, moves the atoms until the forces on them vanish. This gives
the molecule’s resting shape, its <a href="/glossary/#equilibrium-geometry">equilibrium geometry</a>, with the cheap method
<a href="/glossary/#b3lyp">B3LYP</a>.</p>

<p><strong>4. Hessian.</strong> A second program, <a href="/glossary/#pyscf">PySCF</a>, computes how stiff the molecule is in every direction, the
<a href="/glossary/#hessian">Hessian</a>, and how much each vibration changes the molecule’s charge distribution. The stiffness decides where the
bands of the spectrum lie. The charge change decides how strong each band is.</p>

<p><strong>5. The network.</strong> Here the project’s own part comes in. The <a href="/glossary/#neural-network">neural network</a> predicts a
<a href="/glossary/#correction">correction</a> to the cheap stiffness. Three copies of the network, trained from different random starts
(<a href="/glossary/#seed">seeds</a>), each give their correction, and the pipeline adds their average to the B3LYP Hessian. Where the three copies
disagree, the certificate shows the spread.</p>

<p><strong>6. Spectrum and certificate.</strong> From the corrected stiffness the pipeline computes the positions of the bands, and from step 4 their
heights. Then it writes the certificate: for every band, which family of vibrations it belongs to, how large the network’s error was
on molecules it never saw, and whether that family is licensed.</p>

<h2 id="what-the-network-corrects-towards">What the network corrects towards</h2>

<p>The network does not yet correct towards the expensive reference this project is about, <a href="/glossary/#coupled-cluster">coupled cluster</a>.
For now it corrects B3LYP towards <a href="/glossary/#wb97x">ωB97X</a>, a more careful <a href="/glossary/#dft">DFT</a> method that we can afford to compute for
hundreds of molecules. ωB97X plays the role of the better method, a <a href="/glossary/#proxy">proxy</a>, so that we can test whether the whole
machine works before we pay for the real reference.</p>

<p>That choice also gives every molecule in the show a known right answer, because the <a href="/glossary/#corpus">corpus</a> holds an ωB97X
calculation for each of them. So the certificate can put the network’s spectrum next to the right answer and give the difference.</p>

<h2 id="how-close-it-comes">How close it comes</h2>

<p>The show holds 38 molecules. None of them was used to train the network. They fall into three groups.</p>

<p>Eight molecules belong to the first <a href="/glossary/#hold-out-set">hold-out set</a>, hold-out (a): the molecules we set aside before training
to measure the error. They have 12 to 26 atoms. Their band positions are on average 1.64 <a href="/glossary/#wavenumber">wavenumbers</a> from the
ωB97X answer (the median over the eight), and 3.22 in the worst case.</p>

<p>Four belong to the second hold-out set, hold-out (b), with 23 to 28 atoms. Their median is 2.39 wavenumbers, their worst 2.73.</p>

<p>The other 26 are in the catalogue but were never part of training or of either hold-out set. Their median is 1.48 wavenumbers, their
worst 4.60, for a naphthalene with two nitro groups.</p>

<p>To give a sense of scale: for phenanthrene, B3LYP alone is 23.5 wavenumbers from ωB97X on average. With the network’s correction the
distance is 1.2.</p>

<p>Thirty-four of the 38 molecules come within 3 wavenumbers on average. That average includes the bands the certificate does not license.
The licence works per family of vibrations, not per molecule. A family is licensed where the network’s measured error is at most
3 wavenumbers on both hold-out sets. Three families pass: the ring vibrations in the plane, the C–H stretches and the C–H bends out of
the plane. The fourth family, “other”, holds the vibrations that fit none of the three, most of them slow motions of the whole skeleton
at the low end of the spectrum. It misses the line with 3.62 and 3.89. So the certificate
marks those bands “not licensed”, even where they happen to be right.</p>

<h2 id="three-things-the-pipeline-taught-us">Three things the pipeline taught us</h2>

<p>Running whole molecules through the chain found three faults that tests on single parts had missed.</p>

<p><strong>The order of the atoms mattered.</strong> The same molecule can be written as several different SMILES strings, and each writing numbers the
atoms differently. B3LYP does not care: its bands moved by less than a billionth of a wavenumber. The network did care. For phenanthrene
another writing moved single corrected bands by up to 2.9 wavenumbers. Until we find the cause, the pipeline always starts from one
fixed writing, the canonical SMILES, so the same molecule always gets the same answer.</p>

<p><strong>Two of our test molecules had been seen.</strong> The certificate now also says whether the network trained on the molecule. That check found
that two of the ten molecules in hold-out (a) were in the training set under another name. So part of hold-out (a)’s error had been
measured on molecules the network knew. Those two are now out of the training for the next version, and the show uses only the eight
clean ones. Hold-out (b) was clean: none of its 39 molecules had been seen.</p>

<p><strong>A flat molecule sits on a seam.</strong> In a flat molecule many angles lie at exactly half a turn, where the number jumps from plus to minus.
A tiny change in the shape can flip such an angle to the other side, and the network then sees a different input. We measured how much
this costs: at most 0.12 wavenumbers on the average error of hold-out (a). So it is a reproducibility flaw, not a large error.</p>

<h2 id="what-is-not-in-the-show-yet">What is not in the show yet</h2>

<p>The 38 runs in the show did not compute steps 3 and 4 on the laptop. The corpus had already done exactly those calculations at the same
settings, so the pipeline takes them from there and says so on the station. That is why a run in the show takes less than three seconds.
When the corpus computed steps 3 and 4, they took about an hour and a half for a typical molecule (the median) and three hours for the
longest. A new molecule pays that time. A full run of phenanthrene on 30 October, with every step computed, took 76 minutes.</p>

<p>The page and the pipeline were written and tested with an AI assistant, like the rest of the project’s code.</p>

<p>The scripts, the runs and the records behind every number are in the project’s repository.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="pipeline" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">The hill, eighteen times</title><link href="https://thebreadishard.github.io/2026/11/01/the-hill-eighteen-times/" rel="alternate" type="text/html" title="The hill, eighteen times" /><published>2026-11-01T21:40:33+01:00</published><updated>2026-11-01T21:40:33+01:00</updated><id>https://thebreadishard.github.io/2026/11/01/the-hill-eighteen-times</id><content type="html" xml:base="https://thebreadishard.github.io/2026/11/01/the-hill-eighteen-times/"><![CDATA[<!-- Written 1 Nov 2026 at the user's request ("Schrijf de blog over de geometrieterm"); published 1 Nov on the user's word ("Publiceer maar"). Numbers trace to
     (CapstonePlan repo, plans/05_delta-probed-ir-pipeline):
     GoalGathering/notes/PreRegistration_2026-10-31_Geometry_Term.md — registered 31 Oct (commit 613d1381), amendment 1 (dd3890a3),
       amendment 2 before the read (ac7c0f44), outcome (179669fd);
     probes/results_m1/geometry_term_2026-10-31/read/geometry_term_read.md and .json (commit 179669fd): 18 molecules, 758 modes moved
       by >= 1 cm-1; rms shift 10.99, residual 2.41 (true displacement) and 2.41 (Newton step); windows below 1000 8.40 -> 2.19 (332),
       1000-1900 13.99 -> 3.10 (289), 1900-2700 21.94 -> 1.12 (6, not read), X-H stretch 8.12 -> 0.36 (131); helped 18 of 18; per
       molecule 0.72-4.69; without A_8f6ed7c002 and B_190a33a33e: 16 molecules, 680 modes, 11.16 -> 2.39; the left-out G modes at
       39.9 and 41.0 cm-1; overlap^2 below 0.5 in five molecules, lowest 0.324 (B_a41f4cff9d, residual 4.69).
     The imaginary torsions (41.1i, 43.1i cm-1): amendment 2's text. Benzene numbers: the post of 22 Sep. -->

<p>On 22 September this notebook found out that <a href="https://thebreadishard.github.io/spectrum-atlas/molecule/A_8448043181/">benzene</a>’s
carbon–hydrogen stretches had come out forty-five to sixty wavenumbers too low, and why
(<a href="/2026/09/22/the-hill-was-not-where-we-stood/">The hill was not where we stood</a>). Our expensive calculations are made at
the cheap method’s resting shape of the molecule, its <a href="/glossary/#equilibrium-geometry">equilibrium geometry</a>, not at their own. A bond
that the better method would make slightly shorter is measured while it is slightly too long, like a guitar string tuned at the wrong
tension. That post also found a way to fix this without moving anything: a short formula, which we called the <em>geometry term</em>,
predicts how far each vibration shifts when the molecule relaxes to the better method’s resting shape. On benzene it worked.</p>

<p>That was one molecule. A rule that holds on one molecule is a hint, not a rule. This post is about the test on eighteen more.</p>

<h2 id="the-question">The question</h2>

<p>The geometry term multiplies two things. The first is how far each atom would move if the molecule relaxed to the better method’s
shape. The second is how quickly each vibration’s stiffness changes when the atoms move: a third derivative of the energy, the same
numbers the anharmonic step of this project computes anyway (see <a href="/2026/10/14/half-price-if-you-write-it-down-first/">Half price</a>).
Both are cheap compared with a full new calculation at the better level.</p>

<p>So the question had two parts. Does the formula predict the shifts on molecules other than benzene? And does it still work if we only
<em>estimate</em> where the atoms would go, with one step from the forces at the start, instead of actually relaxing the molecule? The second
part matters for the product: one step costs one gradient, relaxing costs several.</p>

<h2 id="the-test-and-what-i-wrote-down-first">The test, and what I wrote down first</h2>

<p>We used the eighteen molecules whose third derivatives we already had: <a href="https://thebreadishard.github.io/spectrum-atlas/molecule/A_01f3186607/">naphthalene</a>,
azulene, styrene, quinoxaline and fourteen others with 12 to 20 atoms. The cheap method is <a href="/glossary/#b3lyp">B3LYP</a>, the
method our whole corpus starts from. In the role of the better method we used <a href="/glossary/#wb97x">ωB97X</a>, a more careful DFT method.
Coupled cluster on eighteen molecules would have cost far more than this test is worth, and ωB97X is the same stand-in the network’s
earlier tests used.</p>

<p>For each molecule the laptop computed ωB97X’s vibrations twice: at B3LYP’s resting shape, and again after relaxing the molecule to
ωB97X’s own resting shape. The difference is the measured shift of every vibration. The formula gives the predicted shift. The
laptop needed just under a day for all eighteen.</p>

<p>Before the first molecule ran, I wrote down four lines (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/613d1381">the registration</a>),
counting only vibrations that move by at least 1 wavenumber:</p>

<ol>
  <li>With the real relaxed shape, at most 30 % of the shift may be left after the correction.</li>
  <li>With the one-step estimate, at most 35 %.</li>
  <li>No region of the spectrum may get worse.</li>
  <li>The correction must help in at least 16 of the 18 molecules.</li>
</ol>

<p>My prediction: all four hold.</p>

<h2 id="a-bump-on-the-way">A bump on the way</h2>

<p>Two molecules made trouble. Styrene and its cousin with an extra carbon–carbon triple bond both carry a small CH=CH₂ group on the ring.
At ωB97X’s resting shape they lie flat, but for one motion, the group twisting about the bond to the ring, flat turns out not to be the
bottom of a valley. It is the top of a very low hill: the molecule would rather twist a little. A motion that sits on top of a hill
does not vibrate at all, and the calculation returns an imaginary number for its frequency (41i and 43i cm⁻¹ in chemists’
notation). There is no shift to measure for it.</p>

<p>So before reading any result, I added a rule to the registration
(<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/ac7c0f44">the amendment</a>): for those two molecules, that one twisting
motion is left out, and every other vibration of all eighteen molecules counts. As a check, the answer is also reported without the two
molecules at all, so that the leaving-out cannot have decided anything. The alternative was to let the two molecules twist into their
real, slightly bent resting shape. Then most of the change between the two shapes would be one large swing along the softest motion,
which a first-order formula is not meant to handle, and we would be testing something else.</p>

<h2 id="the-answer">The answer</h2>

<p>All four lines hold (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/179669fd">the read</a>). Over 758 vibrations, the
typical shift (the root mean square) is 10.99 wavenumbers. After the correction, 2.41 is left: 22 % of the shift, where the line
allowed 30 %. The one-step estimate leaves exactly the same 2.41. The correction helps in all eighteen molecules. Without styrene and
its cousin the picture is the same: 11.16 before, 2.39 after.</p>

<table>
  <thead>
    <tr>
      <th>region of the spectrum</th>
      <th>vibrations</th>
      <th>typical shift</th>
      <th>left after the correction</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>below 1000 cm⁻¹</td>
      <td>332</td>
      <td>8.40</td>
      <td>2.19</td>
    </tr>
    <tr>
      <td>1000–1900 cm⁻¹</td>
      <td>289</td>
      <td>13.99</td>
      <td>3.10</td>
    </tr>
    <tr>
      <td>1900–2700 cm⁻¹</td>
      <td>6</td>
      <td>21.94</td>
      <td>1.12</td>
    </tr>
    <tr>
      <td>C–H stretches, above 2700 cm⁻¹</td>
      <td>131</td>
      <td>8.12</td>
      <td>0.36</td>
    </tr>
  </tbody>
</table>

<p>The 1900–2700 row holds only six vibrations, too few to judge by the third line’s own rule; they are shown for completeness. The
carbon–hydrogen stretches, the motions that started this story on benzene, almost disappear: 8.12 wavenumbers of shift, 0.36 left.</p>

<h2 id="what-i-got-wrong">What I got wrong</h2>

<p>I had predicted that the shift would be largest in the carbon–hydrogen stretches, because those bonds change length the most between
the two methods. It is not. The biggest shifts are in the middle of the spectrum, 1000 to 1900 wavenumbers, where the ring vibrations
live. The formula handles them too, just less completely (13.99 down to 3.10). The four lines held; the reason I gave for expecting it
did not.</p>

<h2 id="what-this-is-not">What this is not</h2>

<p>This is a test between two DFT methods, not against coupled cluster. It shows that the formula carries from one molecule to eighteen and
that the cheap one-step estimate is good enough. It does not show how well it does on the real anchors; that comes when the anchors’
own gradients are used.</p>

<p>In five molecules, matching each vibration at one shape to the same vibration at the other shape was uncertain: two vibrations had
partly mixed. The molecule where this was worst also has the largest left-over error, 4.69 wavenumbers. So part of what is left may be
a bookkeeping limit of the test rather than a failure of the formula.</p>

<p>And the formula is not yet in the product. It goes into the design now.</p>

<h2 id="what-this-means">What this means</h2>

<p>A corrected vibration is still the two things the September post described: the stiffness measured at the cheap shape, plus the geometry
term. What changed today is that the second part is no longer a benzene result. For eighteen molecules it removes about four fifths
of the shift, and it does that with one gradient at the better level and third derivatives the anharmonic step computes anyway.</p>

<p>The full numbers, the scripts and the lines written before the runs are in the project’s repository.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="probes" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">How many teachers?</title><link href="https://thebreadishard.github.io/2026/10/30/how-many-teachers/" rel="alternate" type="text/html" title="How many teachers?" /><published>2026-10-30T08:00:00+01:00</published><updated>2026-10-30T08:00:00+01:00</updated><id>https://thebreadishard.github.io/2026/10/30/how-many-teachers</id><content type="html" xml:base="https://thebreadishard.github.io/2026/10/30/how-many-teachers/"><![CDATA[<!-- Written 29–30 Oct 2026 overnight (TASKS 143; the user: 'Doe alles maar' for the night's list), published 30 Oct on the user's
     word. Correction of 1 Nov: the anchor count, from modules/05_support_predictor/out/ANCHORS_STATUS.json (eleven carried). Numbers trace to (CapstonePlan repo, plans/05_delta-probed-ir-pipeline):
     modules/05_support_predictor/out/anchor_count_2026-10-29/anchor_count_read.md (commit 2c9332b6): arm (c), body1_es: crossings of
       3 cm-1 at k = 100 for ring-ip and CH-stretch, CH-oop 167 (153–177), 'other' > 200; at k = 200: 1.71 / 1.76 / 2.73 / 3.59;
     modules/05_support_predictor/out/anchor_price_2026-10-28/anchor_price_read.md: C1 (a cover beats random draws) does not hold;
     modules/05_support_predictor/out/anchor_plan/count_n167.md (commit 65e84b70): reservoir 200 = fused 89, single 67, five 44;
       167 = fused 74, single 56, five 37; cheapest ≈ 3,503 CPX62 h, ≈ EUR 729, 10–18 atoms, 39 with proxy labels; drawn ≈ EUR 3,026.
     Read set 343 and reservoir 200: PreRegistration_2026-10-29_Anchor_Count.md. The bend's CH-oop factors of the proxy's shift
     (single 0.02, fused −0.45, five −0.90): PreRegistration_2026-10-27_Anchor_Price_Arm_C.md, as registered. -->

<p>The network in this project learns in two steps. First it learns from a large set of cheap quantum-chemistry calculations (DFT) how
the vibrations of a molecule shift between two cheap methods. Then it should learn the last step from a much better and much more
expensive method, coupled cluster. A coupled-cluster calculation of one whole molecule’s vibrations is what we call an <em>anchor</em>: a
teacher’s answer the network can check itself against. We have about ten. The question this post answers is how many we need, and
what they would cost.</p>

<h2 id="why-we-did-not-ask-the-ten-we-have">Why we did not ask the ten we have</h2>

<p>The honest way to find out would be to train on some anchors and test on others. With ten, that test rests on one or two molecules
at a time, and the answer would mostly be luck. So we answered the question on a stand-in task where we can afford hundreds of
teachers.</p>

<p>The stand-in works like this. On the DFT data, we bend the answers the way the real coupled-cluster answers differ from DFT on the
anchors we already have. The clearest pattern there is in the out-of-plane bending of the C–H bonds. Compared with the DFT
difference the network learned first, the coupled-cluster correction to these bands is close to zero on a single ring, about half
as large and of the opposite sign on fused rings like naphthalene, and almost as large and of the opposite sign when the molecule
has a five-membered ring. The bent answers carry that pattern. Then we give the trained network the bent answers of <em>k</em> molecules,
let it adjust, and measure its error on 343 molecules it never saw. We did this for <em>k</em> = 100, 120, 150 and 170 with three random
picks of molecules each, and for <em>k</em> = 200 with the whole pool of 200, each time with three versions of the network.</p>

<h2 id="the-answer-167-for-the-families-that-can-get-there">The answer: 167, for the families that can get there</h2>

<p>The target is the one the project has used throughout: an error of at most 3 cm⁻¹ per family of vibrations.</p>

<ul>
  <li>The in-plane ring motions and the C–H stretches get there with 100 teachers already.</li>
  <li>The C–H out-of-plane bends need <strong>167</strong> (between 153 and 177 within the spread of the runs).</li>
  <li>The floppy, low-frequency motions do not get there by 200: their error is 3.59 cm⁻¹ at 200 and still falling slowly.</li>
</ul>

<p>So 167 is the number for three of the four families. The fourth is the same family the network struggles with on DFT alone, and more
teachers of this kind are not the fix for it.</p>

<h2 id="a-surprise-about-which-molecules-to-pick">A surprise about which molecules to pick</h2>

<p>We expected that choosing the teachers carefully would help: make sure every kind of C–H arrangement (one hydrogen alone on a ring
edge, two side by side, three, four) appears often enough. It did not. A carefully covering selection did no better than random
picks. What does matter is the mix of ring types, because that is what the correction follows. So the plan keeps the mix of the
pool the experiment drew from: 89 fused, 67 single-ring and 44 five-ring molecules out of 200, which for 167 teachers means 74, 56
and 37.</p>

<h2 id="the-price-as-two-numbers">The price, as two numbers</h2>

<p>One anchor costs rented server time, and the cost grows steeply with the size of the molecule. With the price rule we measured on
the last wave of anchors:</p>

<ul>
  <li><strong>The cheapest 167 with that mix: about 3,500 server hours, about €730.</strong> These are small molecules, 10 to 18 atoms.</li>
  <li><strong>167 picked the way the experiment picked them: about €3,000.</strong> The experiment drew from a pool with larger molecules too.</li>
</ul>

<p>Both numbers are honest, and the truth for the plan lies between them. The experiment measured how many teachers are needed when
they look like its pool. Whether small teachers teach as well as a mixed set has not been measured, and the cheaper number assumes
they do.</p>

<p>Not in either number: molecules with a positive charge (they need a different coupled-cluster route we have not built yet), a cheap
extra step that corrects for the basis set, and the DFT calculations for the 128 chosen molecules that do not have them yet — those
are cheap and run on the laptop.</p>

<h2 id="what-happens-next">What happens next</h2>

<p>How the missing anchors will be made is decided in November. This post gives that decision its two inputs: how many (167, for
three of the four families) and what that costs (between about €730 and €3,000 of rented time).</p>

<p>The full numbers, the scripts and the lines written before the runs are in the project’s repository.</p>

<h2 id="correction-1-november-2026">Correction, 1 November 2026</h2>

<p>This post says we have “about ten” anchors and calls them “the ten we have”. The count is eleven, the same eleven as in
<a href="/2026/10/22/rehearsing-the-last-step/">Rehearsing the last step</a>: benzene, naphthalene, pyridine, fluorobenzene,
benzonitrile and anthracene since 6–7 October, and quinoxaline, quinoline, isoquinoline, azulene and acenaphthylene since 20 October.
(The post of 20 October called quinoxaline “one of seven”: that was true that morning, before the other four came in the same
day.) The argument does not change: with eleven, a test that trains on some anchors and checks on the rest still rests on one or two
molecules at a time.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="progress" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Cleaner answers</title><link href="https://thebreadishard.github.io/2026/10/28/cleaner-answers/" rel="alternate" type="text/html" title="Cleaner answers" /><published>2026-10-28T18:45:43+01:00</published><updated>2026-10-28T18:45:43+01:00</updated><id>https://thebreadishard.github.io/2026/10/28/cleaner-answers</id><content type="html" xml:base="https://thebreadishard.github.io/2026/10/28/cleaner-answers/"><![CDATA[<!-- Written 28 Oct 2026 (the user: 'Ja, schrijf het concept over v1.3 nu'); published 28 Oct on the user's word ('Publiceer'; TASKS 143).
     Numbers trace to (CapstonePlan repo, plans/05_delta-probed-ir-pipeline), commit 831afd13:
     modules/05_support_predictor/out/E7_rungC_chain35_read.md (C35R, 28 Oct 04:28): both versions read on the same 862 analytic targets;
       (a) chain 35 / chain 34 read again: ring-ip 1.66 / 1.82, CH-stretch 1.15 / 1.25, CH-oop 1.99 / 2.36 (beyond 2 SE), other 3.35 / 3.35,
       coupling ratio 0.172 / 0.192 (beyond 2 SE); (b): 2.12 / 2.35, 2.50 / 2.55, 2.70 / 3.33, 3.89 / 4.66, 0.263 / 0.289 (beyond 2 SE);
       the registered 'other' line (a fall of at least 0.3 on (a)) reads 'labels not it'; promotion by decision 57 (worse on none of five,
       lower on 9 of 10);
     modules/05_support_predictor/out/E7_rungC_chain35_analytic.log / .md: analytic Hessians substituted for 862 molecules; n 750, three
       seeds; wall 14379 s;
     modules/05_support_predictor/out/E7_rungC_chain35_analytic_eval_frozenb_seed0.md: (a) 10 molecules, (b) 39;
     obstacle ledger 28 Oct 04:4x (labels lanes: 805 ok, 2 failed);
     the 3 Oct promise: _posts/2026-10-03-breadth-first.md (finite-difference noise CH-stretch 0.2, ring and skeletal 1.5, CH-oop 3.3).
     Version list: modules/05_support_predictor/MODELS.md (v1.1 = chain 34, v1.2 = chain 36 superseded, v1.3 = chain 35 carried).
     (b) of chain 34 here differs from the 26 Oct post (2.57 / 3.81 / 2.62 / 6.03): there it was read against the finite-difference
     answers of (b); C35R reads both versions against the analytic ones. -->

<p>Our network learns from examples: for each molecule in its training set, it sees the answer it should give and adjusts itself to come
closer. If those answers are noisy, the network learns the noise along with the physics. This week we replaced the answers, and the
network got better. The vibrations we hoped would improve did not.</p>

<h2 id="where-the-noise-came-from">Where the noise came from</h2>

<p>The answers are <a href="/glossary/#hessian">Hessians</a>: tables of how stiff a molecule is against every small push of its atoms. They hold
everything the network needs to know about where the vibrations of a molecule sit. Until now we computed most of them by
<a href="/glossary/#finite-differences">finite differences</a>: push the atoms a tiny step, compute the forces, push back, compute again, and
subtract. It works, but every subtraction of two nearly equal numbers leaves a little noise behind.</p>

<p>On <a href="/2026/10/03/breadth-first/">3 October</a> we measured that noise per kind of vibration. It was small for the C–H stretches (0.2
wavenumbers), larger for the ring and skeletal motions (1.5), and largest for the C–H out-of-plane bends (3.3), which is exactly the size
of the error we were aiming for. We wrote then that the honest next step was cleaner answers, computed
<a href="/glossary/#analytic-derivative">analytically</a>: from exact formulas for the derivatives instead of small steps.</p>

<p>A rented server spent the past weeks on that, and the training set now has analytic Hessians for 862 molecules. On Tuesday night the laptop trained the
network again on them, with nothing else changed: same design, same recipe, same 750 training molecules, three trainings with different
random starts. Four hours later we had version 1.3.</p>

<h2 id="how-a-new-version-earns-its-place">How a new version earns its place</h2>

<p>We keep a list of network versions, and only one of them is the one the rest of the project uses. A new version replaces it only if it is
worse on none of five measures, on both groups of molecules the network never sees during training
(<a href="/glossary/#hold-out-set">hold-out sets</a>). Ten of those molecules resemble the training set. The other 39 are built on ring frameworks
it has never met.</p>

<p>Four of the measures are the typical frequency error for each kind of vibration. The fifth asks how well the network gets the way the
vibrations of neighbouring rings mix: it is the share of that mixing error the network leaves over, so 0.17 means it removes 83 per cent.</p>

<p>To make the comparison fair, the old version was scored again against the same cleaner answers as the new one. Errors in wavenumbers,
lower is better:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>C–H stretch</th>
      <th>out-of-plane bend</th>
      <th>ring, in plane</th>
      <th>other</th>
      <th>ring mixing</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>familiar frameworks, old (v1.1)</td>
      <td>1.25</td>
      <td>2.36</td>
      <td>1.82</td>
      <td>3.35</td>
      <td>0.192</td>
    </tr>
    <tr>
      <td>familiar frameworks, <strong>new (v1.3)</strong></td>
      <td><strong>1.15</strong></td>
      <td><strong>1.99</strong></td>
      <td><strong>1.66</strong></td>
      <td>3.35</td>
      <td><strong>0.172</strong></td>
    </tr>
    <tr>
      <td>unfamiliar frameworks, old (v1.1)</td>
      <td>2.55</td>
      <td>3.33</td>
      <td>2.35</td>
      <td>4.66</td>
      <td>0.289</td>
    </tr>
    <tr>
      <td>unfamiliar frameworks, <strong>new (v1.3)</strong></td>
      <td><strong>2.50</strong></td>
      <td><strong>2.70</strong></td>
      <td><strong>2.12</strong></td>
      <td><strong>3.89</strong></td>
      <td><strong>0.263</strong></td>
    </tr>
  </tbody>
</table>

<p>Worse on none, lower on nine of ten. Version 1.3 is now the network the project uses
(<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/831afd13">the read</a>). (There was a version 1.2 in between: a different
input to the network, which was tested and rejected.)</p>

<p>Not every one of those nine improvements is solid. Three trainings with different random starts do not end in exactly the same place,
and most of the differences in the table are within that spread. Three are clearly beyond it: the out-of-plane bends on the familiar
frameworks, and the ring mixing on both groups. The rest point the same way, but on their own they would not convince us.</p>

<h2 id="what-did-not-move">What did not move</h2>

<p>The out-of-plane bends improved most, which is what the noise measurement of 3 October predicted: their answers were the noisiest, so
cleaning them helped most.</p>

<p>The column that did not move is “other”: the slow, floppy motions of the whole molecule, and the motions of side groups attached to the
rings. On the familiar frameworks it stayed at 3.35 to two decimals. Before the training we wrote down what we would conclude: if the
cleaner answers lowered that number by at least 0.3, the noise had been the problem; if not, it was not. It did not, so the noise was not
the problem. On the unfamiliar frameworks the same column did fall, from 4.66 to 3.89, but that is within the spread of the three trainings.</p>

<p>That is a useful negative. On 3 October we had already guessed that this family’s error was a learning gap rather than a noise floor, and
now we know it. Whatever holds the floppy motions at 3.35, it is in what the network learns or in what it is shown, not in the quality of
the answers. They stay the open family on our list.</p>

<h2 id="what-this-means">What this means</h2>

<p>Cleaner answers made a better network, at the cost of a few weeks of rented computing and four hours on a laptop, and the improvement landed
where the noise measurement said it would. The floppy motions are now the clearest remaining problem, and we know one thing that is not
causing it.</p>

<p>The full numbers, the scripts and the lines written before the training are in the project’s repository.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="progress" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">The software under the science</title><link href="https://thebreadishard.github.io/2026/10/27/the-software-under-the-science/" rel="alternate" type="text/html" title="The software under the science" /><published>2026-10-27T03:46:36+01:00</published><updated>2026-10-27T03:46:36+01:00</updated><id>https://thebreadishard.github.io/2026/10/27/the-software-under-the-science</id><content type="html" xml:base="https://thebreadishard.github.io/2026/10/27/the-software-under-the-science/"><![CDATA[<!-- Written 27 Oct 2026 on the user's request ('een blog over de softwareontwikkelingskant van ons project ... een korte inleiding en dan
     een lijst met de PR's, met een of twee zinnen per PR voor de leek'); the AI paragraph on the user's word ('Absoluut: dat de code met
     een AI-assistent is geschreven en getest'); published 27 Oct on the user's word ('Publiceer maar'), the four unsubmitted PRs kept
     at the end on purpose (the user: they show that you do not know in advance whether a contribution will succeed); to be extended later.
     Same day, on the user's question ('Werk je de blog bij?'): the four submitted as pyscf/pyscf#3545-#3548, the section renamed and
     corrected (it said all four are about cations; the empty block and the CCSD(T) tables are not).
     Sources: the pull requests and issues themselves (gh search prs/issues --author thebreadishard, 27 Oct 2026, 03:2x); the merge dates
     are GitHub's. The four unsubmitted PRs: CapstonePlan, plans/05_delta-probed-ir-pipeline/GoalGathering/notes/PR_Drafts_2026-10-27_pyscf.md
     (benzene/cc-pVTZ 28 GB per array; H2O+/aug-cc-pVTZ peak 4.09 -> 3.38 GB; benzene+ 22 GB per array, acenaphthylene+ 830 GB; the
     benzene+/cc-pVDZ gradient at a peak of 6.1 GB). Six days lost to the CCSD lambda: CapstonePlan CLAUDE.md, 'the lambda incident of
     29 September 2026'. #3470's 16x / 14x: its title. -->

<p>This project stands on software other people wrote and give away for free. Almost all of our quantum chemistry runs on
<a href="/glossary/#pyscf">PySCF</a>, a Python library kept up by a group of researchers and volunteers. Next to it we use a handful of smaller
packages: one for anharmonic corrections, one for geometry optimisation, one for a fast approximate method.</p>

<p>Software that many people use still has corners that nobody has walked into yet. Our calculations are large and unusual enough that we
walked into several. Each time we had two choices: work around the problem in our own code, or fix it at the source so that the next
person does not hit it. Where we could, we fixed it at the source. Each fix goes to the maintainers as a <em>pull request</em>: a proposed
change, usually with a test that fails without the change and passes with it. The maintainers read it, ask questions, and decide whether to
take it.</p>

<p>One thing should be said plainly. The code in these pull requests was written and tested by an AI coding assistant, Claude Code,
which works on this project with me. I decide what we take on and hand in the pull requests; tracking down the cause, writing the
change and proving it with tests is its work.</p>

<p>So far that has been nineteen pull requests to six projects. Eleven have been accepted, six are waiting for review (four of them
submitted on 27 October), and two we withdrew because a better fix came along. Below is each of them, in a sentence or two.</p>

<h2 id="pyscf">PySCF</h2>

<p><a href="https://github.com/pyscf/pyscf">PySCF</a> is a free Python library for quantum chemistry, written and kept up by researchers and
volunteers; almost every calculation in this project runs on it. Its documentation is at <a href="https://pyscf.org">pyscf.org</a>.</p>

<ul>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3469">#3469</a>, accepted 6 October.</strong> When you ask PySCF for the
<a href="/glossary/#analytic-derivative">gradient</a> of a <a href="/glossary/#ccsd-t">CCSD(T)</a> energy without first computing one particular
intermediate, it quietly used the intermediate of the simpler CCSD method. The result looked fine but was the slope of a different
energy. We lost six days of computing before we noticed; now PySCF computes the right intermediate by itself.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3470">#3470</a>, accepted 6 October.</strong> Two of the slowest steps of the CCSD(T) gradient were
written in Python. We rewrote them in C, which made them 16 and 14 times faster on benzene.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3477">#3477</a>, accepted 12 October.</strong> On its way to the gradient, the calculation builds the
electron density, and from that density the <a href="/glossary/#dipole-moment">dipole moment</a> follows almost for free. PySCF threw the
density away. PySCF now keeps it, so every gradient also gives the dipole moment, which decides how bright an infrared line is.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3507">#3507</a>, accepted 16 October.</strong> A computed <a href="/glossary/#hessian">Hessian</a> should be
symmetric, but small numerical errors make its two halves differ slightly. PySCF’s frequency routine read only one half, so the
answer depended on which half happened to carry the error. It now averages the two halves first.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3531">#3531</a>, accepted 22 October.</strong> Inside a Hessian calculation a large set of equations is
solved step by step (<a href="/glossary/#cphf">CPHF</a>). That solver never wrote its progress to the log, so a run that was stuck looked exactly
like a run that was working. It now reports every step. We had first reported the problem as
<a href="https://github.com/pyscf/pyscf/issues/3530">#3530</a>.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3508">#3508</a>, withdrawn.</strong> The same CPHF solver sometimes stops before it has reached the
accuracy it was asked for, a problem another user had reported in <a href="https://github.com/pyscf/pyscf/issues/2702">#2702</a>. We
added measurements to that report and proposed a fix, then withdrew it in favour of a fix by another contributor that repairs the
solver itself, for every part of PySCF that uses it.</li>
</ul>

<h2 id="pyscf-forge-pyscfs-add-ons">PySCF-forge (PySCF’s add-ons)</h2>

<p><a href="https://github.com/pyscf/pyscf-forge">PySCF-forge</a> holds new methods for PySCF until they are ready to move into PySCF itself.</p>

<ul>
  <li><strong><a href="https://github.com/pyscf/pyscf-forge/pull/212">#212</a>, accepted 30 September.</strong> The add-on for
<a href="/glossary/#local-correlation">local correlation</a> methods stopped working with the newest versions of PySCF and of NumPy, the
number-crunching library underneath. Two small changes made it work again.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf-forge/pull/213">#213</a>, accepted 9 October.</strong> When you install the add-ons, the build looks up
where PySCF lives. It could ask a different Python than the one doing the installing, and then build against the wrong copy of
PySCF. It now asks the right one.</li>
</ul>

<h2 id="pyscf-properties">PySCF properties</h2>

<p><a href="https://github.com/pyscf/properties">PySCF properties</a> computes how molecules and crystals respond to electric and magnetic
fields, among them the brightness of infrared lines.</p>

<ul>
  <li><strong><a href="https://github.com/pyscf/properties/pull/16">#16</a>, waiting for review since 11 October.</strong> The infrared routine crashed when you
handed it your own orbitals instead of letting it compute them; only the default route worked.</li>
  <li><strong><a href="https://github.com/pyscf/properties/pull/17">#17</a>, waiting for review since 11 October.</strong> The existing tests of the infrared
intensities compare with a commercial program. This PR adds a test that needs no other program: it checks the routine against
<a href="/glossary/#finite-differences">finite differences</a>, small steps of the atoms with the dipole moment computed at each step.</li>
</ul>

<h2 id="pyvpt2-anharmonic-corrections">pyVPT2 (anharmonic corrections)</h2>

<p><a href="https://github.com/philipmnel/pyvpt2">pyVPT2</a> computes the <a href="/glossary/#anharmonicity">anharmonic</a> corrections to vibrational
frequencies by <a href="/glossary/#vpt2">second-order perturbation theory</a>, on top of the quantum-chemistry package <a href="/glossary/#psi4">Psi4</a>
(<a href="https://philipmnel.github.io/pyvpt2/">documentation</a>).</p>

<ul>
  <li><strong><a href="https://github.com/philipmnel/pyvpt2/pull/59">#59</a>, accepted 19 October.</strong> pyVPT2 has a built-in check that is meant to warn
when its numbers are noisy. The check compared two numbers that were equal by construction, so it could never fire. It now compares
two independent routes to the same number. This PR replaced our first attempt, <a href="https://github.com/philipmnel/pyvpt2/pull/58">#58</a>,
which only reported the problem; the report itself is <a href="https://github.com/philipmnel/pyvpt2/issues/57">#57</a>.</li>
  <li><strong><a href="https://github.com/philipmnel/pyvpt2/pull/61">#61</a>, accepted 19 October.</strong> When two vibrations share almost the same energy they
mix (a <a href="/glossary/#fermi-resonance">Fermi resonance</a>), and such groups have to be solved together. The solver could crash, or treat
one connected group as two separate ones. It now merges them. Report: <a href="https://github.com/philipmnel/pyvpt2/issues/60">#60</a>.</li>
</ul>

<h2 id="optking-geometry-optimisation">optking (geometry optimisation)</h2>

<p><a href="https://github.com/psi-rking/optking">optking</a> searches for the shape in which a molecule is most stable, a
<a href="/glossary/#geometry-optimisation">geometry optimisation</a>. It started as part of Psi4 and is now a separate Python program.</p>

<ul>
  <li><strong><a href="https://github.com/psi-rking/optking/pull/116">#116</a>, accepted 13 October.</strong> An optimisation could hang forever, using a full
processor core and printing nothing, when an angle close to zero was mistaken for a straight line. optking now recognises that case
and starts again cleanly. Report: <a href="https://github.com/psi-rking/optking/issues/115">#115</a>.</li>
</ul>

<h2 id="dxtb-a-fast-approximate-method">dxtb (a fast approximate method)</h2>

<p><a href="https://github.com/grimme-lab/dxtb">dxtb</a>, from Stefan Grimme’s group in Bonn, is a fast approximate quantum-chemistry method
(extended tight binding) written in PyTorch, so that it can be combined with machine learning
(<a href="https://dxtb.readthedocs.io">documentation</a>).</p>

<ul>
  <li><strong><a href="https://github.com/grimme-lab/dxtb/pull/278">#278</a>, accepted 23 October.</strong> The forces on the atoms came out wrong when the
calculator reused results it had stored for an earlier geometry. It now clears those stored results when it gets a new geometry.
Report: <a href="https://github.com/grimme-lab/dxtb/issues/277">#277</a>.</li>
</ul>

<h2 id="submitted-to-pyscf-waiting-for-review">Submitted to PySCF, waiting for review</h2>

<p>We submitted these four on the morning of 27 October, after reworking all four to follow the style rules that PySCF publishes for
contributors. None of them has been reviewed yet, so they may still change or be turned down. The first two fix problems that can
hit any molecule. The other two are about <em><a href="/glossary/#cation">cations</a></em>, molecules that have lost one electron. Many PAHs in space
are cations, and the most accurate method we use, CCSD(T), ran out of memory on every cation beyond the smallest.</p>

<ul>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3545">#3545</a>, empty block.</strong> The gradient code works through the <a href="/glossary/#basis-set">basis functions</a> in blocks. When an atom’s first group of
functions was wider than one block, the first block came out empty and the calculation crashed. We met it on benzene with a large
basis set.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3546">#3546</a>, CCSD(T) tables on disk.</strong> PySCF has a route that is meant to keep the biggest tables on disk instead of in memory. For the CCSD(T)
gradient it still built three of them in memory, 28 GB each for benzene with a large basis set. They now go to disk.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3547">#3547</a>, the same for cations.</strong> The cation version of the same calculation had the same problem. On a small test molecule the memory peak
drops from 4.1 to 3.4 GB.</li>
  <li><strong><a href="https://github.com/pyscf/pyscf/pull/3548">#3548</a>, CCSD(T) for cations in pieces.</strong> The cation CCSD(T) gradient built several tables of about 22 GB each for the benzene cation, and
830 GB each for a three-ring cation. The work is now done in blocks small enough to fit. The whole gradient of the benzene cation
ran on a laptop with a memory peak of 6.1 GB.</li>
</ul>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="software" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">What a scale factor leaves</title><link href="https://thebreadishard.github.io/2026/10/26/what-a-scale-factor-leaves/" rel="alternate" type="text/html" title="What a scale factor leaves" /><published>2026-10-26T06:07:26+01:00</published><updated>2026-10-26T06:07:26+01:00</updated><id>https://thebreadishard.github.io/2026/10/26/what-a-scale-factor-leaves</id><content type="html" xml:base="https://thebreadishard.github.io/2026/10/26/what-a-scale-factor-leaves/"><![CDATA[<!-- Written 25-26 Oct 2026 (the user: 'Overweeg een blog voor te bereiden over de resultaten van 111 en/of 112'); published 26 Oct on the
     user's word ('Publiceer'; TASKS 128).
     Numbers trace to (CapstonePlan repo, plans/05_delta-probed-ir-pipeline):
     GoalGathering/notes/PreRegistration_2026-10-25_Scale_Factor_Residuals.md (commit 08c594fe, written before any number);
     GoalGathering/notes/Results_2026-10-25_Scale_Factor_Residuals.md;
     modules/05_support_predictor/out/scale_factor_baseline_2026-10-25.md (commit d4b48582): pool 1,073 molecules; (a) 10 molecules, (b) 39;
       (a) network 1.25 / 2.36 / 1.82 / 3.35, three-region factor 2.02 / 9.21 / 15.47 / 8.58, per-family factor 2.04 / 6.61 / 15.31 / 8.46;
       (b) network 2.57 / 3.81 / 2.62 / 6.03, three-region factor 3.86 / 9.96 / 14.19 / 14.63 (CH-stretch / CH-oop / ring-ip / other);
     probes/results_m1/scale_factor_baseline_second_route_2026-10-25.log (commit b452ef0b): ring-ip mean shift +11.61, sd 15.97 on (a);
       C-H stretch +43.59, sd 1.47;
     modules/05_support_predictor/out/fundamentals_vs_experiment_2026-10-25.md (commit e08fddc7): benzene CC harmonic - experimental
       harmonic ring-ip +2.1 / 5.5, CH-stretch -38.5; Mai scaled - fundamental rms 22.5 (benzene), 15.4 (naphthalene), benzene ring-ip
       mean -24.6; CC harmonic - fundamental CH-stretch +90.0 (benzene), +95.7 (naphthalene);
     probes/results_m1/cc_ch_bond_force_2026-10-25.log: C-H 1.0871 A at the B3LYP geometry, CC force inwards, first-order dr -0.0040 A. -->

<p>When you compute the vibrations of a molecule with a standard quantum-chemistry method, the frequencies come out a few per cent too high.
The usual repair is simple: multiply every computed frequency by one number, a <em>scale factor</em>, slightly below one. The NASA Ames PAH
database uses three such numbers, one for each region of the spectrum (0.956, 0.952 and 0.960). The largest infrared library of PAHs to
date, 144,111 spectra published this year by Mai, Lu and Wang, uses exactly those three numbers.</p>

<p>This project tries to do without the scale factor. Instead of stretching the whole spectrum by a fixed amount, a neural network learns how
each vibration of each molecule should move. That only makes sense if a scale factor actually leaves something behind. This week we
measured how much, and where.</p>

<p>Before we looked at any number, we wrote down what we would compute and what would count as a pass. That note is in the repository with a
date and a commit (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/08c594fe">the registration</a>), so the results below could not be bent to fit afterwards.</p>

<h2 id="the-practice-task">The practice task</h2>

<p>Our network learns in two stages. In the first stage it practises on a stand-in: the difference between two cheaper methods (called
B3LYP and ωB97X), which we can compute for hundreds of molecules. The real target, <a href="/glossary/#coupled-cluster">coupled cluster</a>, is too
expensive for that. I wrote about the two stages <a href="/2026/10/22/rehearsing-the-last-step/">last week</a>.</p>

<p>On the stand-in, the question is easy to make fair. We fitted the best scale factors we could to 1,073 molecules — more than the network
ever sees in one training — and asked how far each vibration still ends up from where the second method puts it. Then we asked the same of
the network, on molecules neither had seen: ten held back from training, and 39 built on ring frameworks the network had never met.</p>

<p>We split the vibrations into four kinds:</p>

<ul>
  <li><strong>C–H stretches</strong>: a hydrogen atom bouncing in and out along its bond;</li>
  <li><strong>C–H out-of-plane bends</strong>: the hydrogens flapping up and down through the plane of the rings;</li>
  <li><strong>ring vibrations in the plane</strong>: the carbon framework itself stretching and bending;</li>
  <li><strong>everything else</strong>: mostly slow, floppy motions of the whole molecule.</li>
</ul>

<p>The typical error left over (a root-mean-square average over all vibrations of that kind), in wavenumbers, the unit spectroscopists use:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>C–H stretch</th>
      <th>out-of-plane bend</th>
      <th>ring, in plane</th>
      <th>other</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>best three-region scale factor</td>
      <td>2.0</td>
      <td>9.2</td>
      <td>15.5</td>
      <td>8.6</td>
    </tr>
    <tr>
      <td>best scale factor <em>per kind of vibration</em></td>
      <td>2.0</td>
      <td>6.6</td>
      <td>15.3</td>
      <td>8.5</td>
    </tr>
    <tr>
      <td>our network</td>
      <td><strong>1.3</strong></td>
      <td><strong>2.4</strong></td>
      <td><strong>1.8</strong></td>
      <td><strong>3.4</strong></td>
    </tr>
  </tbody>
</table>

<p>On the 39 molecules with unfamiliar frameworks the network’s errors are larger (2.6, 3.8, 2.6 and 6.0), but it still removes between a
third and more than four fifths (33 to 82 per cent) of what the scale factor leaves (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/d4b48582">the read</a>).</p>

<p>The ring vibrations are the striking column. A scale factor leaves about 15 wavenumbers there, and even a separate factor for every kind of
vibration does not help. The reason shows up when you look at how the ring vibrations actually move between the two methods: on average they
go up by about 12 wavenumbers, but from one vibration to the next that varies by 16. A single number cannot follow that. The network can,
because it looks at each vibration and its surroundings in the molecule.</p>

<p>For the C–H stretches it is the other way round. They all move by almost the same amount (about 44, give or take 1.5), so a scale factor
does nearly as well as the network. That is no surprise: it is the case scale factors were made for.</p>

<h2 id="against-experiment">Against experiment</h2>

<p>The stand-in is a rehearsal. The real test is measured spectra, so we took the two molecules for which we have both a coupled-cluster
calculation and good measurements of every vibration: benzene and naphthalene. Three things came out.</p>

<p><strong>Our reference calculation is good where it should be.</strong> For benzene, spectroscopists have worked back from the measured spectrum to the
frequencies the vibrations would have if the bonds were perfect springs. Against those, our coupled-cluster calculation is typically 5.5
wavenumbers off (root mean square) for the ring vibrations. The C–H stretches, however, come out 38 too low. The likely cause: we compute the
coupled-cluster vibrations at the shape the cheaper method gives the molecule, and its C–H bonds are slightly
longer than coupled cluster would make them: the forces in our own calculation pull every hydrogen about 0.004 ångström closer to its carbon (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/70e9010b">the check</a>).
A longer bond vibrates more slowly. We saw the same offset in an earlier check, so it looks systematic, and the final pipeline will have to correct for it.</p>

<p><strong>Scaled spectra are decent, with offsets.</strong> Mai, Lu and Wang’s scaled spectra land typically 22 wavenumbers from the measured bands of benzene and
15 from those of naphthalene. For benzene the ring vibrations are on average about 25 too low: an offset of one kind of vibration,
the kind a single number cannot remove.</p>

<p><strong>Real bonds are not perfect springs.</strong> Our coupled-cluster frequencies are <em>further</em> from the measured bands than the scaled ones: the C–H
stretches by about 90 wavenumbers. That is not an error in the calculation. Real bonds get softer the more they stretch (anharmonicity), so
real vibrations sit lower than perfect-spring ones. A scale factor quietly folds this effect into its one number, together with everything
else. We want to keep the pieces apart, so we add it as a separate step, which we are building alongside the network (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/e08fddc7">the comparison</a>).</p>

<h2 id="what-this-means">What this means</h2>

<p>On the practice task the network does clearly better than any scale factor, in every kind of vibration, and most of all for the ring
vibrations, which carry much of the structure of a PAH spectrum. Against experiment the picture is more modest, and more useful: a
coupled-cluster correction alone does not beat a scale factor; it needs its companion step for anharmonicity, and a fix for the C–H
offset that comes from the molecule’s shape.</p>

<p>Two caveats. The first part is the stand-in, not experiment. And the second part rests on two molecules; it shows where the differences are,
not how large they will be across all PAHs.</p>

<p>The full numbers, the scripts and the note written beforehand are in the project’s repository.</p>

<h2 id="correction-1-november-2026">Correction, 1 November 2026</h2>

<p>The second paragraph says the NASA Ames PAH database uses the scale factors 0.956, 0.952 and 0.960. That was stated too firmly.
Those three numbers are the factors Mai, Lu and Wang give for the database, citing its 2018 paper: 0.960 above 2500 cm⁻¹, 0.952
between 1111 and 2500, and 0.956 below 1111. They are the numbers their library uses. Nothing in this post’s results depends on
them: the practice-task table fits its own best factors, and the comparison with experiment uses their scaled spectra as they
published them. The database file itself, as served for download, stores
other factors: 0.9597, 0.9691 and 0.9794, as this notebook found when it read the file on 11 September
(<a href="/2026/09/11/who-are-we-up-against/">Who are we up against?</a>). Why the published factors and the stored ones differ we
have not settled.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="progress" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Rehearsing the last step</title><link href="https://thebreadishard.github.io/2026/10/22/rehearsing-the-last-step/" rel="alternate" type="text/html" title="Rehearsing the last step" /><published>2026-10-22T06:44:01+02:00</published><updated>2026-10-22T06:44:01+02:00</updated><id>https://thebreadishard.github.io/2026/10/22/rehearsing-the-last-step</id><content type="html" xml:base="https://thebreadishard.github.io/2026/10/22/rehearsing-the-last-step/"><![CDATA[<!-- Written 21-22 Oct 2026 (the user: 'later vandaag of morgen ook een blog'; rewritten 22 Oct morning on their request: simpler,
     no skipped steps; 'What this means' answers its own question); published 22 Oct on the user's word ('Publiceer maar'); the source
     links added the same morning after the plan repo was pushed ('Push ook maar').
     Numbers trace to (CapstonePlan repo, plans/05_delta-probed-ir-pipeline):
     GoalGathering/notes/PreRegistration_2026-10-12_Anchor_Set_Three.md, Outcome: the final read (20 Oct 23:58; R-A 14.06 -> 13.69, R-B 0 of 3)
       and the whole-head repeat (21 Oct 09:22, commit 3c088dd1; amendment 2801b401): R-A 7.24 -> 6.25 (-13.8 %), R-B 3 of 3;
     GoalGathering/notes/Diagnosis_2026-10-21_Set_Three_Read.md (commit 409a58c6): W06 7.76 / E18 0.80 on the training anchors; E01 12.29 ->
       E16 6.54 on held-out anchors, 3 repeats;
     GoalGathering/notes/Design_2026-10-21_Few_Anchor_Proxy_Check.md (registered, commit bf408823; read 22 Oct 02:1x, commits 62474707 and
       07818876, 'Outcome'): arm (a) CH-oop lambda 1 9.05 / 8.09 / 9.90, whole head 6.55 / 5.08 / 4.04 / 3.81 / 3.17 / 2.82 (k 5 / 10 /
       20 / 50 / 100 / 200), lambda 0 5.90 at k 5, scratch
       7.33 / 5.38 / 4.76 (k 50) / 2.79 (k 5 / 20 / 200); arm (b) base as is 2.66; read sets 343 and 27 molecules. -->

<p>The project tries to predict the infrared spectra of large carbon molecules accurately without paying for the most expensive
calculations. The plan has two parts. A cheap quantum-chemistry method gives every molecule’s vibrations, but with a systematic error.
A neural network learns to correct that error.</p>

<p>The correction we really want points towards <a href="/glossary/#coupled-cluster">coupled cluster</a>, the reference method. But one coupled-cluster
calculation of the vibrations costs days for a small molecule, and it is impossible for the large ones we care about. So we cannot show
the network many examples of the real correction. We do it in two stages:</p>

<ol>
  <li><strong>Practice on a stand-in.</strong> The network first learns a different correction that is cheap to compute for hundreds of molecules: the
difference between two cheaper methods. From that it learns what a correction looks like in general and how it changes with the size
of a molecule.</li>
  <li><strong>Adjust with a few real examples.</strong> Then we show it a handful of molecules where we do have coupled cluster, our <em>anchors</em>, and let
it adjust.</li>
</ol>

<p>This post is about stage 2, which did not work at first.</p>

<h2 id="how-we-let-the-network-adjust">How we let the network adjust</h2>

<p>A neural network is a stack of layers. The first layers turn a molecule into a description the network can work with; the last few
layers, which I call the <em>head</em>, turn that description into the correction. When a network adjusts to new examples, you choose which
layers may change.</p>

<p>We let only the very last layer change, and we added a brake: a penalty that keeps that layer close to what it learned from the
stand-in. The idea was caution. With only eleven anchors, a network that changes too much can learn the eleven by heart and get worse
on everything else.</p>

<h2 id="eleven-anchors-a-flat-line">Eleven anchors, a flat line</h2>

<p>The registered test (written down before we looked) asked: do more anchors help? We looked at the out-of-plane C–H bending vibrations,
where the hydrogen atoms move up and down out of the plane of the rings. These are the hardest for the network and important in the
spectrum.</p>

<p>The answer was no. On the six original anchors the error went from 14.06 to 13.69 cm⁻¹ when five new anchors joined, which is within
the noise (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/15540e0c">the read</a>). The predicted brightness of the lines was also worse than not correcting at all, on all three anchors where we could check it.</p>

<h2 id="a-night-of-asking-why">A night of asking why</h2>

<p>“The idea does not work” was one possible reading. But there are other possible causes: too few anchors, wrong settings, the wrong
layers allowed to change, the network’s design. Overnight I tested more than twenty of them, one at a time, on the data we had.</p>

<p>The test that decided it is an unusual one. Normally you test a network on molecules it has <em>not</em> seen, because the question is whether
it can predict new ones. This time I did the opposite on purpose: I let the network adjust on all eleven anchors and then tested it on
<em>those same eleven</em>.</p>

<p>That is like asking a student to answer the exact questions they just studied, with the answers in front of them. A student who still
gets them wrong does not need more practice questions: something stops them from taking the answers in. The same holds for the network.
If it cannot reproduce the anchors it was just shown, more anchors will not help; the way it is allowed to learn is too narrow.</p>

<p>That is what happened. With only the last layer allowed to change, the network stayed 7.76 cm⁻¹ off on the very anchors it had been
shown. With the whole head allowed to change, it reproduced them to within 0.80 cm⁻¹. It <em>could</em> take the answers in; we had not let
it. Tested properly again, on anchors it had not seen, the error halved: from 12.29 to 6.54 cm⁻¹, averaged over three repeats (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/409a58c6">the night’s report</a>).</p>

<p>A second finding explains why the brake hurt. For the out-of-plane vibrations, the coupled-cluster correction does not point the same way
as the stand-in correction; it partly points the opposite way. A brake that holds the network near the stand-in holds it near the wrong
answer.</p>

<h2 id="repeating-the-test-honestly">Repeating the test, honestly</h2>

<p>The next morning we ran the registered test again, this time with the whole head free and the predictions written down first. Both came
out as predicted. The out-of-plane error now falls with every added anchor, from 7.24 to 6.25 cm⁻¹ (14 % lower; <a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/3c088dd1">the read</a>), and the line
brightness beats “no correction” on all three anchors where we can check it.</p>

<p>That is encouraging, but it is not proof. I chose the whole-head approach <em>by looking at these same eleven anchors</em>. It is like adjusting
a recipe by tasting one pot of soup and then judging the recipe by tasting the same pot again. To judge the recipe you need other pots.</p>

<h2 id="a-rehearsal-with-hundreds-of-molecules">A rehearsal with hundreds of molecules</h2>

<p>We will not have more coupled-cluster anchors before next summer. So we rehearsed stage 2 in a setting where we do have hundreds of
correct answers: the cheaper methods. We built two rehearsals.</p>

<ul>
  <li><strong>A different correction.</strong> We taught a network a deliberately wrong correction, bent family by family the way coupled cluster
differs from the stand-in. Then we gave it <em>k</em> molecules with the right answer and let it adjust. We judged it on 343 other molecules
it never adjusted on. This copies our real situation: a network that learned something close but wrong has to switch with few
examples.</li>
  <li><strong>A new kind of molecule.</strong> We taught a network the right correction but kept one whole family of molecules out of its training. Then
we gave it <em>k</em> molecules of that family and judged it on the other 27 of that family.</li>
</ul>

<p>For each <em>k</em>, from 5 to 200, four ways of adjusting competed: the last layer with the brake (the old way), the last layer without the
brake, the whole head free, and a fresh network that learns from the <em>k</em> molecules alone. The fresh network tells us what the earlier
practice is worth.</p>

<p>Before anything ran, we wrote down what we expected (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/bf408823">the registration</a>): in the first rehearsal the whole head should beat the old way; in the second
probably not, because there the correction does not change, only the molecules.</p>

<h2 id="what-the-rehearsal-said">What the rehearsal said</h2>

<p>Both expectations held (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/07818876">the read</a>).</p>

<p>In the first rehearsal, the old way did not learn at all. With the brake on, the out-of-plane error stayed between 8 and 10 cm⁻¹ whether
the network saw 5 molecules or 200: the brake kept it on the wrong correction. With the whole head free, the error fell steadily, from
6.6 cm⁻¹ with 5 molecules to 4.0 with 20 and 2.8 with 200, and the line brightness improved with it. One nuance: with only five
molecules, freeing just the last layer (without the brake) did slightly better (5.9) than freeing the whole head. From twenty molecules
on, the whole head led.</p>

<p>The earlier practice was worth something even though it had learned the wrong correction, and most of all when examples were few. With
20 molecules, the adjusted network (4.0) was already better than a fresh network that had seen 50 (4.8). With more examples the gap
closed, and at 200 molecules the two were equal.</p>

<p>In the second rehearsal, the network was right before it adjusted at all: about 2.7 cm⁻¹ on a family it had never seen. Adjusting
changed little. That was expected too: what it had learned carried over to the new molecules.</p>

<h2 id="what-this-means">What this means</h2>

<p>Our eleven anchors failed the first test because of how we let the network learn, not because there were too few of them or because the
idea is wrong.</p>

<p>The rehearsal also gives a first answer to the question the next phase has to put a price on: how many anchors buy how much accuracy.
With the whole head free, the out-of-plane error in the rehearsal was:</p>

<table>
  <thead>
    <tr>
      <th>molecules shown</th>
      <th>5</th>
      <th>10</th>
      <th>20</th>
      <th>50</th>
      <th>100</th>
      <th>200</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>error (cm⁻¹)</td>
      <td>6.6</td>
      <td>5.1</td>
      <td>4.0</td>
      <td>3.8</td>
      <td>3.2</td>
      <td>2.8</td>
    </tr>
  </tbody>
</table>

<p>Most of the gain comes from the first twenty or so: going from 5 to 20 molecules removes more error than going from 20 to 200. After
that, each doubling buys a few tenths of a wavenumber. Our own target for this family is 3 cm⁻¹ or better; in the rehearsal that took between
100 and 200 molecules. For coupled cluster that would be far more anchors than we have, so for the next phase the curve says: a few
dozen anchors buy most of the improvement, and the last step to 3 cm⁻¹ is the expensive part.</p>

<p>It is a rehearsal, so it has limits: the stand-in replaces coupled cluster, there is one way of making the correction different, and one
held-out family. It is a guide for the real step, not a guarantee.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="progress" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Size was not the wall, and the ruler got cheaper</title><link href="https://thebreadishard.github.io/2026/10/20/size-was-not-the-wall/" rel="alternate" type="text/html" title="Size was not the wall, and the ruler got cheaper" /><published>2026-10-20T07:00:00+02:00</published><updated>2026-10-20T07:00:00+02:00</updated><id>https://thebreadishard.github.io/2026/10/20/size-was-not-the-wall</id><content type="html" xml:base="https://thebreadishard.github.io/2026/10/20/size-was-not-the-wall/"><![CDATA[<!-- Written 20 Oct 2026 at the user's request ("Ja graag", on post 24 combining the size test and the cheaper anchors); published the same
     day on the user's word ("Push de repo en publiceer"), after the plan repo was pushed so the commit links resolve.
     Numbers trace to (CapstonePlan repo, plans/05_delta-probed-ir-pipeline):
     GoalGathering/notes/PreRegistration_2026-09-25_RungC_Equivariant_vs_Pair_Model.md, chain 41's registration (11 Oct), amendment, addendum
       and outcome (19 Oct, commit a7f54e89): q5 = r_S / r_K = 0.428 / 0.406 = 1.05, lines 1.3 and 1.8; corrected omega on s5 3.97 / 3.41 /
       22.63 cm-1; control half 0.413 -> full 0.406; chain 39's q = 1.96; two s5 molecules out for imaginary modes in their FD labels;
       modules/05_support_predictor/out/read_chain41.{json,md}.
     GoalGathering/notes/PreRegistration_2026-10-19_DFMP2_Composite_Step.md and its outcome (commit 40e09a0c): W 4.6e-9 / 4.9e-8 Eh/bohr;
       swap test pyridine 0.423, naphthalene 0.460 cm-1 (line 0.5; prediction < 0.3 wrong); rows 9/33 and 30/136 s against 13/196 and
       79/1,996 s; peak RSS 3.33 GB; five anchors about 8.4 h; probes/results_m1/dfmp2_composite_2026-10-19/.
     The 98 worker-hours at 26 GB: GoalGathering/notes/PreRegistration_2026-10-12_Anchor_Set_Three.md, declaration of 19 Oct.
     Quinoxaline: probes/results_m1/dfmp2_anchors_2026-10-20/composite_quinoxaline.md (commit 190be713); registry modules/ANCHORS.md.
     Rewritten the same day at 07:4x on the user's word ("Doe maar geen addendum. Herschrijf het stuk (een beetje)."), replacing a dated
     addendum that was live from 07:2x: the four families on s5, computed after the read (chain 41 addendum, commit e0dde855) — size arm
     CH-stretch 3.24, CH-oop 4.99, ring-ip 3.79, other 5.04 cm-1; controls 1.18-1.40, 4.10-4.13, 2.98-3.77, 4.54-4.77; target 3 cm-1 per
     family (decision 53); 82 % = 1 - 3.97 / 22.63. -->

<p>Two things held this project back in the past fortnight. One was a measurement that seemed to say our method stops working when molecules
get bigger. The other was money: the most accurate reference numbers we use, the <a href="/glossary/#anchor">anchors</a>, needed a rented server that we
had stopped paying for. This week both moved, and neither moved by luck: each was a test with its line written down before the result.</p>

<p><strong>The worry about size.</strong> The method learns a correction: how a cheap calculation of a molecule’s vibrations should be adjusted to look like an
expensive one. It learns from a <a href="/glossary/#corpus">corpus</a> of about a thousand molecules, none with more than four fused rings and none larger
than about thirty atoms. The molecules astronomers care about are bigger, from forty atoms to a hundred and more. So the question is whether
the correction learned on small molecules still holds on large ones, where we cannot afford to teach it.</p>

<p>An earlier test, chain 39, said it got much worse: the error of a size-shifted test set was almost twice that of a matched control (a ratio of
1.96). But that test had a flaw we knew about: its small training molecules also had fewer rings, so a bigger molecule was always also a
molecule with a new ring count. Size and ring count were tangled together.</p>

<p><strong>Chain 41 untangles them.</strong> We took thirty molecules with five fused rings, 32 to 40 atoms: perylene, benzo[a]pyrene, benzo[e]pyrene,
benzo[k]fluoranthene, picene and dibenz[a,h]anthracene, each with four relatives carrying small changes. One model was trained on the corpus as
it stands, at most four rings, and tested on them. A second, the control, was also given the five-ring molecules of three of the six families,
and tested on the other three. If the first model is much worse than the control, five rings must be in the training data. If they are about
equal, the correction carries over.</p>

<p>The measure is the share of the <a href="/glossary/#ring-coupling">ring coupling</a> error that a model leaves behind, compared with making no correction
at all: 0 would be perfect, 1 would be no help. Before any number, we wrote down two lines. A ratio of the two models of 1.3 or less: the
correction carries. A ratio of 1.8 or more: steep, and the corpus would need every ring count we ever want to predict.</p>

<p>The result: 0.428 for the model that never saw five rings, 0.406 for the control. A ratio of <strong>1.05</strong> (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/a7f54e89">the read</a>). In frequencies, after the correction,
the two models are off by 3.97 and 3.41 cm⁻¹ on these molecules, where making no correction at all is off by 22.63. And giving the control
twice as many five-ring molecules barely changed it (0.413 with half, 0.406 with all), so this is not a case of the control simply having had
too few examples.</p>

<p>So the steep penalty of chain 39 came mostly from the new ring count, not from size itself. For the ring couplings, the quantity this test
was built to measure, size up to 40 atoms is not a wall.</p>

<p><strong>Equally good is not yet good enough.</strong> Two models can agree and both be poor, so the ratio alone does not settle it. The absolute numbers
settle part of it: without a single five-ring molecule in training, the model still removes about 82 % of the frequency error on these
molecules, from 22.6 to 4.0 cm⁻¹. But our target is 3 cm⁻¹ or less in every family of vibrations, and on the five-ring molecules no family
gets there, with or without five-ring examples (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/e0dde855">per family</a>). The out-of-plane hydrogen bends and the catch-all family sit at
4 to 5 cm⁻¹, as they also do on some families inside the corpus. And one family tells a different story from the ratio: the carbon–hydrogen
stretches are off by 3.2 cm⁻¹ without five-ring examples and by 1.2 to 1.4 with them. A handful of larger ring systems in the corpus clearly
helps. These family numbers were promised beside the ratio before the test, but our read script left them out; we computed them only after
the result was known.</p>

<p><strong>The cheaper ruler.</strong> The second story is about the anchors: molecules for which we have the expensive kind of calculation,
<a href="/glossary/#coupled-cluster">coupled cluster</a>, the reference the network is checked and tuned against. Even that is done in two parts. The
coupled-cluster part uses a small <a href="/glossary/#basis-set">basis set</a>, a coarse brush, because a fine brush would be far too costly. A cheaper
method, MP2, then measures how much the answer changes from the coarse brush to the fine one, and that change is added on top.</p>

<p>That MP2 step was the expensive part in memory: 26 gigabytes per process, which only a rented server had. Five new anchors were finished
in their coupled-cluster part and waiting for it, about 98 server-hours, at a time when we had decided to stop renting.</p>

<p>There is a well-known shortcut, density fitting, which builds the huge table of electron interactions from a much smaller one. The program
we use for most of our work cannot do the derivatives we need with that shortcut; another program, psi4, can. The risk is that the shortcut
changes the answer. So the test, again written down first: take two molecules whose MP2 step we already had the old way, pyridine and
naphthalene, redo the step with the shortcut, and compare the vibrational frequencies that come out. Within 0.5 cm⁻¹ for every vibration:
adopt it. Up to 2: note the difference and carry it as an error. Beyond: refuse it.</p>

<p>Before that, a sanity check on water: the shortcut’s derivatives agree with derivatives worked out step by step from its energies to
5 × 10⁻⁸ atomic units, against a line of 10⁻⁶.</p>

<p>The swap: pyridine’s frequencies moved by at most <strong>0.42 cm⁻¹</strong>, naphthalene’s by at most <strong>0.46</strong>. Both under the line of 0.5, so the
shortcut is adopted (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/40e09a0c">the read</a>). I had predicted less than 0.3, and that was wrong: the line holds, with less room to spare than I expected.</p>

<p>What it buys: one step of the fine-brush calculation for naphthalene took 136 seconds on the laptop, against 1,996 seconds the old way on a
large server. The largest memory use was 3.3 gigabytes. All five waiting anchors together come to about eight and a half hours on the laptop
we already own.</p>

<p><strong>The first one is in.</strong> Quinoxaline, a two-ring molecule with two nitrogen atoms, went through the whole chain this morning. Its two softest
vibrations come out at 115 and 119 cm⁻¹ with the coarse brush, and at 167 and 179 with the fine-brush step added. The cheap calculation the
network starts from has them at 177 and 185. The coarse brush alone would have taught the network to pull these two down by about 60 cm⁻¹;
with the step, by about 10. Most of that big pull was the brush, not the molecule. It passed our promotion rule and is now one of seven
anchors in use (<a href="https://github.com/thebreadishard/udacity-capstone-plan/commit/190be713">the anchor</a>).
The other four follow today.</p>

<p><strong>What this is not.</strong> Chain 41 compares two density functional methods, a cheap one and a more careful one, as a stand-in for the real
target; it says the correction carries in size, not yet that the coupled-cluster correction does. It reaches 40 atoms; the 50- to
100-atom molecules are the next test, chain 42. Two of the thirty molecules left the test before any result was read, because the reference
calculations for them showed an unstable vibration; they come back when better reference calculations exist. And the density-fitting
shortcut was tested on two molecules and adopted with a margin smaller than predicted: if one of the new anchors behaves differently, the old
way is still there, on a server, when the budget allows.</p>]]></content><author><name>Frederic Petrignani</name></author><category term="plan05" /><category term="progress" /><summary type="html"><![CDATA[]]></summary></entry></feed>