import type { SimulatorPlugin } from './types' /** * Ideal Carnot machine between two reservoirs (2nd law). * * Physics (absolute temperatures only): * - Refrigerator: COP_R = Tc/(Th−Tc), W_min = Qc/COP_R, Qh = Qc+W * - Heat pump: COP_HP = Th/(Th−Tc) = COP_R+1, W_min = Qh/COP_HP * - Engine: η = 1−Tc/Th, W_out = η·Qh, Qc = Qh−W * * `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/(Th−Tc); 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/(Th−Tc), COP pompe à chaleur Th/(Th−Tc), rendement moteur η=1−Tc/Th, travail ou puissance minimal(e). 1er et 2e principes.', en: 'Carnot limits between two reservoirs: fridge COP Tc/(Th−Tc), heat-pump COP Th/(Th−Tc), engine η=1−Tc/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 }