Files
Momento/memento-note/lib/simulators/carnot-cycle.ts
Antigravity 69c99e4f4f feat: page interactive, démos Play/Step et simulateur Carnot
Ajoute le pipeline PageSpec (validation, rendu, publication /p/{slug}),
les démos TipTap /demo, et le simulateur Carnot (modes frigo/PAC/moteur,
énergie kJ vs puissance W, unités K/°C/°F) avec correctifs d’équations KaTeX.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 17:51:43 +00:00

255 lines
6.3 KiB
TypeScript
Raw 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.
import type { SimulatorPlugin } from './types'
/**
* Ideal Carnot machine between two reservoirs (2nd law).
*
* Physics (absolute temperatures only):
* - Refrigerator: COP_R = Tc/(ThTc), W_min = Qc/COP_R, Qh = Qc+W
* - Heat pump: COP_HP = Th/(ThTc) = COP_R+1, W_min = Qh/COP_HP
* - Engine: η = 1Tc/Th, W_out = η·Qh, Qc = QhW
*
* `q_cold` is the primary load magnitude (kJ or W — same number; unit is UI-only).
* For fridge it is Qc; the view remaps for PAC / engine. Temperatures always Kelvin.
*
* Refs: COP_R Carnot = Tc/(ThTc); 1st law Qh=Qc+W; energy↔power interchangeable
* if all rates use the same time basis (see standard thermo textbooks / Carnot fridge calculators).
*/
export const carnotCycleSimulator: SimulatorPlugin = {
family: 'sim',
id: 'carnot-cycle',
title: {
fr: 'Machine de Carnot (frigo / PAC / moteur)',
en: 'Carnot machine (fridge / heat pump / engine)',
},
summary: {
fr: 'Limites de Carnot entre deux sources : COP frigo Tc/(ThTc), COP pompe à chaleur Th/(ThTc), rendement moteur η=1Tc/Th, travail ou puissance minimal(e). 1er et 2e principes.',
en: 'Carnot limits between two reservoirs: fridge COP Tc/(ThTc), heat-pump COP Th/(ThTc), engine η=1Tc/Th, minimum work or power. 1st and 2nd laws.',
},
keywords: [
'carnot',
'thermodynamique',
'thermodynamics',
'cop',
'réfrigérateur',
'frigo',
'refrigerator',
'pompe à chaleur',
'heat pump',
'moteur',
'engine',
'rendement',
'efficiency',
'watt',
'puissance',
'power',
'deuxième principe',
'second law',
],
params: [
{
id: 't_cold',
symbol: 'T_c',
label: { fr: 'Source froide', en: 'Cold reservoir' },
min: 200,
max: 320,
step: 1,
defaultValue: 260,
unit: 'K',
intent: 'cache',
},
{
id: 't_hot',
symbol: 'T_h',
label: { fr: 'Source chaude', en: 'Hot reservoir' },
min: 273,
max: 400,
step: 1,
defaultValue: 300,
unit: 'K',
intent: 'warning',
},
{
id: 'q_cold',
symbol: 'Q_c',
label: { fr: 'Charge (Qc frigo)', en: 'Load (fridge Qc)' },
min: 10,
max: 500,
step: 5,
defaultValue: 100,
unit: 'kJ',
intent: 'flow',
},
],
outputs: [
{
id: 'cop_fridge',
symbol: '\\mathrm{COP}_{R}',
label: { fr: 'COP réfrigérateur', en: 'Fridge COP' },
intent: 'output',
digits: 2,
},
{
id: 'cop_hp',
symbol: '\\mathrm{COP}_{HP}',
label: { fr: 'COP pompe à chaleur', en: 'Heat-pump COP' },
intent: 'output',
digits: 2,
},
{
id: 'eta',
symbol: '\\eta',
label: { fr: 'Rendement moteur', en: 'Engine efficiency' },
unit: '%',
intent: 'highlight',
digits: 1,
},
{
id: 'w_min',
symbol: 'W',
label: { fr: 'Travail (énergie)', en: 'Work (energy)' },
unit: 'kJ',
intent: 'compute',
digits: 1,
},
{
id: 'q_hot',
symbol: 'Q_h',
label: { fr: 'Chaleur côté chaud', en: 'Hot-side heat' },
unit: 'kJ',
intent: 'flow',
digits: 1,
},
],
compute(env) {
const tc = env.t_cold
const th = env.t_hot
const qc = env.q_cold
if (!(th > tc) || !(qc > 0)) {
return {
cop_fridge: NaN,
cop_hp: NaN,
eta: NaN,
w_min: NaN,
q_hot: NaN,
}
}
const copFridge = tc / (th - tc)
const copHp = th / (th - tc)
const eta = 1 - tc / th
const wMin = qc / copFridge
return {
cop_fridge: copFridge,
cop_hp: copHp,
eta: eta * 100,
w_min: wMin,
q_hot: qc + wMin,
}
},
}
/** Operating mode for the interactive view (UI-only; physics shared). */
export type CarnotMode = 'fridge' | 'heat_pump' | 'engine'
/** Energy (kJ) vs power (W) — same ratios; only unit labels change. */
export type CarnotQuantity = 'energy' | 'power'
export type CarnotPhysics = {
ok: boolean
tc: number
th: number
copR: number
copHP: number
eta: number
/** Heat exchanged with cold reservoir (magnitude > 0). */
qc: number
/** Heat exchanged with hot reservoir (magnitude > 0). */
qh: number
/** Work magnitude > 0 (input for fridge/PAC, output for engine). */
w: number
/** Reversible check: Qc/Tc ≈ Qh/Th */
entropyOk: boolean
}
/**
* Resolve magnitudes for the selected mode.
* `load` is the primary useful quantity:
* - fridge: Qc extracted from cold
* - heat_pump: Qh delivered to hot
* - engine: Qh absorbed from hot
*/
export function resolveCarnotPhysics(
tc: number,
th: number,
load: number,
mode: CarnotMode
): CarnotPhysics {
if (!(th > tc) || !(load > 0) || !Number.isFinite(tc) || !Number.isFinite(th)) {
return {
ok: false,
tc,
th,
copR: NaN,
copHP: NaN,
eta: NaN,
qc: NaN,
qh: NaN,
w: NaN,
entropyOk: false,
}
}
const copR = tc / (th - tc)
const copHP = th / (th - tc)
const eta = 1 - tc / th
let qc: number
let qh: number
let w: number
if (mode === 'fridge') {
qc = load
w = qc / copR
qh = qc + w
} else if (mode === 'heat_pump') {
qh = load
w = qh / copHP
qc = qh - w
} else {
qh = load
w = eta * qh
qc = qh - w
}
const ratioC = qc / tc
const ratioH = qh / th
const entropyOk =
Number.isFinite(ratioC) &&
Number.isFinite(ratioH) &&
Math.abs(ratioC - ratioH) / Math.max(ratioC, ratioH, 1e-9) < 1e-6
return { ok: true, tc, th, copR, copHP, eta, qc, qh, w, entropyOk }
}
/** Convert fridge-stored Qc load ↔ display load for other modes (fixture-compatible). */
export function fridgeLoadFromModeLoad(
tc: number,
th: number,
modeLoad: number,
mode: CarnotMode
): number {
if (!(th > tc) || !(modeLoad > 0)) return modeLoad
if (mode === 'fridge') return modeLoad
if (mode === 'heat_pump') return modeLoad * (tc / th) // Qc = Qh · Tc/Th
return modeLoad * (tc / th) // engine: Qc = Qh · (1η) = Qh · Tc/Th
}
export function modeLoadFromFridgeLoad(
tc: number,
th: number,
fridgeQc: number,
mode: CarnotMode
): number {
if (!(th > tc) || !(fridgeQc > 0)) return fridgeQc
if (mode === 'fridge') return fridgeQc
if (mode === 'heat_pump') return fridgeQc * (th / tc) // Qh = Qc · Th/Tc
return fridgeQc * (th / tc) // engine Qh = Qc / (1η) = Qc · Th/Tc
}