feat(biochem): Фаза 3 — авто-балансировщик + энергодиаграммы реакций

BIO.balance(reactants, products) — балансировка уравнений через матрицу
«элемент×вещество» и дробный метод Гаусса (RREF) + НОК/НОД, целочисленные
коэффициенты. Проверено: 2H2+O2→2H2O, CH4+2O2→CO2+2H2O, 4Fe+3O2→2Fe2O3,
фотосинтез 6/6/1/6, Ca(OH)2+2HCl→CaCl2+2H2O (скобки), N2+3H2→2NH3.

biochem-reactions.html: в развёрнутой карточке —
- энергетический профиль (реагенты → переходное состояние → продукты) на
  canvas из energy_kj, экзо вниз/эндо вверх, стрелка ΔH, подпись типа;
- бейдж проверки баланса (BIO.balance по формулам молекул реакции).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 13:18:12 +03:00
parent 9a64bebb77
commit d46966c24d
2 changed files with 157 additions and 1 deletions
+71
View File
@@ -234,6 +234,76 @@
};
}
/* ── Балансировка уравнений реакций ───────────────────────────────────────
* Вход: reactants[], products[] — массивы строковых формул ("H2","O2",...).
* Метод: матрица «элемент × вещество» (реагенты +, продукты −), поиск
* целочисленного вектора в ядре через дробный метод Гаусса + НОК/НОД.
* Выход: { coefficients:[...], reactants:[...], products:[...] } или null.
*/
function _gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a || 1; }
function _lcm(a, b) { return Math.abs(a * b) / _gcd(a, b); }
// дроби как [num, den]
function _fr(n, d) { d = d || 1; if (d < 0) { n = -n; d = -d; } const g = _gcd(n, d) || 1; return [n / g, d / g]; }
function _frSub(a, b) { return _fr(a[0] * b[1] - b[0] * a[1], a[1] * b[1]); }
function _frMul(a, b) { return _fr(a[0] * b[0], a[1] * b[1]); }
function _frDiv(a, b) { return _fr(a[0] * b[1], a[1] * b[0]); }
function balance(reactants, products) {
const species = [...reactants, ...products];
if (species.length < 2) return null;
const nR = reactants.length;
const elemSet = new Set();
const comps = species.map(f => { const c = parseFormula(f); Object.keys(c).forEach(e => elemSet.add(e)); return c; });
const elements = [...elemSet];
const n = species.length;
// матрица элементов (дроби)
let M = elements.map(el => comps.map((c, i) => _fr((c[el] || 0) * (i < nR ? 1 : -1), 1)));
// RREF
const rows = M.length, cols = n;
let pivotCols = [];
let r = 0;
for (let c = 0; c < cols && r < rows; c++) {
let piv = -1;
for (let i = r; i < rows; i++) if (M[i][c][0] !== 0) { piv = i; break; }
if (piv < 0) continue;
[M[r], M[piv]] = [M[piv], M[r]];
const pv = M[r][c];
for (let j = 0; j < cols; j++) M[r][j] = _frDiv(M[r][j], pv);
for (let i = 0; i < rows; i++) {
if (i === r || M[i][c][0] === 0) continue;
const factor = M[i][c];
for (let j = 0; j < cols; j++) M[i][j] = _frSub(M[i][j], _frMul(factor, M[r][j]));
}
pivotCols.push(c);
r++;
}
// свободные столбцы (нет пивота) → ставим параметр 1
const pivotSet = new Set(pivotCols);
const free = [];
for (let c = 0; c < cols; c++) if (!pivotSet.has(c)) free.push(c);
if (free.length !== 1) return null; // нет однозначного баланса (или недоопределено)
const freeCol = free[0];
// x[freeCol] = 1; x[pivot] = -M[row][freeCol]
const x = new Array(cols).fill(null);
x[freeCol] = _fr(1, 1);
for (let i = 0; i < pivotCols.length; i++) {
const pc = pivotCols[i];
x[pc] = _fr(-M[i][freeCol][0], M[i][freeCol][1]);
}
// к целым: умножить на НОК знаменателей
let denLcm = 1;
for (const v of x) denLcm = _lcm(denLcm, v[1]);
let ints = x.map(v => v[0] * (denLcm / v[1]));
// знак: сделать положительными
if (ints.some(v => v < 0) && ints.every(v => v <= 0)) ints = ints.map(v => -v);
if (ints.some(v => v < 0)) return null; // несбалансируемо в положительных
// сократить на общий НОД
let g = 0; for (const v of ints) g = _gcd(g, v);
if (g > 1) ints = ints.map(v => v / g);
if (ints.some(v => v <= 0)) return null;
return { coefficients: ints, reactants: ints.slice(0, nR), products: ints.slice(nR) };
}
/* ── 2D-рендер (ball-and-stick для превью) ────────────────────────────────
* atoms: [{s,x,y}] bonds: [{f,t,o}] | [{from,to,order}]
* opts: { fit:true|false, padding, bg, lineColor, showSymbols, hideH, scale }
@@ -732,6 +802,7 @@
bF, bT, bO,
counts, hillFormula, molarMass, parseFormula, dbe,
partialCharges, dipole, polarity, massFractions, functionalGroups, analyze,
balance,
render2D, vsepr, render3D, chargeColor,
safe, RING_TEMPLATES,
_hexRgb, _lighten, _darken,