Skip to main content

Chapter 25
Work and Heat as Reciprocal Flows

Thermal and Entropic Flows
Author: Mark Anthony Higgins
© Mark Anthony Higgins 11 October 2025
Revised Date: 23 February 2026
Audio Overview
Flow Algebra
flow "Work and Heat — Reciprocal Flows" v1.0
# ---------- IO ----------
io.read:  null
io.write: null

# ---------- PARAMETERS ----------
params:
  units: "SI"                       # J, W, Pa, m³, K
  sign_convention:
    heat_in_positive: true          # δQ > 0 into system
    work_out_positive: true         # δW > 0 done by system
  near_equilibrium_assumption: true # enables Onsager reciprocity
  G_heat: 0.0..inf                  # thermal conductance [W/K]
  Z_mech: 0.0..inf                  # mechanical impedance proxy
  T_min:  0.1                       # K; guard against T→0
  IB: 0.0..1.0                      # boundary coherence index (1 = reversible/perfect)

# ---------- STATE ----------
state System:
  U:   real         # internal energy [J]
  S:   real         # entropy [J/K]
  T:   positive     # temperature [K]
  p:   real         # pressure [Pa]
  V:   positive     # volume [m^3]

state Reservoir:
  T_res: positive   # reservoir temperature [K]
  p_env: real       # environmental pressure [Pa]

state Ports:
  Qdot_in:   real   # heat power into system [W]
  Wdot_out:  real   # mechanical power out of system [W]
  Vdot:      real   # volume change rate [m^3/s]
  Fv:        real   # generic mechanical power term F·v [W]

# ---------- DRIVING FORCES & FLOWS (Near-Equilibrium Form) ----------
# Generalised forces X and fluxes J with cross-couplings (Onsager).
state LinearResponse:
  X_q:   (1/System.T - 1/Reservoir.T_res)        # thermal affinity
  X_w:   (System.p - Reservoir.p_env) / System.T # mech affinity scaled by T
  J_q:   Ports.Qdot_in                            # heat flux (power)
  J_w:   Ports.Wdot_out                           # work flux (power)

params L:                                         # phenomenological matrix
  Lqq: positive
  Lww: positive
  Lqw: real
  Lwq: real

invariants:
  # Onsager reciprocity under time-reversal symmetry and near-equilibrium:
  if params.near_equilibrium_assumption:
    L.Lqw = L.Lwq

# ---------- CORE INVARIANTS (First & Second Laws) ----------
invariants:
  # First Law (power form): dU/dt = Q̇_in − Ẇ_out
  ddt(System.U) = Ports.Qdot_in - Ports.Wdot_out

  # Work channel definitions (choose one depending on setup)
  Ports.Wdot_out = System.p * Ports.Vdot  or  Ports.Wdot_out = Ports.Fv

  # Second Law: entropy rate splits into flow + production (σ ≥ 0)
  ddt(System.S) = Ports.Qdot_in / System.T + sigma
  sigma >= 0

  # Temperature and volume safety
  System.T >= params.T_min
  System.V > 0

# ---------- RECIPROCAL COUPLING (Constitutive) ----------
transforms:
  # Linear Onsager pair (captures thermo-mechanical reciprocity)
  # [ J_q ]   [ Lqq  Lqw ] [ X_q ]
  # [ J_w ] = [ Lwq  Lww ] [ X_w ]
  linear_response:
    Ports.Qdot_in  = L.Lqq * LinearResponse.X_q + L.Lqw * LinearResponse.X_w
    Ports.Wdot_out = L.Lwq * LinearResponse.X_q + L.Lww * LinearResponse.X_w

  # Direct boundary laws (useful when not linearising):
  heat_exchange:
    Ports.Qdot_in = params.G_heat * (Reservoir.T_res - System.T)

  mech_exchange:
    Ports.Wdot_out = System.p * Ports.Vdot   # compress/expand work

# ---------- REVERSIBLE LIMIT & CARNOT BOUNDS ----------
transforms:
  reversible_limit (when params.IB ≈ 1 and sigma → 0):
    # dS = δQ_rev/T, path-dependent work but zero entropy production
    sigma = 0
    # Ideal engine/pump inequalities (not enforced equalities unless stated)
    eta_engine_max = 1 - (T_cold / T_hot)              # Carnot efficiency bound
    cop_pump_max   = T_hot / (T_hot - T_cold)          # heat pump COP bound
    cop_fridge_max = T_cold / (T_hot - T_cold)

# ---------- IRREVERSIBILITY & LOSS ----------
transforms:
  entropy_production:
    sigma = Ports.Qdot_in * (1/System.T - 1/Reservoir.T_res)
            + phi_mech
    # phi_mech captures mechanical dissipation (e.g., viscosity, friction)
    phi_mech >= 0

  coherence_loss_link:
    # Map boundary coherence to dissipation fraction (design heuristic)
    loss_fraction = (1 - params.IB)
    phi_mech >= loss_fraction * abs(Ports.Wdot_out)

# ---------- MONITORS ----------
monitors:
  energy_balance_residual:
    eps_U = ddt(System.U) - (Ports.Qdot_in - Ports.Wdot_out)
    assert abs(eps_U) <= 1e-9

  entropy_monitor:
    assert sigma >= 0
    S_non_decreasing_when_isolated:
      if Ports.Qdot_in == 0 and Ports.Wdot_out == 0:
        ddt(System.S) >= 0

  reciprocity_monitor:
    if params.near_equilibrium_assumption:
      assert L.Lqw == L.Lwq

  performance:
    eta_engine = Ports.Wdot_out / max(Ports.Qdot_in, 1e-12)    # heat engine
    cop_pump   = Ports.Qdot_in / max(Ports.Wdot_out, 1e-12)    # heat pump mode (W drives Q in)
    within_carnot:
      if defined(T_hot) and defined(T_cold):
        assert eta_engine <= (1 - T_cold/T_hot) + 1e-9

# ---------- SCENES / EXAMPLES ----------
scenes:
  isothermal_expansion_engine:
    given:
      Reservoir.T_res = T_hot
      System.T        = T_hot
      System.p > Reservoir.p_env
      params.IB = 0.98
    expect:
      sigma ≈ 0
      Ports.Wdot_out > 0
      eta_engine <= 1 - T_cold/T_hot

  Joule_heating_irreversible:
    given:
      Ports.Fv = 0
      Ports.Vdot = 0
      params.IB = 0.3
      Reservoir.T_res < System.T
    expect:
      Ports.Qdot_in < 0     # heat leaving system
      sigma > 0             # irreversibility present

# ---------- NOTES ----------
notes:
  - Heat (Q̇) and work (Ẇ) are boundary power flows that are *reciprocal* under
    reversible conditions: the same affinity pair can drive either channel,
    with symmetric cross-couplings (Lqw = Lwq).
  - Irreversibility breaks perfect reciprocity by adding σ > 0 (dissipation).
  - Choose one mechanical power form (p·V̇ or F·v) to match your system.
  - Sign convention here: Q̇_in > 0 into system; Ẇ_out > 0 done by system.

Open System (with mass/enthalpy & chemical work)
flow "Work & Heat — Open System (Enthalpy + Mass Flow)" v1.0
authors: ["Mark Higins","Sky AI"]
locale: "en-GB"

# ---------- PARAMETERS ----------
params:
  units: "SI"
  sign_convention:
    heat_in_positive: true         # Q̇_in > 0 into control volume (CV)
    work_out_positive: true        # Ẇ_out > 0 done by CV on surroundings
  near_equilibrium_assumption: true
  IB: 0.0..1.0                      # boundary coherence index
  R_universal: 8.314462618

# ---------- STATE ----------
state CV:
  U: real          # internal energy [J]
  S: real          # entropy [J/K]
  T: positive      # [K]
  p: real          # [Pa]
  V: positive      # [m^3]

state Streams:      # multiple inlet/outlet streams allowed
  in:
    m_dot_i[]:   real    # mass flow rates in [kg/s]
    h_i[]:       real    # specific enthalpy of inlets [J/kg]
    s_i[]:       real    # specific entropy of inlets [J/(kg·K)]
    v_i[]:       real    # bulk speeds [m/s] (kinetic)
    z_i[]:       real    # elevations [m] (potential)
    mu_i[]:      real    # chemical potentials (per species or effective) [J/mol]
    n_dot_i[]:   real    # molar flow rates (if using μ) [mol/s]
  out:
    m_dot_o[]:   real
    h_o[]:       real
    s_o[]:       real
    v_o[]:       real
    z_o[]:       real
    mu_o[]:      real
    n_dot_o[]:   real

state Ports:
  Qdot_in:  real   # heat to CV [W]
  Wdot_out: real   # shaft/mech work by CV [W] (excludes p·V boundary work if using steady CV form)
  Wdot_elec: real? # optional electrical work [W]

# ---------- AUX ----------
state Grav:
  g: positive = 9.80665  # [m/s^2]

# ---------- FIRST & SECOND LAWS (Open form) ----------
invariants:
  # Control-Volume Energy Rate (general, includes KE & PE of streams)
  ddt(CV.U) = Ports.Qdot_in - Ports.Wdot_out
              + sum(Streams.in.m_dot_i * (Streams.in.h_i + 0.5*Streams.in.v_i^2 + Grav.g*Streams.in.z_i))
              - sum(Streams.out.m_dot_o * (Streams.out.h_o + 0.5*Streams.out.v_o^2 + Grav.g*Streams.out.z_o))

  # Entropy balance: dS/dt = Σ_in ṁ s_in − Σ_out ṁ s_out + Q̇_in/T_boundary + σ
  ddt(CV.S) = sum(Streams.in.m_dot_i * Streams.in.s_i)
              - sum(Streams.out.m_dot_o * Streams.out.s_o)
              + Ports.Qdot_in / CV.T
              + sigma
  sigma >= 0

  # Volume/pressure safety
  CV.T > 0
  CV.V > 0

# ---------- CHEMICAL WORK CHANNEL (optional, molar) ----------
invariants (optional_mu_channel):
  Wdot_chem = sum(Streams.out.mu_o * Streams.out.n_dot_o) - sum(Streams.in.mu_i * Streams.in.n_dot_i)
  # You may fold Ẇ_chem into Ẇ_out or keep separate for bookkeeping.

# ---------- NEAR-EQUILIBRIUM LINEARISED COUPLINGS ----------
state Linear:
  X_q: (1/CV.T - 1/T_res)                 # thermal affinity vs an external boundary temp T_res
  X_p: (CV.p - p_env)/CV.T                # mechanical affinity
  J_q: Ports.Qdot_in
  J_w: Ports.Wdot_out

params L:
  Lqq: positive
  Lww: positive
  Lqw: real
  Lwq: real

invariants:
  if params.near_equilibrium_assumption:
    L.Lqw = L.Lwq           # Onsager reciprocity (reversible limit)

transforms:
  linear_response:
    Ports.Qdot_in  = L.Lqq * Linear.X_q + L.Lqw * Linear.X_p
    Ports.Wdot_out = L.Lwq * Linear.X_q + L.Lww * Linear.X_p

# ---------- IRREVERSIBILITY ----------
transforms:
  entropy_production:
    sigma = (Ports.Qdot_in)*(1/CV.T - 1/T_res) + phi_mech + phi_mix
    phi_mech >= 0         # viscous/frictional
    phi_mix  >= 0         # mixing/chemical

  coherence_loss_link:
    loss_fraction = (1 - params.IB)
    phi_mech >= loss_fraction * max(Ports.Wdot_out, 0)

# ---------- STEADY CV SPECIALISATION ----------
scenes:
  steady_turbine (steady_state = true):
    given:
      ddt(CV.U) = 0
      ddt(CV.S) = 0
      Ports.Qdot_in ≈ 0
      single_inlet, single_outlet: true
    expect:
      Ports.Wdot_out = m_dot * ((h_in - h_out) + 0.5*(v_in^2 - v_out^2) + Grav.g*(z_in - z_out))
      sigma >= 0

# ---------- MONITORS ----------
monitors:
  energy_residual:
    eps = ddt(CV.U) - (Ports.Qdot_in - Ports.Wdot_out
          + sum(Streams.in.m_dot_i * (Streams.in.h_i + 0.5*Streams.in.v_i^2 + Grav.g*Streams.in.z_i))
          - sum(Streams.out.m_dot_o * (Streams.out.h_o + 0.5*Streams.out.v_o^2 + Grav.g*Streams.out.z_o)))
    assert abs(eps) <= 1e-9

  entropy_nonnegativity:
    assert sigma >= 0

Finite-Time Heat Engine (two reservoirs + timed strokes)
flow "Finite-Time Engine — Timed Strokes & Coherence Loss" v1.0
authors: ["Mark Higins","Sky AI"]
locale: "en-GB"

# ---------- PARAMETERS ----------
params:
  units: "SI"
  T_hot: positive
  T_cold: positive
  G_hot: positive        # hot-side thermal conductance [W/K]
  G_cold: positive       # cold-side thermal conductance [W/K]
  gamma: positive        # heat capacity ratio (ideal gas scene)
  R: positive = 8.314462618
  IB_hot: 0.0..1.0       # coherence index on hot exchanger
  IB_cold: 0.0..1.0
  cycle:
    tau_hot:  positive   # contact time with hot reservoir
    tau_expand: positive # adiabatic/finite-time expand
    tau_cold:  positive
    tau_compress: positive
  sign_convention:
    heat_in_positive: true
    work_out_positive: true

# ---------- STATE ----------
state WorkingFluid:
  U: real
  S: real
  T: positive
  p: real
  V: positive
  n: positive            # moles (closed WF)

state Ports:
  Qdot_hot:  real        # + into WF from hot
  Qdot_cold: real        # + into WF from cold (usually negative)
  Wdot_out:  real

# ---------- STROKE SCHEDULER ----------
state StrokeTimer:
  t: real
  phase ∈ {hot_iso, expand_ad, cold_iso, compress_ad}

transforms:
  scheduler:
    if 0        <= StrokeTimer.t < params.cycle.tau_hot:        StrokeTimer.phase = hot_iso
    if tau_hot  <= t < tau_hot+tau_expand:                      StrokeTimer.phase = expand_ad
    if tau_hot+tau_expand <= t < tau_hot+tau_expand+tau_cold:   StrokeTimer.phase = cold_iso
    if tau_hot+tau_expand+tau_cold <= t < tau_hot+tau_expand+tau_cold+tau_compress:
                                                               StrokeTimer.phase = compress_ad
    if t ≥ total_tau: t = 0   # loop cycle
  total_tau = params.cycle.tau_hot + params.cycle.tau_expand + params.cycle.tau_cold + params.cycle.tau_compress

# ---------- LAW CORE ----------
invariants:
  # First law (instantaneous):
  ddt(WorkingFluid.U) = Ports.Qdot_hot + Ports.Qdot_cold - Ports.Wdot_out
  # Second law:
  ddt(WorkingFluid.S) = Ports.Qdot_hot/WorkingFluid.T + Ports.Qdot_cold/WorkingFluid.T + sigma
  sigma >= 0
  WorkingFluid.T > 0; WorkingFluid.V > 0

# ---------- STROKE MODELS ----------
transforms hot_isothermal (when StrokeTimer.phase == hot_iso):
  # Finite-time isothermal with heat leak/dissipation via IB_hot
  Ports.Qdot_hot = params.G_hot * (params.T_hot - WorkingFluid.T)
  # Ideal isothermal work element (e.g., ideal gas): Ẇ ≈ p·V̇ ; pV = nRT
  Ports.Wdot_out = WorkingFluid.p * ddt(WorkingFluid.V)
  # Entropy production from finite ΔT:
  sigma += Ports.Qdot_hot * (1/WorkingFluid.T - 1/params.T_hot)
  # Coherence loss mapping:
  phi_hot >= (1 - params.IB_hot) * max(Ports.Qdot_hot, 0)
  sigma += phi_hot / max(WorkingFluid.T, 1e-12)

transforms adiabatic_expand (when StrokeTimer.phase == expand_ad):
  Ports.Qdot_hot  = 0
  Ports.Qdot_cold = 0
  # Finite-time adiabatic with frictional dissipation:
  Ports.Wdot_out = WorkingFluid.p * ddt(WorkingFluid.V) - phi_mech
  phi_mech >= 0
  sigma += phi_mech / WorkingFluid.T
  # Ideal-gas guide (optional): T·V^{γ-1} ≈ const (monitor only)

transforms cold_isothermal (when StrokeTimer.phase == cold_iso):
  Ports.Qdot_cold = params.G_cold * (params.T_cold - WorkingFluid.T)  # usually negative
  Ports.Wdot_out  = WorkingFluid.p * ddt(WorkingFluid.V)
  sigma += Ports.Qdot_cold * (1/WorkingFluid.T - 1/params.T_cold)
  phi_cold >= (1 - params.IB_cold) * max(-Ports.Qdot_cold, 0)  # loss on rejection
  sigma += phi_cold / max(WorkingFluid.T, 1e-12)

transforms adiabatic_compress (when StrokeTimer.phase == compress_ad):
  Ports.Qdot_hot  = 0
  Ports.Qdot_cold = 0
  Ports.Wdot_out  = WorkingFluid.p * ddt(WorkingFluid.V) - phi_mech
  phi_mech >= 0
  sigma += phi_mech / WorkingFluid.T

# ---------- CYCLE ACCUMULATORS ----------
state CycleTotals (reset each cycle):
  Q_in:   real
  Q_out:  real
  W_out:  real
  Sigma:  real

transforms accumulate:
  if StrokeTimer.phase == hot_iso:
    CycleTotals.Q_in  += Ports.Qdot_hot * dt
  if StrokeTimer.phase == cold_iso:
    CycleTotals.Q_out += (-Ports.Qdot_cold) * dt   # magnitude rejected
  CycleTotals.W_out   += Ports.Wdot_out * dt
  CycleTotals.Sigma   += sigma * dt

# ---------- PERFORMANCE ----------
monitors:
  efficiency:
    eta = CycleTotals.W_out / max(CycleTotals.Q_in, 1e-12)
  carnot_bound:
    eta_carnot = 1 - params.T_cold / params.T_hot
    assert eta <= eta_carnot + 1e-9

  reversibility_indicator:
    if params.IB_hot ≈ 1 and params.IB_cold ≈ 1 and CycleTotals.Sigma ≈ 0:
      assert abs(eta - eta_carnot) <= 1e-6

  positivity:
    assert CycleTotals.Sigma >= 0

SIMPLE VOLUME PROGRAM
# ---------- SIMPLE VOLUME PROGRAM (piecewise-linear per stroke) ----------
transforms volume_program:
  # Define linear dV/dt for each phase to hit target endpoints exactly.
  # Hot isothermal:   V1 -> V2 (expand)
  # Adiabatic expand: V2 -> V3 (expand)
  # Cold isothermal:  V3 -> V2 (compress)
  # Adiabatic comp.:  V2 -> V1 (compress)
  V1 = V_min
  V2 = V_mid
  V3 = V_max

  if StrokeTimer.phase == hot_iso:
    dVdt = (V2 - V1) / params.cycle.tau_hot
  if StrokeTimer.phase == expand_ad:
    dVdt = (V3 - V2) / params.cycle.tau_expand
  if StrokeTimer.phase == cold_iso:
    dVdt = (V2 - V3) / params.cycle.tau_cold
  if StrokeTimer.phase == compress_ad:
    dVdt = (V1 - V2) / params.cycle.tau_compress

  ddt(WorkingFluid.V) = dVdt

# ---------- STARTER SCENE (ideal gas guide) ----------
scenes:
  starter_demo_ideal_gas:
    # --- Engine setup ---
    given params:
      T_hot:  700.0         # K
      T_cold: 300.0         # K
      G_hot:  40.0          # W/K
      G_cold: 40.0          # W/K
      gamma:  1.4           # ideal diatomic-ish guide
      IB_hot: 0.95
      IB_cold: 0.95
      cycle:
        tau_hot:      0.50  # s
        tau_expand:   0.25  # s
        tau_cold:     0.50  # s
        tau_compress: 0.25  # s
      sign_convention:
        heat_in_positive: true
        work_out_positive: true

    # --- Working fluid & geometry ---
    given WorkingFluid:
      n: 0.10               # mol
      T: 450.0              # K (initial guess)
      V: 1.00e-3            # m^3  (V1)
    given:
      V_min: 1.00e-3        # m^3 (V1)
      V_mid: 1.35e-3        # m^3 (V2)
      V_max: 1.80e-3        # m^3 (V3)

    # --- Ideal-gas guidance (used to compute p whenever needed) ---
    transforms ideal_gas_guide:
      WorkingFluid.p = (WorkingFluid.n * params.R * WorkingFluid.T) / WorkingFluid.V

    # --- Activate the stroke scheduler & volume program ---
    enable:
      - scheduler
      - volume_program
      - hot_isothermal
      - adiabatic_expand
      - cold_isothermal
      - adiabatic_compress
      - accumulate
      - ideal_gas_guide

    # --- Integration hints (engine loop) ---
    notes:
      - Use a fixed step like dt = 1e-3 s (explicit Euler is fine for demo).
      - Run for N cycles (e.g., 30) and report the last cycle’s totals.
      - Expect η < η_Carnot = 1 - Tc/Th due to finite ΔT and φ losses.

    expect (qualitative):
      - CycleTotals.W_out > 0
      - 0 < efficiency.eta < 1 - T_cold/T_hot
      - CycleTotals.Sigma > 0 and small if IB_hot, IB_cold ≈ 1

  # (Optional) high-loss variant to see degradation
  starter_demo_lossy:
    given params:
      T_hot:  700.0
      T_cold: 300.0
      G_hot:  25.0
      G_cold: 25.0
      gamma:  1.4
      IB_hot: 0.75
      IB_cold: 0.70
      cycle:
        tau_hot:      0.40
        tau_expand:   0.20
        tau_cold:     0.40
        tau_compress: 0.20
    given WorkingFluid:
      n: 0.10
      T: 450.0
      V: 1.00e-3
    given:
      V_min: 1.00e-3
      V_mid: 1.30e-3
      V_max: 1.70e-3
    enable:
      - scheduler
      - volume_program
      - hot_isothermal
      - adiabatic_expand
      - cold_isothermal
      - adiabatic_compress
      - accumulate
      - ideal_gas_guide
    expect:
      - Lower η vs. starter_demo_ideal_gas
      - Higher CycleTotals.Sigma

RESULTS EXPORT (CSV)
# ---------- RESULTS EXPORT (CSV) ----------
io.write:
  format: csv
  encoding: utf-8-sig            # Excel-safe (BOM)
  filename: "finite_time_engine_results_${run_id}.csv"
  schema:
    - cycle:int
    - t_end:float                # s (end time of cycle)
    - Q_in:float                 # J
    - Q_out:float                # J (magnitude rejected)
    - W_out:float                # J
    - eta:float                  # efficiency = W_out / Q_in
    - Sigma:float                # total entropy production [J/K]
    - T_hot:float
    - T_cold:float
    - G_hot:float
    - G_cold:float
    - IB_hot:float
    - IB_cold:float
    - tau_total:float            # s (cycle duration)
    - scene:str?                 # optional label

# ---------- CYCLE LOGGER ----------
state Results:
  run_id: str = now_timestamp_compact    # e.g., 2025-10-30T07-12-03
  cycle_no: int = 0
  t_prev: float = 0.0

transforms detect_cycle_boundary:
  # Detect wrap-around when scheduler resets t to 0 at the end of a cycle.
  if StrokeTimer.t < Results.t_prev:
    Results.cycle_no += 1
    call finalize_and_flush_cycle
  Results.t_prev = StrokeTimer.t

transforms finalize_and_flush_cycle:
  # Compute end-of-cycle metrics from accumulators
  eta_cycle = CycleTotals.W_out / max(CycleTotals.Q_in, 1e-12)
  tau_total = params.cycle.tau_hot + params.cycle.tau_expand + params.cycle.tau_cold + params.cycle.tau_compress

  # Append one row to CSV
  io.write.append:
    cycle:     Results.cycle_no
    t_end:     StrokeTimer.t + tau_total   # end timestamp for this cycle window
    Q_in:      CycleTotals.Q_in
    Q_out:     CycleTotals.Q_out
    W_out:     CycleTotals.W_out
    eta:       eta_cycle
    Sigma:     CycleTotals.Sigma
    T_hot:     params.T_hot
    T_cold:    params.T_cold
    G_hot:     params.G_hot
    G_cold:    params.G_cold
    IB_hot:    params.IB_hot
    IB_cold:   params.IB_cold
    tau_total: tau_total
    scene:     active_scene_label?   # (optional) set this in your scene

  # Reset accumulators for next cycle
  CycleTotals.Q_in  = 0
  CycleTotals.Q_out = 0
  CycleTotals.W_out = 0
  CycleTotals.Sigma = 0

# ---------- HOOKS (enable these in your scene) ----------
enable:
  - detect_cycle_boundary

# ---------- OPTIONAL: ROLLING SUMMARY (printed to console/log) ----------
transforms rolling_summary_every_k_cycles:
  k = 5
  if Results.cycle_no > 0 and Results.cycle_no % k == 0 and StrokeTimer.t < 1e-9:   # just after wrap
    avg_eta   = mean(last_k(io.write.rows.eta, k))
    avg_sigma = mean(last_k(io.write.rows.Sigma, k))
    log.info = "Cycles: ${Results.cycle_no} | avg_eta(last ${k})=${round(avg_eta,4)} | avg_Sigma=${round(avg_sigma,6)}"

# ---------- MONITORS ----------
monitors csv_sanity:
  assert CycleTotals.Q_in   >= 0
  assert CycleTotals.Q_out  >= 0
  assert Results.cycle_no   >= 0
  assert eta >= 0 when defined(eta)       # instantaneous guard

How to use:

In your chosen scene (e.g., starter_demo_ideal_gas), add to enable::

  • detect_cycle_boundary
  • (optionally)rolling_summary_every_k_cycles

Optionally set active_scene_label: "starter_demo_ideal_gas" at the top of the scene to populate the scene column.

Overview

This flow describes the dual nature of work and heat as reciprocal boundary exchanges within a coherent energy field.
Where classical thermodynamics treats them as separate modes of energy transfer, Flow Algebra unifies them as paired channels of coherence: one structured (work), one distributed (heat).
They are not different in substance, but in symmetry — work is directional ordering of field tension, while heat is the same energy diffused through phase-space microstates.


Conceptual Core

At the heart of the model lies the First and Second Laws of thermodynamics re-expressed as flow invariants:

$$ \frac{dU}{dt} = \dot{Q}_{\text{in}} - \dot{W}_{\text{out}}, \qquad \frac{dS}{dt} = \frac{\dot{Q}_{\text{in}}}{T} + \sigma, \qquad \sigma \ge 0. $$

Here, UU and S evolve together — energy and entropy forming a coupled pair.
In Holographic terms, this expresses the boundary’s coherence gradient: when alignment is perfect (σ → 0), energy exchange is reversible and coherent; when alignment fails, part of that tension disperses as entropy — a measurable loss of structural memory.

Reciprocity and Symmetry

Using Onsager’s framework, the flow defines cross-coupled responses between mechanical and thermal channels:

$$ \begin{bmatrix} J_q \\[4pt] J_w \end{bmatrix} = \begin{bmatrix} L_{qq} & L_{qw} \\[4pt] L_{wq} & L_{ww} \end{bmatrix} \begin{bmatrix} X_q \\[4pt] X_w \end{bmatrix}. $$

with the reciprocity condition Lqw=LwqL_{qw} = L_{wq} enforcing mirror symmetry of cause and effect.
This symmetry is broken only by dissipation (σ > 0), representing coherence loss through microscopic uncertainty or boundary scattering.
Thus, work and heat are reversible mirrors of one another in the coherence limit.

Boundary Coherence Index (IB)

To quantify how reversible an exchange is, the flow introduces IB ∈ [0, 1], the boundary coherence index:

  • IB = 1: perfectly coherent, no phase loss, ideal reversible process.
  • IB < 1: partial coherence — finite entropy generation or radiation leakage.
  • IB = 0: incoherent, energy disperses as thermal noise.

This gives engineers and theorists a continuous measure of how “tight” or “loose” an energy boundary behaves, bridging pure thermodynamics with boundary-based coherence geometry.


Interpretation

Work corresponds to structured curvature alignment (pressure–volume, force–velocity, or field–flux pairs),
while heat corresponds to distributed curvature relaxation, the system’s way of restoring symmetry after deformation.
Together they form a single feedback loop:

  • Work converts ordered boundary tension into motion.
  • Heat returns dispersed curvature to equilibrium.

This cycle is the reciprocal heartbeat of every physical system — from engines and phase transitions to biological metabolism and neural computation.


In the HST View

In Holographic Space-Time, this reciprocity arises because every boundary exchange seeks to conserve total coherence.
The Lorentz curvature of motion and the thermal curvature of memory are not distinct forces, but dual projections of the same field geometry.
Work and heat are therefore the vector and scalar components of a unified energy flow, maintaining the equilibrium between form and information.

Applications and Extensions

  • Modelling reversible engines and finite-time thermodynamic cycles
  • Studying entropy production in materials, plasmas, or information systems.
  • Analysing AI processor thermals and feedback coherence at micro-architectural level.
  • Linking mechanical work, radiation, and computation through a single energy-flow syntax.

Connection to the Finite-Time Engine Flow

The Work and Heat — Reciprocal Flows model defines the local symmetry that governs every reversible exchange.
The Finite-Time Engine flow builds upon this foundation by introducing timing, loss, and boundary asymmetry — showing what happens when those reciprocal channels are no longer perfectly aligned.

In that later model, the coherence index (IB) and conductances (Gₕₒₜ, Gₐₗₒₗd) determine how closely each stroke adheres to the reciprocal ideal defined here.
When IB → 1 and thermal gradients shrink, the engine approaches the reversible limit described by this flow.
When IB falls, dissipation rises, and the symmetry between work and heat fractures — entropy production becomes visible as measurable loss of coherence.

Thus, these two flows form a natural pair:

  • This flow — the universal law of reciprocity and energy balance.
  • Finite-Time Engine — the temporal expression of that law under finite coherence and real-world constraints.

Together, they outline the bridge between thermodynamic theory and dynamic coherence geometry — a continuous spectrum from reversible symmetry to dissipative reality.


Kireth Research
38 Thorndale Croft, Wetwang, Driffield
East Riding of Yorkshire YO25 9XZ
Designed By