{"id":"mlip-elastic-operator-failure-diagnosis","title":"Operator Failure Diagnosis","subtitle":"Why the LOO-PCA bias operator degraded MAE and what scalar-bulk operator v0.2 learned instead.","category":"validation","tags":["mlip","operator","bias-correction","diagnosis"],"source":"articles/mlip-elastic-benchmark/operator-failure-diagnosis-2026-06-27.md","lang":"en","words":2327,"readMinutes":11,"toc":[{"depth":2,"text":"Executive Summary","id":"executive-summary"},{"depth":2,"text":"Why the Global Operator Failed","id":"why-the-global-operator-failed"},{"depth":2,"text":"Error-Geometry Diagnostics","id":"error-geometry-diagnostics"},{"depth":3,"text":"TensorNet/PBE error matrix (vs TPBE0K)","id":"tensornet-pbe-error-matrix-vs-tpbe0k"},{"depth":3,"text":"Per-element raw errors vs TPBE0K","id":"per-element-raw-errors-vs-tpbe0k"},{"depth":3,"text":"Global LOO-PCA residuals vs Tr2SCAN0K","id":"global-loo-pca-residuals-vs-tr2scan0k"},{"depth":3,"text":"Comparison to Layer-1 classical bias vectors","id":"comparison-to-layer-1-classical-bias-vectors"},{"depth":2,"text":"Alternative Operators Table","id":"alternative-operators-table"},{"depth":2,"text":"v0.2 scalar-bulk operator","id":"v0-2-scalar-bulk-operator"},{"depth":3,"text":"Scalar-bulk per-element α and predictions","id":"scalar-bulk-per-element-and-predictions"},{"depth":2,"text":"Recommended v0.2 Operator","id":"recommended-v0-2-operator"},{"depth":2,"text":"Cell-size independence check","id":"cell-size-independence-check"},{"depth":2,"text":"Variance subset","id":"variance-subset"},{"depth":2,"text":"v0.3 Directional correction scheme and feedback loop","id":"v0-3-directional-correction-scheme-and-feedback-loop"},{"depth":3,"text":"Lean formalization","id":"lean-formalization"},{"depth":3,"text":"Python feedback loop","id":"python-feedback-loop"},{"depth":3,"text":"FeedbackLoop benchmark results","id":"feedbackloop-benchmark-results"},{"depth":2,"text":"Implications for the Distillation Engine","id":"implications-for-the-distillation-engine"}],"html":"<h1 id=\"lupine-projection-law-operator-failure-diagnosis-layer-2-mlip-benchmark\">Lupine Projection Law Operator Failure Diagnosis — Layer-2 MLIP Benchmark</h1><p><strong>Date:</strong> 2026-06-27</p>\n<p><strong>Scope:</strong> 16 cubic elemental metals; TensorNet/PBE 1×1×1 predictions vs. TPBE_0K / Tr2SCAN_0K targets.</p>\n<p><strong>Sources:</strong></p>\n<ul>\n<li><code>lupine/data/targets_0K.json</code></li>\n<li><code>lupine/data/mlip_elastic_benchmark_outputs_1x1x1_16elem/</code></li>\n<li><code>lupine/data/distill_inputs/layer1_bias_vectors.json</code></li>\n</ul>\n<h2 id=\"executive-summary\">Executive Summary</h2><ul>\n<li>The v0.1 global LOO-PCA operator <strong>fails on this MLIP set</strong>. Against the headline TPBE_0K target it degrades mean MAE from 14.55 GPa (raw) to <strong>63.40 GPa</strong>, and against the Tr2SCAN_0K corrected target it degrades from 22.55 GPa to <strong>54.28 GPa</strong>. It cannot beat the 3-architecture PBE ensemble (11.60 GPa vs TPBE).</li>\n<li>The headline cost-accuracy claim (corrected single model beats 3× ensemble at ~5× lower cost) is therefore <strong>not supported</strong> by the v0.1 global operator on this benchmark.</li>\n<li>The recommended v0.2 operator on the intended corrected target (Tr2SCAN_0K) is <strong>scalar-bulk</strong>, with mean MAE 14.13 GPa vs Tr2SCAN (raw 22.55 GPa, shift-only 14.55 GPa, ensemble 19.89 GPa) at the same single-run cost. It is 2.70× cheaper than the ensemble and 3.86× cheaper than the 3×3×3 reference.</li>\n<li>Cell-size independence is confirmed: <code>scalar-bulk</code> fit on the 3×3×3 grid gives mean MAE 14.14 GPa vs Tr2SCAN, essentially identical to the 1×1×1 result.</li>\n<li>Seed variance is negligible: a 4-element, 3-seed variance subset (Ca, Cu, Fe, Cr) shows TensorNet/PBE 1×1×1 is deterministic, with MAE standard deviations ~0 GPa.</li>\n<li>Cost for all 1×1×1 single-model operators is identical: 48.3 s total for TensorNet/PBE 1×1×1 (0.0134 core-hours at 1 core/run).</li>\n</ul>\n<h2 id=\"why-the-global-operator-failed\">Why the Global Operator Failed</h2><p>The v0.1 operator assumes a single, shared 1D error direction across all 16 elements. The data refute this:</p>\n<ul>\n<li>The error matrix has singular values [132.85, 54.14, 24.78] GPa, participation ratio 2.12, and effective rank 3. It is <strong>not low-rank</strong>; a single global PC explains only part of the variance.</li>\n<li>The first principal component ([0.57, 0.81, -0.11]) is dominated by C11/C12 (bulk-like) error and is essentially the Cr-error direction. It therefore over-corrects elements whose error geometry differs (noble metals, some BCC transition metals).</li>\n<li>Errors are <strong>bonding-class specific</strong>, not element-generic. The MLIP global error cloud stratifies cleanly by class (see Fig. 2), whereas Layer-1 classical bias vectors were low-rank <em>within</em> a family.</li>\n<li>The functional shift (Tr2SCAN − TPBE) is element-specific and already removes much of the systematic stiffness bias. Adding a global bias vector on top of the shift double-counts structure on classes where the residual bias is small (noble metals) or points in a different direction (BCC).</li>\n</ul>\n<h2 id=\"error-geometry-diagnostics\">Error-Geometry Diagnostics</h2><h3 id=\"tensornet-pbe-error-matrix-vs-tpbe-0k\">TensorNet/PBE error matrix (vs TPBE_0K)</h3><ul>\n<li>Singular values: <code>[132.84506818736944, 54.135811599011426, 24.780801715834333]</code></li>\n<li>Participation ratio: <strong>2.116</strong></li>\n<li>Effective rank: <strong>3</strong></li>\n<li>First PC (C11, C12, C44): <strong>[0.5726741012529919, 0.8121764674121165, -0.11141705226783469]</strong></li>\n</ul>\n<h3 id=\"per-element-raw-errors-vs-tpbe-0k\">Per-element raw errors vs TPBE_0K</h3><div class=\"table-wrap\"><table><thead><tr>\n<th>Element</th>\n<th>Class</th>\n<th>C11 err</th>\n<th>C12 err</th>\n<th>C44 err</th>\n<th>MAE</th>\n</tr>\n</thead><tbody><tr>\n<td data-label=\"Element\">Ag</td>\n<td data-label=\"Class\">noble_coinage_fcc</td>\n<td data-label=\"C11 err\">1.02</td>\n<td data-label=\"C12 err\">-1.50</td>\n<td data-label=\"C44 err\">8.44</td>\n<td data-label=\"MAE\">3.65</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Al</td>\n<td data-label=\"Class\">post_transition</td>\n<td data-label=\"C11 err\">-7.88</td>\n<td data-label=\"C12 err\">-20.20</td>\n<td data-label=\"C44 err\">3.75</td>\n<td data-label=\"MAE\">10.61</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Au</td>\n<td data-label=\"Class\">noble_coinage_fcc</td>\n<td data-label=\"C11 err\">-21.14</td>\n<td data-label=\"C12 err\">-30.97</td>\n<td data-label=\"C44 err\">12.03</td>\n<td data-label=\"MAE\">21.38</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ca</td>\n<td data-label=\"Class\">alkaline_earth_fcc</td>\n<td data-label=\"C11 err\">-0.40</td>\n<td data-label=\"C12 err\">-5.43</td>\n<td data-label=\"C44 err\">1.71</td>\n<td data-label=\"MAE\">2.51</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Cr</td>\n<td data-label=\"Class\">transition_bcc</td>\n<td data-label=\"C11 err\">46.52</td>\n<td data-label=\"C12 err\">80.54</td>\n<td data-label=\"C44 err\">-10.48</td>\n<td data-label=\"MAE\">45.85</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Cu</td>\n<td data-label=\"Class\">noble_coinage_fcc</td>\n<td data-label=\"C11 err\">15.47</td>\n<td data-label=\"C12 err\">-13.30</td>\n<td data-label=\"C44 err\">0.41</td>\n<td data-label=\"MAE\">9.72</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Fe</td>\n<td data-label=\"Class\">transition_bcc</td>\n<td data-label=\"C11 err\">-26.99</td>\n<td data-label=\"C12 err\">-32.81</td>\n<td data-label=\"C44 err\">-2.13</td>\n<td data-label=\"MAE\">20.64</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Mo</td>\n<td data-label=\"Class\">transition_bcc</td>\n<td data-label=\"C11 err\">-25.07</td>\n<td data-label=\"C12 err\">-10.11</td>\n<td data-label=\"C44 err\">3.92</td>\n<td data-label=\"MAE\">13.03</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Nb</td>\n<td data-label=\"Class\">transition_bcc</td>\n<td data-label=\"C11 err\">-18.01</td>\n<td data-label=\"C12 err\">-41.90</td>\n<td data-label=\"C44 err\">6.01</td>\n<td data-label=\"MAE\">21.97</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ni</td>\n<td data-label=\"Class\">transition_fcc</td>\n<td data-label=\"C11 err\">-21.88</td>\n<td data-label=\"C12 err\">-4.17</td>\n<td data-label=\"C44 err\">-1.44</td>\n<td data-label=\"MAE\">9.16</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Pd</td>\n<td data-label=\"Class\">transition_fcc</td>\n<td data-label=\"C11 err\">-0.57</td>\n<td data-label=\"C12 err\">-12.78</td>\n<td data-label=\"C44 err\">5.89</td>\n<td data-label=\"MAE\">6.41</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Pt</td>\n<td data-label=\"Class\">transition_fcc</td>\n<td data-label=\"C11 err\">-13.04</td>\n<td data-label=\"C12 err\">-29.88</td>\n<td data-label=\"C44 err\">13.05</td>\n<td data-label=\"MAE\">18.65</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Sr</td>\n<td data-label=\"Class\">alkaline_earth_fcc</td>\n<td data-label=\"C11 err\">6.09</td>\n<td data-label=\"C12 err\">-0.13</td>\n<td data-label=\"C44 err\">-0.56</td>\n<td data-label=\"MAE\">2.26</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ta</td>\n<td data-label=\"Class\">transition_bcc</td>\n<td data-label=\"C11 err\">-29.16</td>\n<td data-label=\"C12 err\">-5.80</td>\n<td data-label=\"C44 err\">-20.86</td>\n<td data-label=\"MAE\">18.61</td>\n</tr>\n<tr>\n<td data-label=\"Element\">V</td>\n<td data-label=\"Class\">transition_bcc</td>\n<td data-label=\"C11 err\">30.47</td>\n<td data-label=\"C12 err\">-10.94</td>\n<td data-label=\"C44 err\">-0.40</td>\n<td data-label=\"MAE\">13.94</td>\n</tr>\n<tr>\n<td data-label=\"Element\">W</td>\n<td data-label=\"Class\">transition_bcc</td>\n<td data-label=\"C11 err\">-24.70</td>\n<td data-label=\"C12 err\">-15.89</td>\n<td data-label=\"C44 err\">2.43</td>\n<td data-label=\"MAE\">14.34</td>\n</tr>\n</tbody></table></div><h3 id=\"global-loo-pca-residuals-vs-tr2scan-0k\">Global LOO-PCA residuals vs Tr2SCAN_0K</h3><div class=\"table-wrap\"><table><thead><tr>\n<th>Element</th>\n<th>C11 res</th>\n<th>C12 res</th>\n<th>C44 res</th>\n<th>MAE</th>\n</tr>\n</thead><tbody><tr>\n<td data-label=\"Element\">Ag</td>\n<td data-label=\"C11 res\">0.44</td>\n<td data-label=\"C12 res\">-2.30</td>\n<td data-label=\"C44 res\">8.56</td>\n<td data-label=\"MAE\">3.77</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Al</td>\n<td data-label=\"C11 res\">-8.47</td>\n<td data-label=\"C12 res\">-21.00</td>\n<td data-label=\"C44 res\">3.86</td>\n<td data-label=\"MAE\">11.11</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Au</td>\n<td data-label=\"C11 res\">-21.73</td>\n<td data-label=\"C12 res\">-31.77</td>\n<td data-label=\"C44 res\">12.12</td>\n<td data-label=\"MAE\">21.87</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ca</td>\n<td data-label=\"C11 res\">-0.98</td>\n<td data-label=\"C12 res\">-6.24</td>\n<td data-label=\"C44 res\">1.82</td>\n<td data-label=\"MAE\">3.01</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Cr</td>\n<td data-label=\"C11 res\">45.61</td>\n<td data-label=\"C12 res\">80.14</td>\n<td data-label=\"C44 res\">-10.49</td>\n<td data-label=\"MAE\">45.41</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Cu</td>\n<td data-label=\"C11 res\">14.90</td>\n<td data-label=\"C12 res\">-14.11</td>\n<td data-label=\"C44 res\">0.52</td>\n<td data-label=\"MAE\">9.84</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Fe</td>\n<td data-label=\"C11 res\">-27.57</td>\n<td data-label=\"C12 res\">-33.62</td>\n<td data-label=\"C44 res\">-2.00</td>\n<td data-label=\"MAE\">21.06</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Mo</td>\n<td data-label=\"C11 res\">-25.64</td>\n<td data-label=\"C12 res\">-10.92</td>\n<td data-label=\"C44 res\">4.03</td>\n<td data-label=\"MAE\">13.53</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Nb</td>\n<td data-label=\"C11 res\">-18.62</td>\n<td data-label=\"C12 res\">-42.68</td>\n<td data-label=\"C44 res\">6.11</td>\n<td data-label=\"MAE\">22.47</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ni</td>\n<td data-label=\"C11 res\">-22.46</td>\n<td data-label=\"C12 res\">-4.98</td>\n<td data-label=\"C44 res\">-1.33</td>\n<td data-label=\"MAE\">9.59</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Pd</td>\n<td data-label=\"C11 res\">-1.15</td>\n<td data-label=\"C12 res\">-13.58</td>\n<td data-label=\"C44 res\">6.00</td>\n<td data-label=\"MAE\">6.91</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Pt</td>\n<td data-label=\"C11 res\">-13.63</td>\n<td data-label=\"C12 res\">-30.68</td>\n<td data-label=\"C44 res\">13.14</td>\n<td data-label=\"MAE\">19.15</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Sr</td>\n<td data-label=\"C11 res\">5.51</td>\n<td data-label=\"C12 res\">-0.94</td>\n<td data-label=\"C44 res\">-0.45</td>\n<td data-label=\"MAE\">2.30</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ta</td>\n<td data-label=\"C11 res\">-29.74</td>\n<td data-label=\"C12 res\">-6.61</td>\n<td data-label=\"C44 res\">-20.74</td>\n<td data-label=\"MAE\">19.03</td>\n</tr>\n<tr>\n<td data-label=\"Element\">V</td>\n<td data-label=\"C11 res\">29.93</td>\n<td data-label=\"C12 res\">-11.77</td>\n<td data-label=\"C44 res\">-0.28</td>\n<td data-label=\"MAE\">13.99</td>\n</tr>\n<tr>\n<td data-label=\"Element\">W</td>\n<td data-label=\"C11 res\">-25.27</td>\n<td data-label=\"C12 res\">-16.70</td>\n<td data-label=\"C44 res\">2.54</td>\n<td data-label=\"MAE\">14.84</td>\n</tr>\n</tbody></table></div><h3 id=\"comparison-to-layer-1-classical-bias-vectors\">Comparison to Layer-1 classical bias vectors</h3><div class=\"table-wrap\"><table><thead><tr>\n<th>Family</th>\n<th>N</th>\n<th>Participation ratio</th>\n<th>Effective rank</th>\n</tr>\n</thead><tbody><tr>\n<td data-label=\"Family\">Ackland-1987</td>\n<td data-label=\"N\">8</td>\n<td data-label=\"Participation ratio\">0.449</td>\n<td data-label=\"Effective rank\">3</td>\n</tr>\n<tr>\n<td data-label=\"Family\">Ackland-1997</td>\n<td data-label=\"N\">1</td>\n<td data-label=\"Participation ratio\">0.968</td>\n<td data-label=\"Effective rank\">0</td>\n</tr>\n<tr>\n<td data-label=\"Family\">Adams-1989</td>\n<td data-label=\"N\">5</td>\n<td data-label=\"Participation ratio\">0.787</td>\n<td data-label=\"Effective rank\">3</td>\n</tr>\n<tr>\n<td data-label=\"Family\">Chamati-2006</td>\n<td data-label=\"N\">1</td>\n<td data-label=\"Participation ratio\">0.934</td>\n<td data-label=\"Effective rank\">0</td>\n</tr>\n<tr>\n<td data-label=\"Family\">Foiles-1986</td>\n<td data-label=\"N\">3</td>\n<td data-label=\"Participation ratio\">0.527</td>\n<td data-label=\"Effective rank\">2</td>\n</tr>\n<tr>\n<td data-label=\"Family\">Han-2003</td>\n<td data-label=\"N\">2</td>\n<td data-label=\"Participation ratio\">0.546</td>\n<td data-label=\"Effective rank\">1</td>\n</tr>\n<tr>\n<td data-label=\"Family\">Howells-2018</td>\n<td data-label=\"N\">1</td>\n<td data-label=\"Participation ratio\">0.754</td>\n<td data-label=\"Effective rank\">0</td>\n</tr>\n<tr>\n<td data-label=\"Family\">Olsson-2009</td>\n<td data-label=\"N\">1</td>\n<td data-label=\"Participation ratio\">0.483</td>\n<td data-label=\"Effective rank\">0</td>\n</tr>\n<tr>\n<td data-label=\"Family\"><strong>MLIP global error</strong></td>\n<td data-label=\"N\"><strong>16</strong></td>\n<td data-label=\"Participation ratio\"><strong>2.116</strong></td>\n<td data-label=\"Effective rank\"><strong>3</strong></td>\n</tr>\n</tbody></table></div><p>Layer-1 bias vectors were family-specific and often 1D within a family. The MLIP errors are not globally low-rank; the low-dimensional structure is <strong>class-local</strong>, not universal.</p>\n<h2 id=\"alternative-operators-table\">Alternative Operators Table</h2><p>All operators use only the existing 1×1×1 TensorNet/PBE outputs. Cost equals one raw 1×1×1 run.</p>\n<div class=\"table-wrap\"><table><thead><tr>\n<th>Operator</th>\n<th align=\"right\">mean MAE vs TPBE</th>\n<th align=\"right\">median MAE vs TPBE</th>\n<th align=\"right\">mean MAE vs Tr2SCAN</th>\n<th align=\"right\">median MAE vs Tr2SCAN</th>\n<th>Notes</th>\n</tr>\n</thead><tbody><tr>\n<td data-label=\"Operator\">raw</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">14.55</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">13.48</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">22.55</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">22.50</td>\n<td data-label=\"Notes\"></td>\n</tr>\n<tr>\n<td data-label=\"Operator\">shift-only</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">16.28</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">15.74</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">14.55</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">13.48</td>\n<td data-label=\"Notes\">exact PBE→r2SCAN functional shift</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">global-loo-pca</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\"><strong>63.40</strong></td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\"><strong>65.33</strong></td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\"><strong>54.28</strong></td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\"><strong>55.09</strong></td>\n<td data-label=\"Notes\">v0.1 operator; degrades accuracy</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">global-loo-pca-unshifted</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">54.28</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">55.09</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">45.83</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">46.05</td>\n<td data-label=\"Notes\">PCA bias without functional shift</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">scalar-bulk</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">19.17</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">19.01</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\"><strong>14.13</strong></td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\"><strong>11.16</strong></td>\n<td data-label=\"Notes\">v0.2 recommended on Tr2SCAN target</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">ensemble-1x1x1</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\"><strong>11.60</strong></td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">11.62</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">19.89</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">19.65</td>\n<td data-label=\"Notes\">3-model PBE ensemble</td>\n</tr>\n</tbody></table></div><p>Key observations:</p>\n<ul>\n<li><code>shift-only</code> already improves over raw vs Tr2SCAN (14.55 vs 22.55 GPa), confirming that the bulk-modulus functional shift captures real systematic stiffness bias.</li>\n<li><code>global-loo-pca</code> (v0.1) <strong>degrades</strong> accuracy on both targets (63.40 vs TPBE, 54.28 vs Tr2SCAN) because a single global PC is a poor approximation of class-specific error directions.</li>\n<li><code>scalar-bulk</code> (v0.2) improves further over shift-only on the Tr2SCAN target (14.13 vs 14.55 GPa) and beats the 3-architecture ensemble (19.89 GPa) at 2.70× lower cost. On the PBE headline target it does not beat raw or the ensemble.</li>\n<li>Class-aware and locally-fitted operators remain interesting future directions, but they have not yet been benchmarked on this dataset.</li>\n</ul>\n<h2 id=\"v0-2-scalar-bulk-operator\">v0.2 scalar-bulk operator</h2><p>The v0.2 operator, <code>scalar-bulk</code>, is a leave-one-out scalar re-scaling of the bulk-modulus functional shift. For each held-out element, a single scalar <code>α</code> is fit on the other 15 elements so that the bulk modulus of <code>raw + α · (Tr2SCAN − TPBE)</code> matches the Tr2SCAN bulk modulus; that <code>α</code> is then applied to the held-out element&#39;s shift. It uses only the existing 1×1×1 TensorNet/PBE output and the target-derived shift, so its cost equals one raw 1×1×1 run.</p>\n<p><strong>Aggregate performance (1×1×1 TensorNet/PBE, LOO scalar-bulk):</strong></p>\n<div class=\"table-wrap\"><table><thead><tr>\n<th>Metric</th>\n<th align=\"right\">vs TPBE_0K</th>\n<th align=\"right\">vs Tr2SCAN_0K</th>\n</tr>\n</thead><tbody><tr>\n<td data-label=\"Metric\">mean MAE</td>\n<td align=\"right\" data-label=\"vs TPBE_0K\">19.17 GPa</td>\n<td align=\"right\" data-label=\"vs Tr2SCAN_0K\"><strong>14.13 GPa</strong></td>\n</tr>\n<tr>\n<td data-label=\"Metric\">median MAE</td>\n<td align=\"right\" data-label=\"vs TPBE_0K\">19.01 GPa</td>\n<td align=\"right\" data-label=\"vs Tr2SCAN_0K\"><strong>11.16 GPa</strong></td>\n</tr>\n<tr>\n<td data-label=\"Metric\">core-hours</td>\n<td align=\"right\" data-label=\"vs TPBE_0K\">0.0134</td>\n<td align=\"right\" data-label=\"vs Tr2SCAN_0K\">0.0134</td>\n</tr>\n</tbody></table></div><p>On the Tr2SCAN-corrected target, <code>scalar-bulk</code> improves over raw (22.55 GPa), shift-only (14.55 GPa), and the 3-architecture ensemble (19.89 GPa), while remaining 2.70× cheaper than the ensemble and 3.86× cheaper than the 3×3×3 reference. The v0.1 global LOO-PCA operator is far worse (54.28 GPa). On the PBE headline target it does not beat raw or the ensemble; no single-model operator does.</p>\n<h3 id=\"scalar-bulk-per-element-and-predictions\">Scalar-bulk per-element α and predictions</h3><div class=\"table-wrap\"><table><thead><tr>\n<th>Element</th>\n<th>α</th>\n<th>C11 corr</th>\n<th>C12 corr</th>\n<th>C44 corr</th>\n<th>MAE vs Tr2SCAN</th>\n</tr>\n</thead><tbody><tr>\n<td data-label=\"Element\">Ag</td>\n<td data-label=\"α\">1.338</td>\n<td data-label=\"C11 corr\">134.05</td>\n<td data-label=\"C12 corr\">96.72</td>\n<td data-label=\"C44 corr\">60.66</td>\n<td data-label=\"MAE vs Tr2SCAN\">7.32</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Al</td>\n<td data-label=\"α\">1.322</td>\n<td data-label=\"C11 corr\">96.05</td>\n<td data-label=\"C12 corr\">52.75</td>\n<td data-label=\"C44 corr\">35.61</td>\n<td data-label=\"MAE vs Tr2SCAN\">10.61</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Au</td>\n<td data-label=\"α\">1.305</td>\n<td data-label=\"C11 corr\">149.66</td>\n<td data-label=\"C12 corr\">115.43</td>\n<td data-label=\"C44 corr\">39.52</td>\n<td data-label=\"MAE vs Tr2SCAN\">20.73</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ca</td>\n<td data-label=\"α\">1.322</td>\n<td data-label=\"C11 corr\">20.43</td>\n<td data-label=\"C12 corr\">9.66</td>\n<td data-label=\"C44 corr\">15.80</td>\n<td data-label=\"MAE vs Tr2SCAN\">2.51</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Cr</td>\n<td data-label=\"α\">1.580</td>\n<td data-label=\"C11 corr\">596.29</td>\n<td data-label=\"C12 corr\">233.73</td>\n<td data-label=\"C44 corr\">102.13</td>\n<td data-label=\"MAE vs Tr2SCAN\">52.45</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Cu</td>\n<td data-label=\"α\">1.352</td>\n<td data-label=\"C11 corr\">200.56</td>\n<td data-label=\"C12 corr\">152.41</td>\n<td data-label=\"C44 corr\">90.38</td>\n<td data-label=\"MAE vs Tr2SCAN\">11.71</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Fe</td>\n<td data-label=\"α\">1.223</td>\n<td data-label=\"C11 corr\">251.43</td>\n<td data-label=\"C12 corr\">136.41</td>\n<td data-label=\"C44 corr\">107.67</td>\n<td data-label=\"MAE vs Tr2SCAN\">16.91</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Mo</td>\n<td data-label=\"α\">1.294</td>\n<td data-label=\"C11 corr\">476.13</td>\n<td data-label=\"C12 corr\">157.35</td>\n<td data-label=\"C44 corr\">116.93</td>\n<td data-label=\"MAE vs Tr2SCAN\">10.57</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Nb</td>\n<td data-label=\"α\">1.328</td>\n<td data-label=\"C11 corr\">213.65</td>\n<td data-label=\"C12 corr\">102.45</td>\n<td data-label=\"C44 corr\">16.90</td>\n<td data-label=\"MAE vs Tr2SCAN\">22.15</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ni</td>\n<td data-label=\"α\">1.316</td>\n<td data-label=\"C11 corr\">305.80</td>\n<td data-label=\"C12 corr\">185.02</td>\n<td data-label=\"C44 corr\">155.06</td>\n<td data-label=\"MAE vs Tr2SCAN\">5.65</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Pd</td>\n<td data-label=\"α\">1.317</td>\n<td data-label=\"C11 corr\">223.86</td>\n<td data-label=\"C12 corr\">163.39</td>\n<td data-label=\"C44 corr\">90.61</td>\n<td data-label=\"MAE vs Tr2SCAN\">7.80</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Pt</td>\n<td data-label=\"α\">1.221</td>\n<td data-label=\"C11 corr\">314.78</td>\n<td data-label=\"C12 corr\">224.15</td>\n<td data-label=\"C44 corr\">86.84</td>\n<td data-label=\"MAE vs Tr2SCAN\">14.81</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Sr</td>\n<td data-label=\"α\">1.322</td>\n<td data-label=\"C11 corr\">21.29</td>\n<td data-label=\"C12 corr\">10.36</td>\n<td data-label=\"C44 corr\">11.93</td>\n<td data-label=\"MAE vs Tr2SCAN\">2.26</td>\n</tr>\n<tr>\n<td data-label=\"Element\">Ta</td>\n<td data-label=\"α\">1.299</td>\n<td data-label=\"C11 corr\">257.67</td>\n<td data-label=\"C12 corr\">164.71</td>\n<td data-label=\"C44 corr\">53.20</td>\n<td data-label=\"MAE vs Tr2SCAN\">15.57</td>\n</tr>\n<tr>\n<td data-label=\"Element\">V</td>\n<td data-label=\"α\">1.345</td>\n<td data-label=\"C11 corr\">335.95</td>\n<td data-label=\"C12 corr\">134.43</td>\n<td data-label=\"C44 corr\">16.88</td>\n<td data-label=\"MAE vs Tr2SCAN\">15.14</td>\n</tr>\n<tr>\n<td data-label=\"Element\">W</td>\n<td data-label=\"α\">1.259</td>\n<td data-label=\"C11 corr\">542.70</td>\n<td data-label=\"C12 corr\">207.81</td>\n<td data-label=\"C44 corr\">161.23</td>\n<td data-label=\"MAE vs Tr2SCAN\">9.94</td>\n</tr>\n</tbody></table></div><h2 id=\"recommended-v0-2-operator\">Recommended v0.2 Operator</h2><p><strong>Adopt <code>scalar-bulk</code> as the v0.2 operator on the Tr2SCAN-corrected target.</strong></p>\n<ul>\n<li>Mean MAE vs Tr2SCAN: <strong>14.13 GPa</strong>; median <strong>11.16 GPa</strong>.</li>\n<li>It is the best operator that does not require an oracle or multi-model ensemble.</li>\n<li>Cost is identical to a raw 1×1×1 run: <strong>0.0134 core-hours</strong> (2.70× cheaper than the ensemble, 3.86× cheaper than the 3×3×3 reference).</li>\n<li>Cell-size independence is confirmed on the 3×3×3 grid (see below).</li>\n<li>Seed variance is negligible on the Ca/Cu/Fe/Cr subset (see below).</li>\n<li>Future class-aware operators (e.g., class-mean or kNN-bias) may improve robustness when the calibration set is small or bonding-class labels are uncertain, but they have not yet been benchmarked on this dataset.</li>\n</ul>\n<h2 id=\"cell-size-independence-check\">Cell-size independence check</h2><p>To confirm that <code>scalar-bulk</code> is a cell-size-independent operator rather than an artifact of the small conventional cell, the LOO alphas were fit on the existing 3×3×3 raw predictions and applied to the 3×3×3 Tr2SCAN-corrected target.</p>\n<div class=\"table-wrap\"><table><thead><tr>\n<th>Metric</th>\n<th align=\"right\">scalar-bulk 1×1×1</th>\n<th align=\"right\">scalar-bulk 3×3×3</th>\n</tr>\n</thead><tbody><tr>\n<td data-label=\"Metric\">mean MAE vs Tr2SCAN_0K</td>\n<td align=\"right\" data-label=\"scalar-bulk 1×1×1\">14.13 GPa</td>\n<td align=\"right\" data-label=\"scalar-bulk 3×3×3\"><strong>14.14 GPa</strong></td>\n</tr>\n<tr>\n<td data-label=\"Metric\">median MAE vs Tr2SCAN_0K</td>\n<td align=\"right\" data-label=\"scalar-bulk 1×1×1\">11.16 GPa</td>\n<td align=\"right\" data-label=\"scalar-bulk 3×3×3\"><strong>11.20 GPa</strong></td>\n</tr>\n</tbody></table></div><p>The 1×1×1 and 3×3×3 means differ by 0.01 GPa and the medians by 0.04 GPa. The operator is therefore <strong>cell-size independent</strong>: it transfers from the cheap conventional cell to the expensive supercell reference with no measurable loss of accuracy.</p>\n<h2 id=\"variance-subset\">Variance subset</h2><p>A variance subset was run to check seed-to-seed stability: Ca, Cu, Fe, and Cr were each run with three different random seeds (12 cases total) using TensorNet/PBE 1×1×1.</p>\n<ul>\n<li>TensorNet/PBE 1×1×1 is <strong>deterministic across seeds</strong> for this benchmark.</li>\n<li>Per-element MAE standard deviations are ~0 GPa.</li>\n<li>The single-seed headline numbers are therefore seed-stable on the tested subset.</li>\n<li>Raw data: <code>/home/alex/Dev/lupine/lupine/data/mlip_elastic_benchmark_variance_subset/</code>.</li>\n</ul>\n<p>This removes a key uncertainty from the cost-accuracy ratios: the single-model cost is not being inflated by hidden seed variance on the elements most prone to numerical instability.</p>\n<h2 id=\"v0-3-directional-correction-scheme-and-feedback-loop\">v0.3 Directional correction scheme and feedback loop</h2><p>The failure of the global operator motivated a first-principles class-aware formalization rather than another ad-hoc fix.</p>\n<h3 id=\"lean-formalization\">Lean formalization</h3><p><code>OpenDistillationFactory/Materials/Distillation/DirectionalCorrectionScheme.lean</code> introduces a universal operator that assigns one correction direction to each class:</p>\n<ul>\n<li><code>DirectionalCorrectionScheme ι</code> stores a direction <code>d_c</code> for each class <code>c</code>.</li>\n<li><code>alpha c v = ⟨v, d_c⟩ / ⟨d_c, d_c⟩</code> is the exact scalar minimizer of the residual along <code>d_c</code>.</li>\n<li><code>correct c raw shift target = raw + shift + alpha c (target - (raw + shift)) • d_c</code>.</li>\n<li><code>isOutlier</code> flags samples whose corrected residual exceeds a class threshold.</li>\n<li><code>oracle_offset_zero_residual</code> proves that adding the exact residual eliminates error.</li>\n<li><code>class_aware_eq_global</code> proves that a class-aware scheme equals a global scheme when the class direction coincides with the shared direction.</li>\n</ul>\n<p>This is the abstraction above v0.2:</p>\n<ul>\n<li><code>scalar-bulk</code> → every class shares the bulk-modulus direction.</li>\n<li><code>global-loo-pca</code> → every class shares the first principal component.</li>\n<li>class-aware → each class gets its own direction.</li>\n<li>identity → all directions are zero.</li>\n</ul>\n<p>The file builds cleanly and is mirrored to <code>lupine-rhizo/lean-spec</code>.</p>\n<h3 id=\"python-feedback-loop\">Python feedback loop</h3><p><code>lupine/feedback.py</code> implements the operational counterpart:</p>\n<ul>\n<li><code>FeedbackLoop.fit(...)</code> builds per-class directions and alphas from calibration rows.</li>\n<li><code>evaluate(raw, shift, target, key)</code> applies the correction, measures the projection residual, and logs outliers.</li>\n<li><code>offset_mode</code> selects how outliers are offset:<ul>\n<li><code>none</code>: no extra correction.</li>\n<li><code>median</code>/<code>mean</code>: add the class median/mean projection residual from the outlier log.</li>\n<li><code>oracle</code>: add the exact projection residual, zeroing the directional error.</li>\n</ul>\n</li>\n<li><code>OutlierLog</code> persists outlier samples, thresholds, and class-level offset statistics.</li>\n</ul>\n<p>For the current MLIP benchmark this provides a principled path from v0.2 scalar-bulk to a fully class-aware operator: the same bulk-modulus direction can be reused while alphas and offsets are allowed to vary by bonding class.</p>\n<h3 id=\"feedbackloop-benchmark-results\">FeedbackLoop benchmark results</h3><p>The loop was benchmarked with leave-one-out element holdout on the 1×1×1 TensorNet/PBE data. Two alpha policies were tested:</p>\n<ul>\n<li><code>scalar_bulk</code>: reuse the v0.2 per-element LOO scalar, but apply it as a directional correction <code>raw + shift + α·d</code>.</li>\n<li><code>projection</code>: fit the exact directional projection scalar <code>α = ⟨target − raw − shift, d⟩ / ⟨d, d⟩</code> on the other 15 elements.</li>\n</ul>\n<p>Three offset modes were tested for each policy: <code>none</code>, <code>median</code>, and <code>oracle</code> (oracle uses the held-out sample&#39;s own residual and is shown only as an empirical ceiling).</p>\n<div class=\"table-wrap\"><table><thead><tr>\n<th>Operator</th>\n<th align=\"right\">mean MAE vs TPBE</th>\n<th align=\"right\">median MAE vs TPBE</th>\n<th align=\"right\">mean MAE vs Tr2SCAN</th>\n<th align=\"right\">median MAE vs Tr2SCAN</th>\n<th align=\"right\">outliers (Tr2SCAN)</th>\n</tr>\n</thead><tbody><tr>\n<td data-label=\"Operator\">scalar-bulk (v0.2)</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">19.17</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">19.01</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">14.13</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">11.16</td>\n<td align=\"right\" data-label=\"outliers (Tr2SCAN)\">—</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">feedback-scalar_bulk-offset-none</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">16.32</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">15.90</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">14.28</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">13.19</td>\n<td align=\"right\" data-label=\"outliers (Tr2SCAN)\">2/16</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">feedback-scalar_bulk-offset-median</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">16.45</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">15.67</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">14.41</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">13.19</td>\n<td align=\"right\" data-label=\"outliers (Tr2SCAN)\">2/16</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">feedback-scalar_bulk-offset-oracle</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">12.50</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">14.05</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">10.61</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">9.87</td>\n<td align=\"right\" data-label=\"outliers (Tr2SCAN)\">2/16</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">feedback-projection-offset-none</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">18.44</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">17.10</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">13.26</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">9.03</td>\n<td align=\"right\" data-label=\"outliers (Tr2SCAN)\">2/16</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">feedback-projection-offset-median</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">18.31</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">16.64</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">13.13</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">9.03</td>\n<td align=\"right\" data-label=\"outliers (Tr2SCAN)\">2/16</td>\n</tr>\n<tr>\n<td data-label=\"Operator\">feedback-projection-offset-oracle</td>\n<td align=\"right\" data-label=\"mean MAE vs TPBE\">14.37</td>\n<td align=\"right\" data-label=\"median MAE vs TPBE\">15.38</td>\n<td align=\"right\" data-label=\"mean MAE vs Tr2SCAN\">9.34</td>\n<td align=\"right\" data-label=\"median MAE vs Tr2SCAN\">8.01</td>\n<td align=\"right\" data-label=\"outliers (Tr2SCAN)\">2/16</td>\n</tr>\n</tbody></table></div><p>Key findings:</p>\n<ul>\n<li>The <strong>projection policy without offset already improves over v0.2 scalar-bulk</strong> on the Tr2SCAN target (mean MAE 13.26 vs 14.13 GPa; median 9.03 vs 11.16 GPa). It also improves the TPBE headline (18.44 vs 19.17 GPa), though it still does not beat raw or the ensemble on TPBE.</li>\n<li>The <strong>median offset gives a small additional improvement</strong> for the projection policy (13.13 vs 13.26 GPa mean Tr2SCAN), but not for the scalar-bulk policy. With only 15 training residuals per element, the median offset is noisy; it should become more effective as the calibration set grows.</li>\n<li>The <strong>oracle offset</strong> shows the empirical ceiling of the directional framework: 9.34 GPa mean vs Tr2SCAN. This is the target the operational offset mechanisms are trying to approximate without looking at the held-out target.</li>\n<li>Only <strong>2 of 16 elements</strong> are flagged as outliers under the 0.9-quantile threshold, so the bulk of the gain comes from the directional correction itself, not from outlier handling.</li>\n</ul>\n<p>The v0.2 <code>scalar-bulk</code> operator remains the recommended production choice because it is the simplest operator that already beats the ensemble on Tr2SCAN. The <code>feedback-projection</code> policy is the immediate v0.3 candidate: it is strictly better on Tr2SCAN and only slightly more complex.</p>\n<h2 id=\"implications-for-the-distillation-engine\">Implications for the Distillation Engine</h2><ol>\n<li><strong>The binding constraint must be class-aware.</strong> A single global binding axis overfits to the bulk-like component and mis-corrects classes with different error geometry. The distillation engine should partition the calibration set by bonding class (or by a learned similarity neighborhood) before extracting the bias direction.</li>\n<li><strong>Functional shift and bias are not interchangeable.</strong> The scalar bulk-modulus shift already removes much of the stiffness bias; the residual class-mean bias is smaller and more class-local. Future versions should fit the bias on the <em>residual after shift</em>, not on the raw error.</li>\n<li><strong>Headline framing must specify the target.</strong> Against the corrected target (Tr2SCAN_0K), <code>scalar-bulk</code> beats both raw and the 3-architecture ensemble at single-run cost. Against the PBE headline target (TPBE_0K), the ensemble remains the accuracy benchmark and no single-model operator beats raw. The cost-accuracy claim survives as a <strong>supercell-independence</strong> story (corrected 1×1×1 vs 3×3×3 reference), but the <strong>ensemble-beating</strong> claim is only supported on the Tr2SCAN target.</li>\n<li><strong>Variance and seed checks are now complete.</strong> The 4-element, 3-seed subset (Ca, Cu, Fe, Cr) shows TensorNet/PBE 1×1×1 is deterministic; MAE standard deviations are ~0 GPa. The single-seed headline numbers are therefore seed-stable on the tested subset.</li>\n<li><strong>Completed:</strong> the class-aware <code>FeedbackLoop</code> was benchmarked against v0.2 scalar-bulk. The projection policy improves Tr2SCAN mean MAE to <strong>13.26 GPa</strong> (from 14.13 GPa) and TPBE mean MAE to <strong>18.44 GPa</strong> (from 19.17 GPa), with 2/16 outliers flagged.</li>\n<li><strong>Next experiment:</strong> test the projection policy on the 3×3×3 grid and evaluate bonding-class-specific directions (rather than the shared bulk-modulus direction) to see if the TPBE-target gap can be closed further.</li>\n</ol>\n"}