Entropyk/demo/inverse_control_template.html

747 lines
34 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Entropyk — Contrôle Inverse One-Shot</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
:root {
--bg: #0d1117;
--surface: #161b22;
--border: #30363d;
--text: #e6edf3;
--dim: #7d8590;
--cyan: #39d0d8;
--cyan-bg: rgba(57, 208, 216, .06);
--green: #3fb950;
--green-bg: rgba(63, 185, 80, .06);
--orange: #f0883e;
--orange-bg: rgba(240, 136, 62, .06);
--blue: #58a6ff;
--blue-bg: rgba(88, 166, 255, .06);
--purple: #bc8cff;
--purple-bg: rgba(188, 140, 255, .06);
--red: #f85149;
--yellow: #e3b341;
--pink: #ff7eb6;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Inter', sans-serif;
min-height: 100vh;
padding: 40px 20px 80px;
display: flex;
flex-direction: column;
align-items: center;
}
h1 {
font-size: 1.5rem;
font-weight: 700;
color: var(--cyan);
margin-bottom: 4px;
letter-spacing: -.5px;
}
.sub { font-size: .82rem; color: var(--dim); margin-bottom: 40px; }
.page {
width: 100%;
max-width: 1080px;
display: flex;
flex-direction: column;
gap: 28px;
}
section { width: 100%; }
section h2 {
font-size: .75rem;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--dim);
font-weight: 600;
border-bottom: 1px solid var(--border);
padding-bottom: 8px;
margin-bottom: 16px;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 18px;
justify-content: center;
margin-bottom: 36px;
}
.leg {
display: flex;
align-items: center;
gap: 7px;
font-size: .74rem;
color: var(--dim);
}
.leg-dot {
width: 11px;
height: 11px;
border-radius: 3px;
border: 2px solid;
flex-shrink: 0;
}
.flow-diagram {
border: 2px solid var(--cyan);
border-radius: 14px;
background: var(--cyan-bg);
padding: 28px 24px 32px;
position: relative;
}
.flow-diagram > .blabel {
position: absolute;
top: -13px;
left: 22px;
background: var(--bg);
padding: 0 10px;
font-size: .72rem;
font-weight: 600;
color: var(--cyan);
letter-spacing: .07em;
text-transform: uppercase;
}
.node {
border: 2px solid var(--border);
border-radius: 10px;
background: var(--surface);
padding: 12px 16px;
text-align: center;
min-width: 90px;
cursor: default;
transition: transform .18s, box-shadow .18s;
}
.node:hover {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
}
.node .nm { font-size: .8rem; font-weight: 600; }
.node .ty { font-size: .62rem; color: var(--dim); margin-top: 2px; font-family: 'JetBrains Mono', monospace; }
.node .vals { font-size: .6rem; font-family: 'JetBrains Mono', monospace; color: var(--cyan); margin-top: 4px; line-height: 1.5; }
.node.constraint { border-color: var(--purple); background: var(--purple-bg); }
.node.control { border-color: var(--orange); background: var(--orange-bg); }
.node.component { border-color: var(--blue); background: var(--blue-bg); }
.node.result { border-color: var(--green); background: var(--green-bg); }
.arr {
display: flex;
flex-direction: column;
align-items: center;
min-width: 48px;
}
.arr-line {
width: 100%;
height: 2px;
background: linear-gradient(90deg, var(--border), var(--dim));
position: relative;
}
.arr-line::after {
content: '';
position: absolute;
right: -1px;
top: -4px;
border: 5px solid transparent;
border-left-color: var(--dim);
}
.arr-lbl {
font-size: .58rem;
color: var(--dim);
font-family: 'JetBrains Mono', monospace;
margin-top: 3px;
white-space: nowrap;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px;
}
.icard {
border: 1px solid var(--border);
border-radius: 10px;
background: var(--surface);
padding: 14px;
}
.icard h3 {
font-size: .7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--dim);
border-bottom: 1px solid var(--border);
padding-bottom: 7px;
margin-bottom: 9px;
}
.irow {
display: flex;
justify-content: space-between;
align-items: center;
font-size: .75rem;
padding: 3px 0;
}
.irow .k { color: var(--dim); }
.irow .v { font-family: 'JetBrains Mono', monospace; font-size: .7rem; }
.v.g { color: var(--green); }
.v.c { color: var(--cyan); }
.v.o { color: var(--orange); }
.v.b { color: var(--blue); }
.v.p { color: var(--purple); }
.v.r { color: var(--red); }
.eq-box {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 18px 20px;
}
.eq-table {
width: 100%;
border-collapse: collapse;
font-size: .75rem;
}
.eq-table th {
text-align: left;
font-weight: 600;
font-size: .68rem;
color: var(--dim);
text-transform: uppercase;
letter-spacing: .07em;
padding: 0 8px 10px;
border-bottom: 1px solid var(--border);
}
.eq-table td {
padding: 5px 8px;
font-family: 'JetBrains Mono', monospace;
font-size: .7rem;
}
.eq-table tr:not(:last-child) td { border-bottom: 1px solid rgba(48, 54, 61, .5); }
.eq-table .cat { color: var(--dim); font-family: 'Inter', sans-serif; font-size: .72rem; }
.eq-table .formula { color: var(--cyan); }
.eq-table .result { color: var(--green); text-align: right; }
pre {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 18px;
font-family: 'JetBrains Mono', monospace;
font-size: .72rem;
line-height: 1.7;
overflow-x: auto;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
border-radius: 100px;
font-size: .68rem;
font-weight: 600;
}
.status-badge.ok {
background: var(--green-bg);
color: var(--green);
border: 1px solid rgba(63, 185, 80, .3);
}
.status-badge.warn {
background: rgba(227, 179, 65, .1);
color: var(--yellow);
border: 1px solid rgba(227, 179, 65, .3);
}
.status-badge.error {
background: rgba(248, 81, 73, .1);
color: var(--red);
border: 1px solid rgba(248, 81, 73, .3);
}
.dof-box {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
padding: 20px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
}
.dof-side {
text-align: center;
min-width: 150px;
}
.dof-side .title {
font-size: .7rem;
color: var(--dim);
text-transform: uppercase;
letter-spacing: .08em;
margin-bottom: 8px;
}
.dof-side .count {
font-size: 2rem;
font-weight: 700;
font-family: 'JetBrains Mono', monospace;
}
.dof-side .detail {
font-size: .65rem;
color: var(--dim);
font-family: 'JetBrains Mono', monospace;
margin-top: 4px;
}
.dof-equals { font-size: 1.5rem; color: var(--green); }
.mapping-box {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
padding: 24px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
flex-wrap: wrap;
}
.mapping-arrow {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.mapping-arrow svg { width: 80px; height: 40px; }
.mapping-arrow .lbl { font-size: .6rem; color: var(--cyan); font-family: 'JetBrains Mono', monospace; }
.conv-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
gap: 6px;
}
.conv-cell {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 8px;
text-align: center;
}
.conv-cell .iter { font-size: .55rem; color: var(--dim); }
.conv-cell .val { font-size: .7rem; font-family: 'JetBrains Mono', monospace; color: var(--cyan); margin-top: 2px; }
.conv-cell.active { border-color: var(--green); background: var(--green-bg); }
.conv-cell.active .val { color: var(--green); }
[data-tip] { position: relative; }
[data-tip]:hover::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: #1c2128;
border: 1px solid var(--border);
color: var(--text);
font-size: .68rem;
font-family: 'JetBrains Mono', monospace;
padding: 6px 10px;
border-radius: 6px;
white-space: pre;
z-index: 100;
pointer-events: none;
box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
}
</style>
</head>
<body>
<h1>🎯 Entropyk — Contrôle Inverse One-Shot</h1>
<p class="sub">Superheat control avec valve d'expansion — Story 5.3: Residual Embedding</p>
<div class="legend">
<div class="leg"><div class="leg-dot" style="border-color:var(--purple)"></div>Constraint</div>
<div class="leg"><div class="leg-dot" style="border-color:var(--orange)"></div>BoundedVariable</div>
<div class="leg"><div class="leg-dot" style="border-color:var(--cyan)"></div>Lien One-Shot</div>
<div class="leg"><div class="leg-dot" style="border-color:var(--green)"></div>Résultat</div>
</div>
<div class="page">
<!-- ═══ CONCEPT ═══ -->
<section>
<h2>Concept — One-Shot Inverse Control</h2>
<div class="flow-diagram">
<span class="blabel">FR24: Inverse Control solved simultaneously with cycle equations</span>
<div style="display:flex;align-items:stretch;gap:24px;justify-content:center;flex-wrap:wrap;">
<div style="flex:1;min-width:280px;background:rgba(248,81,73,.05);border:1px solid rgba(248,81,73,.2);border-radius:10px;padding:16px;">
<div style="font-size:.75rem;font-weight:600;color:var(--red);margin-bottom:12px;text-transform:uppercase;letter-spacing:.08em;">
❌ Approche Traditionnelle
</div>
<div style="font-size:.72rem;color:var(--dim);line-height:1.8;">
<div>1. Fixer valve → Simuler</div>
<div>2. Mesurer superheat</div>
<div>3. Ajuster valve</div>
<div>4. <span style="color:var(--red)">Répéter (optimisation externe)</span></div>
<div style="border-top:1px solid var(--border);padding-top:8px;margin-top:8px;">
<span style="color:var(--red)">→ Lent, coûteux, non garanti</span>
</div>
</div>
</div>
<div style="flex:1;min-width:280px;background:var(--green-bg);border:1px solid rgba(63,185,80,.3);border-radius:10px;padding:16px;">
<div style="font-size:.75rem;font-weight:600;color:var(--green);margin-bottom:12px;text-transform:uppercase;letter-spacing:.08em;">
✅ Approche One-Shot (Entropyk)
</div>
<div style="font-size:.72rem;color:var(--dim);line-height:1.8;">
<div>1. Définir contrainte: superheat = 5K</div>
<div>2. Lier à variable: valve position</div>
<div>3. <span style="color:var(--cyan)">Valve devient inconnue du solveur</span></div>
<div>4. <span style="color:var(--green)">Résolution simultanée (1 appel)</span></div>
<div style="border-top:1px solid rgba(63,185,80,.3);padding-top:8px;margin-top:8px;">
<span style="color:var(--green)">→ Rapide, garanti, élégant</span>
</div>
</div>
</div>
</div>
<div style="margin-top:20px;padding:16px;background:var(--bg);border-radius:8px;text-align:center;">
<div style="font-size:.7rem;color:var(--dim);margin-bottom:8px;">Vecteur de résidus étendu</div>
<div style="font-family:'JetBrains Mono',monospace;font-size:.85rem;color:var(--cyan);">
r<sub>total</sub> = [ r<sub>cycle</sub>, r<sub>constraint</sub> ]<sup>T</sup> = 0
</div>
<div style="font-size:.65rem;color:var(--dim);margin-top:8px;">
r<sub>constraint</sub> = superheat<sub>mesuré</sub> superheat<sub>cible</sub>
</div>
</div>
</div>
</section>
<!-- ═══ MAPPING ═══ -->
<section>
<h2>Mapping — Contrainte → Variable de Contrôle</h2>
<div class="mapping-box">
<div class="node constraint" data-tip="Constraint&#10;ID: superheat_control&#10;Output: Superheat(evaporator)&#10;Target: 5.0 K">
<div class="nm">📐 Constraint</div>
<div class="ty">superheat_control</div>
<div class="vals">target: 5.0 K</div>
</div>
<div class="mapping-arrow">
<svg viewBox="0 0 80 40">
<defs>
<linearGradient id="linkGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#bc8cff;stop-opacity:1" />
<stop offset="100%" style="stop-color:#f0883e;stop-opacity:1" />
</linearGradient>
</defs>
<line x1="0" y1="20" x2="70" y2="20" stroke="url(#linkGrad)" stroke-width="2" stroke-dasharray="6,3"/>
<polygon points="70,15 80,20 70,25" fill="#f0883e"/>
</svg>
<div class="lbl">link_constraint_to_control()</div>
</div>
<div class="node control" data-tip="BoundedVariable&#10;ID: valve_position&#10;Value: 0.5 (50%)&#10;Bounds: [0.1, 1.0]">
<div class="nm">🎚 BoundedVar</div>
<div class="ty">valve_position</div>
<div class="vals">[0.1, 1.0]</div>
</div>
<div class="mapping-arrow">
<svg viewBox="0 0 60 40">
<line x1="0" y1="20" x2="50" y2="20" stroke="#7d8590" stroke-width="2"/>
<polygon points="50,15 60,20 50,25" fill="#7d8590"/>
</svg>
<div class="lbl">state[idx]</div>
</div>
<div class="node result" data-tip="Résultat One-Shot&#10;7 itérations&#10;Convergence: 1e-8">
<div class="nm">✓ Résolu</div>
<div class="ty">valve = 38%</div>
<div class="vals">SH = 5.02 K</div>
</div>
</div>
</section>
<!-- ═══ DOF ═══ -->
<section>
<h2>Validation des Degrés de Liberté (DoF)</h2>
<div class="dof-box">
<div class="dof-side">
<div class="title">Équations</div>
<div class="count" style="color:var(--blue)">9</div>
<div class="detail">composants: 8<br>contraintes: +1</div>
</div>
<div class="dof-equals">=</div>
<div class="dof-side">
<div class="title">Inconnues</div>
<div class="count" style="color:var(--orange)">9</div>
<div class="detail">états bords: 8<br>contrôles: +1</div>
</div>
</div>
<div style="text-align:center;margin-top:12px;">
<span class="status-badge ok">✓ Système bien posé — validate_inverse_control_dof() = Ok</span>
</div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:16px;">
<div style="background:var(--green-bg);border:1px solid rgba(63,185,80,.3);border-radius:8px;padding:12px;text-align:center;">
<div style="font-size:.7rem;color:var(--green);font-weight:600;">Équilibré</div>
<div style="font-size:.6rem;color:var(--dim);font-family:'JetBrains Mono',monospace;margin-top:4px;">n_eqs == n_unknowns</div>
<div class="status-badge ok" style="margin-top:8px;">Résolvable</div>
</div>
<div style="background:rgba(248,81,73,.05);border:1px solid rgba(248,81,73,.2);border-radius:8px;padding:12px;text-align:center;">
<div style="font-size:.7rem;color:var(--red);font-weight:600;">Sur-contraint</div>
<div style="font-size:.6rem;color:var(--dim);font-family:'JetBrains Mono',monospace;margin-top:4px;">n_eqs > n_unknowns</div>
<div class="status-badge error" style="margin-top:8px;">Erreur</div>
</div>
<div style="background:rgba(227,179,65,.1);border:1px solid rgba(227,179,65,.3);border-radius:8px;padding:12px;text-align:center;">
<div style="font-size:.7rem;color:var(--yellow);font-weight:600;">Sous-contraint</div>
<div style="font-size:.6rem;color:var(--dim);font-family:'JetBrains Mono',monospace;margin-top:4px;">n_eqs &lt; n_unknowns</div>
<div class="status-badge warn" style="margin-top:8px;">Warning</div>
</div>
</div>
</section>
<!-- ═══ ÉQUATIONS ═══ -->
<section>
<h2>Équations du système étendu</h2>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div class="eq-box">
<div style="font-size:.8rem;font-weight:600;color:var(--blue);margin-bottom:12px">📦 Équations du cycle (8 éq.)</div>
<table class="eq-table">
<thead><tr><th>Composant</th><th>Équation</th><th>État</th></tr></thead>
<tbody>
<tr><td class="cat">Compresseur</td><td class="formula">r₁(P,h) = 0</td><td class="result">2 éq.</td></tr>
<tr><td class="cat">Condenseur</td><td class="formula">r₂(P,h) = 0</td><td class="result">2 éq.</td></tr>
<tr><td class="cat">EXV</td><td class="formula">r₃(P,h) = 0</td><td class="result">2 éq.</td></tr>
<tr><td class="cat">Évaporateur</td><td class="formula">r₄(P,h) = 0</td><td class="result">2 éq.</td></tr>
</tbody>
</table>
</div>
<div class="eq-box">
<div style="font-size:.8rem;font-weight:600;color:var(--purple);margin-bottom:12px">📐 Équations de contrainte (+1 éq.)</div>
<table class="eq-table">
<thead><tr><th>Type</th><th>Équation</th><th>Valeur</th></tr></thead>
<tbody>
<tr><td class="cat">Superheat</td><td class="formula">r_c = SH 5.0</td><td class="result">5.02 5.0</td></tr>
<tr><td class="cat">Jacobian</td><td class="formula">∂r_c/∂valve</td><td class="result">≈ 1.0</td></tr>
<tr style="background:var(--green-bg)">
<td class="cat">State idx</td><td class="formula">2·edges + i</td><td class="result" style="color:var(--green)">idx = 8</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- ═══ CONVERGENCE ═══ -->
<section>
<h2>Convergence — Newton-Raphson</h2>
<div class="info-grid">
<div class="icard">
<h3>Superheat (K)</h3>
<div class="conv-grid">
<div class="conv-cell"><div class="iter">i=0</div><div class="val">2.30</div></div>
<div class="conv-cell"><div class="iter">i=1</div><div class="val">3.12</div></div>
<div class="conv-cell"><div class="iter">i=2</div><div class="val">3.89</div></div>
<div class="conv-cell"><div class="iter">i=3</div><div class="val">4.41</div></div>
<div class="conv-cell"><div class="iter">i=4</div><div class="val">4.73</div></div>
<div class="conv-cell"><div class="iter">i=5</div><div class="val">4.91</div></div>
<div class="conv-cell"><div class="iter">i=6</div><div class="val">5.01</div></div>
<div class="conv-cell active"><div class="iter">i=7</div><div class="val">5.02</div></div>
</div>
</div>
<div class="icard">
<h3>Valve Position (%)</h3>
<div class="conv-grid">
<div class="conv-cell"><div class="iter">i=0</div><div class="val">50.0</div></div>
<div class="conv-cell"><div class="iter">i=1</div><div class="val">46.2</div></div>
<div class="conv-cell"><div class="iter">i=2</div><div class="val">43.5</div></div>
<div class="conv-cell"><div class="iter">i=3</div><div class="val">41.3</div></div>
<div class="conv-cell"><div class="iter">i=4</div><div class="val">39.8</div></div>
<div class="conv-cell"><div class="iter">i=5</div><div class="val">38.9</div></div>
<div class="conv-cell"><div class="iter">i=6</div><div class="val">38.3</div></div>
<div class="conv-cell active"><div class="iter">i=7</div><div class="val">38.0</div></div>
</div>
</div>
</div>
</section>
<!-- ═══ VALEURS FINALES ═══ -->
<section>
<h2>Valeurs au point de fonctionnement</h2>
<div class="info-grid">
<div class="icard">
<h3>Contrainte Superheat</h3>
<div class="irow"><span class="k">ID</span><span class="v p">superheat_control</span></div>
<div class="irow"><span class="k">Output</span><span class="v">Superheat(evaporator)</span></div>
<div class="irow"><span class="k">Target</span><span class="v c">5.0 K</span></div>
<div class="irow"><span class="k">Mesuré</span><span class="v g">5.02 K</span></div>
<div class="irow"><span class="k">Résidu</span><span class="v g">0.02 K</span></div>
<div class="irow"><span class="k">Satisfait</span><span class="v g"></span></div>
</div>
<div class="icard">
<h3>Variable de Contrôle</h3>
<div class="irow"><span class="k">ID</span><span class="v o">valve_position</span></div>
<div class="irow"><span class="k">Initial</span><span class="v">50.0%</span></div>
<div class="irow"><span class="k">Final</span><span class="v g">38.0%</span></div>
<div class="irow"><span class="k">Bounds</span><span class="v">[10%, 100%]</span></div>
<div class="irow"><span class="k">Saturé</span><span class="v c">Non</span></div>
<div class="irow"><span class="k">State idx</span><span class="v b">8</span></div>
</div>
<div class="icard">
<h3>Solveur Newton-Raphson</h3>
<div class="irow"><span class="k">Itérations</span><span class="v c">7</span></div>
<div class="irow"><span class="k">Tolérance</span><span class="v">1e-8</span></div>
<div class="irow"><span class="k">‖r‖ final</span><span class="v g">3.2e-9</span></div>
<div class="irow"><span class="k">Méthode</span><span class="v">One-Shot</span></div>
<div class="irow"><span class="k">Temps</span><span class="v g">12 ms</span></div>
</div>
<div class="icard">
<h3>DoF Validation</h3>
<div class="irow"><span class="k">Edges</span><span class="v">4 (×2 = 8)</span></div>
<div class="irow"><span class="k">Controls</span><span class="v o">+1</span></div>
<div class="irow"><span class="k">Total unknowns</span><span class="v b">9</span></div>
<div class="irow"><span class="k">Components</span><span class="v">4 (×2 = 8)</span></div>
<div class="irow"><span class="k">Constraints</span><span class="v p">+1</span></div>
<div class="irow"><span class="k">Total equations</span><span class="v b">9</span></div>
<div class="irow"><span class="k">Balance</span><span class="v g">9 = 9 ✓</span></div>
</div>
</div>
</section>
<!-- ═══ API RUST ═══ -->
<section>
<h2>API Rust — Utilisation</h2>
<pre style="color:#d4d4d4"><span style="color:#6a9955">// ══════════════════════════════════════════════════════════════════════</span>
<span style="color:#6a9955">// Story 5.3: Residual Embedding for Inverse Control</span>
<span style="color:#6a9955">// ══════════════════════════════════════════════════════════════════════</span>
<span style="color:#569cd6">use</span> entropyk_solver::inverse::{
<span style="color:#4ec9b0">Constraint</span>, <span style="color:#4ec9b0">ConstraintId</span>, <span style="color:#4ec9b0">ComponentOutput</span>,
<span style="color:#4ec9b0">BoundedVariable</span>, <span style="color:#4ec9b0">BoundedVariableId</span>,
};
<span style="color:#6a9955">// 1. Définir la contrainte: superheat = 5K</span>
<span style="color:#569cd6">let</span> constraint = <span style="color:#4ec9b0">Constraint</span>::<span style="color:#dcdcaa">new</span>(
<span style="color:#4ec9b0">ConstraintId</span>::<span style="color:#dcdcaa">new</span>(<span style="color:#ce9178">"superheat_control"</span>),
<span style="color:#4ec9b0">ComponentOutput</span>::<span style="color:#4ec9b0">Superheat</span> { component_id: <span style="color:#ce9178">"evaporator"</span>.<span style="color:#dcdcaa">into</span>() },
<span style="color:#b5cea8">5.0</span>, <span style="color:#6a9955">// target: 5 Kelvin</span>
);
system.<span style="color:#dcdcaa">add_constraint</span>(constraint)?;
<span style="color:#6a9955">// 2. Définir la variable de contrôle bornée</span>
<span style="color:#569cd6">let</span> control = <span style="color:#4ec9b0">BoundedVariable</span>::<span style="color:#dcdcaa">new</span>(
<span style="color:#4ec9b0">BoundedVariableId</span>::<span style="color:#dcdcaa">new</span>(<span style="color:#ce9178">"valve_position"</span>),
<span style="color:#b5cea8">0.5</span>, <span style="color:#b5cea8">0.1</span>, <span style="color:#b5cea8">1.0</span>, <span style="color:#6a9955">// init, min, max</span>
)?;
system.<span style="color:#dcdcaa">add_bounded_variable</span>(control)?;
<span style="color:#6a9955">// 3. Lier contrainte → contrôle (One-Shot!)</span>
system.<span style="color:#dcdcaa">link_constraint_to_control</span>(
<span style="color:#569cd6">&amp;</span><span style="color:#4ec9b0">ConstraintId</span>::<span style="color:#dcdcaa">new</span>(<span style="color:#ce9178">"superheat_control"</span>),
<span style="color:#569cd6">&amp;</span><span style="color:#4ec9b0">BoundedVariableId</span>::<span style="color:#dcdcaa">new</span>(<span style="color:#ce9178">"valve_position"</span>),
)?;
<span style="color:#6a9955">// 4. Valider DoF + Finalize</span>
system.<span style="color:#dcdcaa">validate_inverse_control_dof</span>()?;
system.<span style="color:#dcdcaa">finalize</span>()?;
<span style="color:#6a9955">// 5. Résoudre (One-Shot)</span>
<span style="color:#569cd6">let</span> result = <span style="color:#4ec9b0">NewtonRaphson</span>::<span style="color:#dcdcaa">new</span>().<span style="color:#dcdcaa">solve</span>(<span style="color:#569cd6">&amp;</span>system)?;
<span style="color:#6a9955">// 6. Résultat</span>
<span style="color:#569cd6">let</span> valve = system.<span style="color:#dcdcaa">get_bounded_variable</span>(<span style="color:#569cd6">&amp;</span><span style="color:#4ec9b0">BoundedVariableId</span>::<span style="color:#dcdcaa">new</span>(<span style="color:#ce9178">"valve_position"</span>)).<span style="color:#dcdcaa">unwrap</span>();
<span style="color:#dcdcaa">println!</span>(<span style="color:#ce9178">"Valve: {:.1}% SH: {:.2} K"</span>, valve.<span style="color:#dcdcaa">value</span>()<span style="color:#569cd6">*</span><span style="color:#b5cea8">100.0</span>, sh);</pre>
</section>
<!-- ═══ API MÉTHODES ═══ -->
<section>
<h2>API — Méthodes System</h2>
<div class="eq-box">
<table class="eq-table">
<thead><tr><th>Méthode</th><th>Description</th><th>Retour</th></tr></thead>
<tbody>
<tr><td class="formula">add_constraint(c)</td><td class="cat">Ajoute une contrainte</td><td class="result">Result&lt;(), ConstraintError&gt;</td></tr>
<tr><td class="formula">add_bounded_variable(v)</td><td class="cat">Ajoute variable bornée</td><td class="result">Result&lt;(), BoundedVariableError&gt;</td></tr>
<tr><td class="formula">link_constraint_to_control(cid, vid)</td><td class="cat">Lie contrainte → contrôle</td><td class="result">Result&lt;(), DoFError&gt;</td></tr>
<tr><td class="formula">unlink_constraint(cid)</td><td class="cat">Supprime le lien</td><td class="result">Option&lt;BoundedVariableId&gt;</td></tr>
<tr><td class="formula">validate_inverse_control_dof()</td><td class="cat">Valide éq == inconnues</td><td class="result">Result&lt;(), DoFError&gt;</td></tr>
<tr><td class="formula">control_variable_state_index(id)</td><td class="cat">Index vecteur d'état</td><td class="result">Option&lt;usize&gt;</td></tr>
<tr><td class="formula">full_state_vector_len()</td><td class="cat">Longueur totale</td><td class="result">usize</td></tr>
<tr><td class="formula">compute_constraint_residuals(...)</td><td class="cat">Calcule résidus contraintes</td><td class="result">usize</td></tr>
<tr><td class="formula">compute_inverse_control_jacobian(...)</td><td class="cat">Jacobian ∂r/∂control</td><td class="result">Vec&lt;(row,col,val)&gt;</td></tr>
</tbody>
</table>
</div>
</section>
</div>
<script>
document.querySelectorAll('.node').forEach(el => {
el.addEventListener('mouseenter', () => {
const c = getComputedStyle(el).borderColor;
el.style.boxShadow = '0 0 0 2px ' + c + ', 0 8px 28px rgba(0,0,0,.5)';
});
el.addEventListener('mouseleave', () => { el.style.boxShadow = ''; });
});
</script>
</body>
</html>