Skip to content

Close the heat-transfer and electrical gaps against ShipSIM - #6

Merged
pankgeorg merged 2 commits into
mainfrom
thermal-electrical-gaps
Sep 13, 2026
Merged

Close the heat-transfer and electrical gaps against ShipSIM#6
pankgeorg merged 2 commits into
mainfrom
thermal-electrical-gaps

Conversation

@pankgeorg

Copy link
Copy Markdown
Member

Closes the remaining ShipSIM components that had been left out, plus three bugs the work exposed. Every claim below is checked by an assertion in one of the two validation scripts.

Heat transfer

The four ConvectionFactors. Upstream's Internal.ConvectionBaseClass is
ThermalComponents.Interfaces.ConvectiveElement1D — same solid/fluid ports, same ΔT,
same sign convention — so Thermal.ConvectionBaseClass extends the library interface and
adds only A, h, Tavg and Q_flow = h·A·ΔT. The porting notes had blamed
BuildingsHeatTransfer not resolving; it was never the blocker. Upstream gives its four
factors two different shapes (two extend the base class, two wrap a Convection and drive
its Gc); all four extend the base class here, so they report h and A the same way —
which is what ShipCompartment then consumes.

ConvRadSunWall rebuilt as a composition. The previous port flattened it into one
algebraic flux and took h_c as an input. It now composes what upstream composes: two
BodyRadiation paths split by view factor, ExternalConvection, IrradiationOnPlane,
SunScreen and two PrescribedHeatFlows. The flattened version had radiated the whole
surface to the sky, overstating night-time cooling on a vertical wall by a factor of two,
and had no transmission port, no sun-screen wiring and no sun-height gate.

ShipCompartment is MultiportLumpedRoom with its constant U_inner/U_outer
replaced by the two convection factors. A building's outside film coefficient is near
enough constant; a ship carries its own weather. Alongside, the two models agree (the
fixed U_outer = 5 really is the still-air 5.36); at sea the fixed-U model runs 2.75 K
hot behind insulation, and 9.30 K on bare steel where the films are nearly the whole
resistance.

EnvironmentHeatTransfer derives cloud cover from the irradiance shortfall against
clear sky — a clocked moving average with a night latch, where upstream uses
Mean(f=1/1800) and a when IsDay == false — plus sky temperature, wind vector and a
Magnus dew point (upstream's linear form asserts it is only valid above 50 % RH).

Electrical and fatigue

RandomStart is upstream's when time >= pre(NextStep) scheduler written in the
clocked sublanguage on DiscreteComponents.UniformNoise — seeded and reproducible. The
note in OnOffConsumer saying this could not be expressed in Dyad predated
DiscreteComponents.

ElectricalLoad is the EPLA that MiscSamples.Balance performs, with the consumer
list as parameter arrays rather than 177 hand-written instances. Six consumers over six
hours: bus load 0–67 kW across 51 distinct levels, 24.9 kW average against 79.1 kW
installed, diversity factor 0.314.

Rainflow as Julia, not a component. src/Rainflow.jl counts cycles (ASTM E1049
three-point), bins the matrix and accumulates Miner damage over the turning points
EventPeakSampler already emits. Upstream runs it inside the model on an algorithm
section with two stacks and while loops. The clocked layer could express a bounded
version, but counting cycles is post-processing over a series: it has no state the rest of
the model can see, it needs an unbounded stack to be exact, and clocked array state does
not cross-compile (SynchToolkit #212), so a model carrying one could not be deployed.

Three bugs this exposed

  • sun_vector_world had the sun in the northern sky. It negated the east and up
    components to turn direction-to-sun into ray direction, but not the north one. A
    south-facing wall was lit at dawn and dusk and dark at noon. Latent since the solar port:
    IrradianceOnPanel uses only the vertical component and nothing else consumed
    SunVector_y. The two solstice analyses are the regression check — in winter the south
    wall takes the whole gain and the north none; in summer that inverts, which is the real
    behaviour of a vertical wall at 43°N.
  • ExternalConvection was not differentiable at rest. sqrt(u_x²+u_y²+u_z²) has no
    gradient at the origin (0/0) and |u|^0.78 an unbounded one; a max(|u|, tiny) floor
    bounds neither. Either puts a NaN or a 2400 W/(m²·K) per m/s entry in the Jacobian at
    t = 0. Between two ideal temperature sources neither shows — which is why the §1 suite
    passed — but with a wall face that has no capacitance the integrator drives dt below
    floating-point epsilon and reports only "unstable". A steady wind of any speed hides
    it, zero included; it is passing through zero that fails, which is what a ship leaving
    harbour does.
  • A ZeroOrderHold collapses everything downstream of it, not just itself. The known
    caveat is that its continuous output reads back as its final value under symbolic
    indexing. It also collapses anything algebraically derived from it. An ElectricalLoad
    bank read back as a flat line, every consumer that happened to be off at the end
    reporting zero for the whole run, while the model had integrated all six correctly. Fixed
    by putting a state in the path in both places it bit — a switching lag in OnOffConsumer,
    a one-tick lag on the cloud ratio.

Library findings, recorded in PORTING_NOTES.md

  • HVACComponents 0.3.0 will not initialise a MultiportVolume whose moist-air fluid
    boundary is below about 15 °C. Minimal reproducer with no component of ours in it; the
    media's own property functions are exact down to −5 °C, so it is in the assembled DAE.
    Only fluid ports are affected, so the deckhouse still runs its 0 °C winter case on a
    preheated supply — which is what a ship does anyway.
  • ThreeTempWall1D.m_wall and OneTempWall1D.m_wall are documented as the total mass
    across N surfaces but each surface's balance divides by the whole of it.

Validation

scripts/validate_heattransfer.jl — 93 assertions. scripts/validate_machinery.jl — 43.
Both green on a clean rm -rf generated && dyad compile. Correlations are recomputed from
the literature in the test rather than read off the port; the wall's radiation split is
checked against the view factors on the solver's own saved steps; rainflow is checked
against hand-worked sequences.

Not ported, and why, is in PORTING_NOTES.md: TriggerConsumer/StartGenerator (variants
of RandomStart, writable the same way), SunIrradianceMultibody, SubComponents.Ikeda
(a stub upstream) and VariableTranslation.

🤖 Generated with Claude Code

pankgeorg and others added 2 commits September 13, 2026 01:57
Ports the four ConvectionFactors, rebuilds ConvRadSunWall as a composition,
adds a ship compartment and an environment boundary, and closes the electrical
and fatigue gaps.

Heat transfer
- ConvectionBaseClass extends ThermalComponents.Interfaces.ConvectiveElement1D,
  which is upstream's ConvectionBaseClass with the same ports, ΔT and sign
  convention. All four factors extend it, so upstream's two different shapes
  become one.
- ConvRadSunWall composes two view-factor-split BodyRadiation paths,
  ExternalConvection, IrradiationOnPlane, SunScreen and two PrescribedHeatFlows,
  in place of a flattened algebraic approximation that took h_c as an input and
  radiated the whole surface to the sky.
- ShipCompartment is MultiportLumpedRoom with its fixed U_inner/U_outer replaced
  by the two convection factors, so a compartment's heat loss follows the ship's
  apparent wind.
- EnvironmentHeatTransfer derives cloud cover from the irradiance shortfall on a
  clocked moving average with a night latch, plus sky temperature, wind vector
  and Magnus dew point.

Electrical and fatigue
- RandomStart is upstream's when-event scheduler as clocked logic on
  DiscreteComponents.UniformNoise, seeded and reproducible. ElectricalLoad is the
  EPLA: N consumers as parameter arrays rather than 177 hand-written instances.
- src/Rainflow.jl counts cycles and accumulates Miner damage over the turning
  points EventPeakSampler emits. Upstream runs this inside the model on an
  algorithm section; it is post-processing, and clocked array state does not
  cross-compile.

Fixes found on the way
- sun_vector_world returned the northward component of the direction to the sun
  while negating the other two, so the sun stood in the northern sky. Nothing
  consumed SunVector_y before, so no earlier result changes.
- ExternalConvection's wind speed is now sqrt(|u|² + u_reg²): sqrt(Σu²) has no
  gradient at the origin and |u|^0.78 an unbounded one, either of which kills the
  first step once the coefficient feeds a wall face with no capacitance.
- OnOffConsumer takes its work signal behind a switching lag. A ZeroOrderHold's
  output collapses to its final value under symbolic indexing, and so does
  everything algebraically derived from it.

136 assertions across scripts/validate_heattransfer.jl (93) and
scripts/validate_machinery.jl (43), on a clean regenerate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e reproducer

The sun-vector sign error was filed under "corrections relative to upstream",
which is where upstream's own defects go. It is ours: ShipSIM's SolarIrradiation
negates all three components correctly. Moved to its own section.

The MultiportVolume initialisation reproducer said "no component of ours in it"
while using our SourceMoistAir. Rebuilt from library components only —
MassFlowSource_TPhi and Boundary_pTPhi — and verified: solves at 20 C, returns
InitialFailure at 0 C.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pankgeorg
pankgeorg merged commit 3d3003c into main Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant