Repository navigation
This release replaces the simulator's model of the heater
(nwp500.sim, nwp500.sim_calibrate and nwp-cli simulate).
The cloud API, the MQTT and BLE clients and their models are unchanged.
- The tank is stacked layers (
sim.physics.stratified), in place
of the two-zone tank: draws, losses, circulation and each heat source
act on the layers they reach, and the probes read the layers at their
heights. - The heat pump is a performance map fitted on the heater's own
runs, its heat and electricity each a plane in the air and the
condenser's water, in place of the refrigerant cycle and the heat
book. - Calibration fits the layered tank and the map from a recorded
history and writesheater-params/2; aheater-params/1
document is refused and must be calibrated again. - Parameters can be scored on recorded days they were not fitted on
(nwp500.sim_score), and two parameter sets compared on the same
events. - A simulation starts from the layers a history leaves, and can go
on from where another ended (HeaterState.resume).
These changes break callers of the simulator: see Upgrading below.
Upgrading
The simulator's tank is stacked layers and its heat pump a fitted
performance map; the two-zone tank, the refrigerant cycle, the heat
book and the element-mode planner are gone. To move a caller over:
- Parameters. A
heater-params/1document is refused: calibrate
again withnwp-cli simulate calibrate(or
sim_calibrate.calibrate), which writesheater-params/2. A
/2document with removed fields, such as the heat book's, still
reads: they are left out and named byparams.unknown_fields. One
written before the layered tank also reads its tank as the priors,
withtankunfitted, until it is calibrated again. - Removed parameters.
PowerParamsandHeaterParams.power;
BookParamsandHeaterParams.book; the two zones' tank, loss
and draw terms (TankParams.lower_zone_shareand
stratification_gap_tau_h,LossParams.ua_btu_hr_f,
ua_exponent,bottom_loss_btu_hr, theDrawParamsreach and
observability terms); the refrigerant cycle'sCompressorParams
fields;ElementParams.upper_f_per_minandlower_f_per_min;
the readiness evidence and outlet lag (ReadinessParams.evidence,
delivered_lag_gal,fall_f_per_gal); the circulation's book
terms (CirculationParams.loss_btu_per_gal,book_circuit_gal).
The Removed and Changed lists below name each. simulateno longer takesledger,lead_draws,
physics_usableorbook, andreadinessand
time_to_readyno longer takebook.extensions.HeatLedger
and its types are gone.- The tank's state.
HeaterState.layers_fcarries the layers a
walk starts from; without it every answer reads the profile the two
probes stand for and saystank_profile_estimated.
sim_adapters.layers_from_historyandlayers_from_changes, and
sim_home_assistant.HomeAssistantTank(wasHomeAssistantBook),
give it from a recorded history;book_from_historyand
book_from_changesare gone. element_mode(scenario, params, *, mode, ...)takes a
Scenarioin place ofstate,settingsandat, and no
hp_lower_minutes;ElementModeResulthas noplan.
physics.element.mode_planandmode_recoveryare gone.draw_forecast.draws_firstgives each placed draw as
(at, gallons, per-draw gallons, seconds), its time drawing added.- A custom
Physicsimplementsestimate,observe,
advanceanddrawon aTankState. - The circulation's terms.
CirculationParams.displaced_galis
the pipe's measured volume, no longer an effective one: measure it
and the newpipe_ua_btu_hr_fon one run after a rest
(calibration.measure_pipe), then fit the return's rest and
cooling withcalibration.fit_return(runs, displaced_gal, pipe_ua_btu_hr_f), which returns those two.fit_displaced_gal
is gone.
Added
-
A run under way at a walk's start, from the water under its
probes.CompressorParams.running_probes_warm_f((0, 0) unless
set;defaults.SITE_RUNNING_PROBES_WARM_F, (0.51, 0.91) degF, in
site_example_paramsand sodefault_params): how much higher
the upper and lower probes read than the water at them while the
compressor heats. A scenario that starts with the compressor running,
not starting there (running_starts) nor resumed from a walk's end
(HeaterState.resume), takes its tank as the probes less these, its
layers given moved so much at the probes (TankPhysics.at_probes),
insimulate,forecast(and sonext_recoveryand
readiness's recovery) andactive_cycle, whose checks of the
water at the target are of it too. A walk's end state
(HeaterState.resumegiven, carried or dropped) holds the walk's
water and is taken as it is. The values are the probes' step down in
the 5 minutes after 47 quiet stops of runs heating 40 minutes or more
(measured 2026-10-28,recovery-speed). On the 29 held-out days at
DrawParams.meter_reads0.85 (measured 2026-11-06), the five runs
under way at a day's start on a hot tank, 22 to 128 minutes from their
stop, came 4.7 to 15.2 minutes short (8.5 on average), reaching the
stop on probes read warm, and now 2.9 short on average; walked from
their recorded starts, four of them come 3.0 short to 10.2 long. The
day's electricity comes 11.41 % off and 0.72 % low against 12.66 and
2.15, the tank's metrics within 0.02 degF. -
The evaporator fan's high speed in the compressor's power.
CompressorParams.fan_high_below_air_f(None unless set) and
fan_high_w(0): a walk's compressor run puts its fan at the high
speed from the first instant the air is underfan_high_below_air_f
to the run's end (TankPhysics.fan_high,
physics.heat_pump.fan_high_from,
decay.AmbientSampler.first_under,
stratified.Compressor.fan_high_at), and the power map's
electricity is its plane's plusfan_high_wwhile it is
(physics.heat_pump.power_w, itsfan_high; without it the speed
is read from the air alone, as the record's replay reads it); the heat
is unchanged. A run a walk ends under way keeps its fan's speed in the
resume (CompressorRun.fan_high_at,fan_low_through), so the
walk that goes on fromSimulationResult.end_stateholds it; a run
under way at a scenario's start without one reads its fan from that
start.heat_pump_fit.power_blocksgives each block
run_air_min_f, the least air of its run's every minute to the
block's end;heat_pump_fit.fit_power_maptakes the air
(fit_heat_pumpthe base's), takes a block for the high speed where
that is under it (heat_pump_fit.fan_high), fits the plane on the
rest and the step, at least 0, as the mean the plane leaves of the
high speed's (Plane.fan_high), fitting no plane with too few or
too alike at the low speed and keeping the base's step with none at
the high;power_map_errorreads the blocks so; the calibration
report shows the step, andviolationschecks the low speed at the
fan's air and over and the high at every air, unless the fan's air is
at or under the envelope's coldest. On one NWP500-65 (measured
2026-11-05) the fan ran at about 543 rpm or 895, went to the high
speed inside a run as the heater's own air reading fell to about 58
degF and held it to the run's end, and its high speed drew 23.7 W more
on the 10-minute metered climb blocks with one speed throughout. Read
from the air at the heater and held so, at 59.0 degF and +20.68 W
fitted on the blocks before 2026-09-18 with the plane kept (the
blocks' speed so taken agreeing with the fan's measured one on 97.3 %
of them), the 1,807 winter blocks under 60 degF read 1.1 W over (in
the fit) against 19.2 low with the plane alone, the winter's at 60 to
80 degF 1.6 W low against 6.4, and the 50 held out under 60 degF 9.3 W
low against 26.3. The 29 held-out days atDrawParams.meter_reads
0.85 move only in their electricity, the day's 12.66 % off against
12.80, 2.15 % low against 2.48. -
The house meter's reading of a gallon.
DrawParams.meter_reads
(1, the meter as it reads, unless set; over 0 and at most 2) takes
each metered gallon as1 / meter_readsdelivered
(DrawParams.delivered_gal) where the tank takes it or is judged
against it: aDraw's gallons in a walk, aDrawWindow's
expected gallons, a bath's and a shower's intime_to_ready(and
ReadinessParams.bath_galandshower_gal), a record's draws in
its replay and the tank fit, a delivery's gallons in the score. A
record still holds the meter's gallons. The flows a draw is judged by,
and its domain volume, stay the meter's. On the site's 29 held-out
days (measured 2026-11-03 on the parameters in use) 0.85, a known
volume's reading on that day, puts the day's electricity 2.4 % low
against 9.8 % at 1, 12.9 % off against 14.3, and the false starts at
0.28 a day against 0.17. A meter whose reading moved takes dated
readings (DrawParams.meter_reads_since,(from, share)in
order,meter_readsbefore the first;DrawParams.reads_at):
each metered gallon is taken at the reading at its own instant, a
bath's and a shower's at the scenario's start, a window's at its
start. On the site, 1 before the meter's step of 2026-09-12 19:51
UTC and 0.85 from it scores the held-out days as 0.85 throughout
does, as they fall after the step;site_example_paramscarries
it (defaults.SITE_METER_READS_SINCE), anddefault_params
reads as the meter reads. -
How near a walk came to its start triggers.
sim.start_margins(result.points, hp_lower_on_f, tou_windows, loads, params)gives each coast of a walk and its nearest approach to each
heat-pump trigger in force over it (StartMargin): the lower zone
over its trigger (hp_lower_on, no nearer than the setpoint), held
as the heater holds it and in Energy Saver released by a drop, a
draw's or the circulation's; inside the TOU windows the upper zone
over the setpoint lessUPPER_DEFICIT_Fas well; where, and whether
the coast ended in a start. A caller can weigh a start the walk did
not call by how nearly it called it. On the site's 29 held-out days, 3
of the 11 recorded starts the walk did not place fell on coasts it did
not start: inside the windows at the smallest and fourth smallest
upper margins of 23 (1.8 and 7.8 °F), and for the lower trigger at the
second smallest of 24 (7.0 °F). Each carries the gallons drawn in the
hour up to its point (StartMargin.drawn_gal) and whether they came
to 8 gal or more (after_large_draw), where a lower margin is the
least sure: on the site's held-out single draws of that size the
walk's lower probe fell at once, a median 12.8 °F under the record's a
minute after, and an hour after was 3.4 to 16.5 °F off, either way, on
the 9 it missed by 3 °F or more.simulategives them on its result
(SimulationResult.margins), read on the lower trigger, the TOU
windows, the draws it took, the circulation and the
parameters the walk had.start_marginscounts no draw before the
walk's first point, nor, givenuntil, at or after it. -
PathPoint.heating_disabled: heating was disabled at the point
(Vacation, or powered off with no entry having turned the heater on). -
A slower draw takes more of the tank than the valve's share.
Each delivered gallon takess + undiluted_share · (1 − s)of the
tank's water,sthe valve's share
(DrawParams.undiluted_share, 0-1, 0 by default), in the walk, the
deliverable gallons and a history's metered tank gallons;
physics.draw.hot_fractionandwithdrawn_galtake it.
site_example_paramssets 0.25 (SITE_UNDILUTED_SHARE): after
the site's 32 held-out draws of 8 gal or more it put the lower
probe's fall within 4.86 °F RMS of the record's, against 5.92 with
the valve's share alone. On the 29 held-out days the
electricity came out 14.3 % off against 16.0, the recoveries 14.5 min
against 17.1, and one day whose start at the TOU window's close was
missed is now called. -
A fast draw takes the tank's water whole. A draw at
DrawParams.undiluted_gpmor faster (its gallons over its
duration_s,Draw.flow_gpm) takes as much of the tank's water
as it delivers, as with no valve.site_example_paramssets 2.0
gal/min (SITE_UNDILUTED_GPM); the default, None, takes every draw
through the valve. On the site's record the four draws of 20 gal or
more at 2.1-2.7 gal/min took about 1.3 times the valve's share. The
record times each draw by the history's draw detector
(TankRecord.draw_seconds,tank_fit.draw_durations,
draw_spans), andsim_scorewalks one at that flow or faster
over its time, one under way at an event's start replayed as far as
it ran and walked on for the rest. On the 29 held-out days the two
the tank ran out on are called, where neither was. The upper probe's
lowest is 2.9 degF off against 3.8, and the electricity 16.0 % off
against 16.5. The 16 held-out recoveries with water in use are 25.0
min off against 31.4. -
Candidates weighed over many households.
sim.draw_ensemble
samples members from a weekly draw forecast (Poisson counts at each
slot's rate, long draws' durations lognormal on the published mean
and 90th percentile, volume with duration) andweighwalks every
candidate on the same members:cold_share, each member's lowest
delivery over its long draws, and its electricity, for a cost and its
spread. -
What a change leaves behind.
sim.carry_onwalks each walk's
end on through one tail, the heater in its standing settings, so
changes are weighed with the tank each leaves rather than over their
own span alone.sim.Carriedholds the walk and its tail, their
electricity by slot, and how far the tail's end is from the first
walk's (residual_f,residual_wh,heating_alike,
settled). Whether the tails go on alike is walked past every
instant still due and every TOU window's close, up to 8 days
(sim.carry.GO_ON_MAX); tails owing one past that are not counted
alike. -
The scoreboard walks whole days.
sim_score.day_eventstakes
each local day a record observed throughout, andsim_score.score
walks it from its start as the heater was told over it: the recorded
setpoint and mode writes, the TOU windows the heater marked, the
metered draws, the circulation and the air. It is scored on what a
scheduler compares plans by: the day's electricity and when in the
day it fell, the order of days by their electricity, the
compressor's and the elements' starts, the upper probe's lowest and
the tank at the day's end (Events.days, theday_*metrics).
sim.record.Recordcarries the lower probe's stop threshold
(hp_lower_off_f), whichsim_adapters.recordreads. -
A simulation goes on from where another ended. A scheduler that
stands the simulator in for a heater calls it once a step, and each
call began as a mode entry: a running compressor's hold, lockout and
pending start, an element's dead time and the heat pump's
unavailability were taken afresh, and the heating intervals running
at the cut were cut.SimulationResult.end_state,end_history
andend_settingsare the heater at the end: the probes and
layers, what runs, andHeaterState.resume, the JSON text of a
versioned document of the controller's state. Passed as the next
Scenario'sstate,historyandsettings, the walk goes
on: chained at marks of the clock's 10-minute grid, the calls give
the path and heating of one walk with marks at the cuts, through
codecand JSON too, with TOU and the climate's mains across
midnight. A
cut off the grid splits a walk's step (0.002 °F after two days of
15-minute calls). A setpoint or mode other than the one the last
call left is a write at the start (serves="settings"), its
setpoint held and range-checked as an app's write is. Into or out of
Vacation or Power Off, which the simulation holds no rule for as a
write, the call starts afresh from the tank and the history and says
resume_dropped_heating_disabled_changed.engine.simulate
takesresumeandSimulation.resumegives it
(engine.Resume,to_document,from_document); one from
another instant, or of another version, is refused. -
The mains by the day, from the climate.
cold_supply.mains_f
is Burch and Christensen's correlation of a utility's water with the
year's outdoor air (its mean, and its warmest monthly mean less its
coldest), andcold_supply.climate_ofreads that climate off a
year of outdoor readings.DrawParams.climate_fcarries it:
DrawParams.mains_on(day)gives that day's mains, else
inlet_temp_f. A scenario without an inlet meets each draw with
the mains on its day, as a draw window's deliverable gallons and
time_to_ready's walk read them, anddefaults_usednames the
start's; the history's replay, the
tank's fits andreadiness(..., on=day)take each day's. At one
installation (mean air 61.3 °F, swing 19.9 °F, its weather
station's 2025-11 to 2026-10) the correlation read 1.5 °F under its
inlet probe's plateaus on 25 days of 2026-09-20 to 10-28, and 0.8 °F
under from 10-20 on; September's 72.4 °F held all year was 4.4 °F
over by 10-28.site_example_paramscarries that climate
(SITE_CLIMATE). -
The tank's layers from a recorded history, for any caller.
sim_adapters.layers_from_historyandlayers_from_changesgive
HeaterState.layers_fat an instant from a heater's statuses or its
values' changes, the household's flow, the circulation's runs and the
air: the last 24 hours walked through the tank (nwp500.sim.history:
record,replay), corrected to the probes as they were read.
sim_home_assistant.HomeAssistantTank.layersgives them from Home
Assistant, and
nwp-cli simulate runstarts a scenario that starts now from them,
with the scenario's probes laid over them: before, it started from
the two probes alone, astank_profile_estimatedsaid. -
A tank's layers as where a simulation starts
(HeaterState.layers_f, as many asTankParams.layers, the
bottom first). Two probes do not say how the water between and below
them is layered: without the layers, the simulator makes them of the
probes and saystank_profile_estimated.tank_fit.start_state
gives them from a recorded history, andsim_score.score(..., record=...)starts each event from them. -
Where the condenser heats, fitted.
tank_fit.CONDENSER_TERMS
(TankParams.condenser_span's low end and width) is fitted on
recoveries (sim_score.layer_recoveries: the compressor from its
start, its hold by time off, and the circulation through it) together
with the tank's other terms on the quiet windows and draws
(sim_score.fit_layers). Fitted apart, the span that times the
recoveries left the bottom of the tank each window starts from wrong:
held out, a coast's lower probe went from 0.52 to 0.86 °F. -
The layered tank fitted to a history (
nwp500.sim.tank_fit).
Two probes do not say where the water below the lower one is, so
each event starts from the layers the history before it leaves
(replay): a day of the recorded heat pump, element, draws and
circulation walked through the layers, corrected to the probes every
ten minutes.fitfits the tank's terms (TERMS) by robust least
squares on the probes over the record's quiet coasts and the hours
after its draws; it takes amap_to spread the events over
processes.nwp500.sim_score.fit_layersandscore_layersfit
and score it on a history's events withscore's metrics.On the held-out days, both models given the heater's mixing valve and
circulation, the fitted layered tank tracks a coast's upper probe to
0.189 °F (the two-zone model 0.299) and its lower to 0.519 °F
(0.630), each interval clear of the two-zone value, and the probes
after a draw to 0.310 and 2.379 °F (0.281 and 2.161, inside the
intervals). -
The layered tank's probes and ends. Each probe reads the layers
around its height, weighted by distance over its spread
(TankParams.upper_probe_spread_gal,lower_probe_spread_gal),
and the lower one readslower_probe_offset_fabove them. The top
layer loses heat throughLossParams.ua_top_btu_hr_f, and the
sides throughua_side_btu_hr_f. -
nwp500.sim.physics.stratified: the tank as stacked layers.
Twelve layers by default (HPWHsim's model), the bottom first, each
mixed within itself, with operators that conserve energy and never
take a layer past what drives it:mixturns inversions over;loserelaxes each layer toward the air exactly, the bottom
layer through the bottom's UA too;conductconducts between neighbours;add_heat, with the condenser's heat spread over the span it
wraps and an element's into its layer;flowpushes water in at the bottom, through an inlet zone that
mixes it in as it comes, and out of the top as a plug;drawtakes a delivered draw through the mixing valve.
The probes read the layers (
observe);estimateand
assimilatebuild or correct a profile from them, and
deliverable_galis the tap's gallons to the line.walksteps
the tank through time on a clock-anchored grid, cuts it where a stop
holds, and books every term's energy, which closes. A coast never
falls below the air, an element's heat is its electricity, and 100
gal drawn in an hour empties the top. Its parameters are new
TankParamsfields (layers, the probes' and elements'
heights,condenser_span,inlet_mix_gal,
conductance_btu_hr_f) andLossParams.ua_bottom_btu_hr_f,
priors until fitted (tank_fit). -
The heat pump's operating range. It runs only with the air at
41-113 °F and the tank's lower zone at 50-149 °F
(ControllerParams.hp_air_range_f,hp_water_range_f, from the
user's manual). Outside them a running compressor stops and the
elements heat the tank by Electric's rule; back inside, a recovery the
elements were carrying returns to the heat pump when it has room to
heat. A recovery stops at 149 °F, and the elements finish a setpoint
above it.forecast,active_cycleandtime_to_readysay
heat_pump_unavailable;next_recoveryisunavailable
(Recovery.hp_unavailable), andtime_to_readyprojects every
mode as Electric, Heat Pump mode from the heater's own trigger. A walk
that relied on the elements' takeover says
envelope_fallback_unverified, since none is on the record. A
recovery with its air outside the span its heat was fitted on says
heat_map_out_of_domain.AmbientSampler.outsidefinds the air's
spans outside a range, on the readings given
(TankPhysics(air_readings=...)). See
:doc:/explanation/simulator-methodology. -
nwp500.sim_score: parameters scored on recorded events they
were not fitted on. It takes the events from the record without a
model (recoveries that reached their target, quiet windows, a meter's
isolated draws, and its clean draws with the compressor off, against
the gallons each delivered before the top fell below 105 °F), predicts
each through the public simulator, and reports each metric's mean
with a 90 % interval from a bootstrap of local days. Two parameter
sets scored on one history are scored on the same events, so models
that share no terms can be compared (compare). It does no I/O.
See :doc:/how-to/calibrate-simulator. -
nwp500.sim.heat_pump_fit: a heat pump's recoveries measured
from a history, without a model of the cycle.recovery_frame
puts each recovery (sim.calibration.heat_pump_cycles) on a minute
grid in three phases: the hold after a start, the climb, and the run
past the target.start_holds,run_ons(with a fixed run-on and
a stop at an overshoot judged against each other),climb_heat
(the heat a mixed, draw-free climb puts into the tank) and
power_blocks(the metered power in 10-minute blocks) measure
from it.evidencegathers them, andfit_heat_pumpfits the
heat's and the power's maps and the run-on on them
(with_heat_pumpapplies it). The helpers that read a history
frame moved tosim.history_frame.physics.heat_pumpreads the
map. -
nwp-cli simulate calibrate --holdout-from DATEfits on the
history beforeDATEand prints the base's and the fit's scores on
the events after it. -
sim_calibrate.prepareandprepare_changes: a history as
the fits read it, the frame a scorer reads. -
The setpoint in a recorded history.
sim_adapters.historyand
change_historycarry asetpoint_fcolumn;
HeaterEntities.setpointnames its entity, andChangeRowshas
asetpoint_fseries. -
ChangeRows.untilandHeaterHistory.until, the mirror of
since: a history split at an instant is the two.
Changed
-
The tank is calibrated on the history's last seven weeks.
sim_calibrate.calibrateandcalibrate_changesfit the tank's
terms on the quiet windows, draws and recoveries starting in the last
tank_weeksof the history (TANK_WEEKS, 7), the history before
them read only as their replay's lead-in;tank_weeks=Nonefits on
every event, as before. The heat pump is still fitted on the whole
history.nwp-cli simulate calibrate --tank-weeks WEEKSsets it (0
for the whole history), counted back from the history's last status
(with--holdout-from, the last before it). On one site's record,
the tank fitted on its events in the seven weeks before 2026-09-18
scored better on the next 29 days than fitted on the five months
before: 13.4 against 16.1 minutes off a recovery, 3.1 against 3.8 °F
off the day's end, 10.1 against 11.5 % off its electricity, 0.82
against 0.79 of the compressor's starts placed; the probes after a
draw and in standby 0.02 to 0.13 °F worse, the day's lowest upper
probe 2.5 against 2.2 °F off and a recovery's electricity 4.9 against
4.3 % (calibrate-simulator). -
The probes' lean stops at 0.75 of the height. A profile built from
the two probes (stratified.estimate) and the replay's correction
toward them (assimilate) ran linear from the lower probe's height
to the upper's; they now run linear to
TankParams.correction_top_height(0.75, not fitted; the upper
probe's height if lower) and level above it. It must be above the
lower probe; the tank fit (sim.tank_fit.fit) keeps the lower
probe's height at least 0.01 under it. At the upper probe's height the
water just under that probe took a cold lower probe's correction:
three large held-out draws in an evening window without heating, two
from tanks with the probes 22 and 40 °F apart, dropped the walk's
upper probe 2.9-9.8 °F against the record's 1.1-3.1. Over the site's
29 held-out days (measured 2026-11-05 atDrawParams.meter_reads
0.85 on the parameters in use, not refitted) the upper probe through
draws reads 0.33 °F RMS against 0.37, the recoveries 13.4 min off
against 14.1 (the draw-free 3.5 against 4.2), the day's electricity
12.8 % off against 12.9, the compressor's starts as before; the
elements start twice more over the 29 days, the recoveries'
electricity is a mean 2.7 % low against 1.6, and the lower probe
through draws reads 1.71 °F RMS against 1.68. A walk without layers
reads the water above the lean's top level, a little above the upper
probe (0.03 °F per °F between the probes), so it crosses the line at
once: on the defaults, with the upper probe 0.5 °F under the line,
nothing is promised with the probes up to 16 °F apart and the 15.8 gal
above 0.75 from 18 °F, against 6.6-9.2 gal before. -
A gallon of hot water holds less heat. Every heat in the
simulator took water at 8.34 BTU per gallon per °F, the mains'
value; hot water is lighter, 8.24 at 120 °F and 8.20 at 140, so the
tank held up to 2 % more heat than it does. The new
physics.waterhas the heat per gallon by temperature
(btu_per_gal_f, Kell's density and IAPWS-95's heat capacity to
0.002) and keeps each layer's heat asheat_f, the temperature
water of a constant 8.34 would have with it, so mixing, flows and
heat put in stay linear and conserve energy. The loss, the
column's conduction, the delivered water's mix, the pipe's loss
(stratified.Flowing.pipe_ua_btu_hr_freplacesshare;
circulation.steady_sharetakes the water's temperature) and the
heat fits (heat_pump_fit.climb_heat,calibration's run heat
andmeasure_pipe) read it. The 65's heat map and loss, fitted in
BTU against the constant, are restated at their reference water
(defaults._restated): 4,445 BTU/h at 65 °F air and 120 °F water
(from 4,499), -16.7 per °F of water (from -15.9); refitted under the
water's heat the map lands within 0.1 % of that. The site's pipe
measures 14.4 BTU/h·°F (from 14.6). The element's metered power
stands, so it heats hot water faster: the goldens' element runs end
4 s to 2.2 min sooner, and the golden 8 h coast starts its heat
7 min sooner.simulatetakes about 20 % longer. -
A simulation's cost grows with its span. A coast walked the tank
to the next fixed event, often the horizon, to find the heater's own
start, then again to the start; and every step of a walk scanned all
its draws, flows and change instants, and the trigger search every
drop for every segment. A coast now walks 2 hours, then each time
twice as far (to a 10-minute mark of the clock, where a split walk is
the walk whole), and stops once the heater's own start is triggered,
which is then the start the whole walk finds; the draws, flows,
instants and drops are found by bisection. On one NWP500-65's
parameters with about 30 draws a day,simulateover 14 days takes
392 ms against 2.6 s in Heat Pump mode and 441 ms against 4.8 s in
High Demand. A coast of 224 days with draws and a circulation run
four times a day takes 1.9 s against 46 s. The answers are the same. -
A circulation run returns its pipe, then the tank's own water a
little cooler. A run was priced on an effective volume (19.36 gal
at the site) back at the return's rest, then the tank's own water as
hot as it left; a run held on for 35.7 min after a 4 h rest on
2026-10-29 brought the pipe's water back for 5 gal, rose half way to
its steady value at 6.86 gal and held 1.6 °F under the upper probe at
1.08 gal/min.CirculationParams.displaced_galis now that measured
volume and the newpipe_ua_btu_hr_fthe pipe's loss: the run's
own water comes back at the rest pluscirculation.steady_share
of the top's lift over it (exp(−UA / ṁc)), in the walk
(tank_physics.circulation_flows,stratified.Flowing's new
rest_fandpipe_ua_btu_hr_f), the tank's fit and
run_heat_btu.
calibration.measure_pipemeasures the two on such a run, and
fit_returnfits the return's rest and cooling given them (3.74 °F
and 0.72 h at the site, on 122 runs). The run's readings are a test
fixture,circulation_run_2026-10-29.json. Fitted before
2026-09-18 and scored after, a recovery's duration comes out 27.2
min off against 30.9 (5.45 against 5.69 with no draw) and its energy
4.4 % against 4.5. Refitted on the whole record with it, the site's
tank fits to 2.26 °F RMS (from 2.50): the bottom's loss 0.445 to
0.559 BTU/h·°F, the condenser's span 0.148-0.323 to 0.154-0.289, the
lower probe 0.469 to 0.476; its power map takes +1.02 W per °F of
air and +2.72 per °F of the condenser's water (12.7 W RMS, from
13.0). -
Water warmer than the tank's inlet zone rises past it. The
layered tank took every flow in at the bottom and pushed the column
up as a plug, so a run's warm return lifted the cold water the pipe
had brought back through the whole tank, and the upper probe of the
run above read 2.0 °F RMS off. Water warmer than the inlet zone now
goes in where the column is as warm and pushes out only the water
above (stratified.flow); on that run the upper probe reads
0.8 °F RMS off, and the tank gives up the heat the return line
measured but 3.2 %. Only circulation flows this way; draws do not. The
lower probe, at its fitted height (0.48), cannot read the 105.8 °F
it fell to on that run: no stable column with a probe that high
holds so little heat below it, which places the probe lower than
the tank's fit does. -
The upper element's height, documented. An element's heat turns
its layer over into the ones above, so its height acts in whole
layers: of twelve, the upper element's 0.72 heats 21.1 gal and climbs
the upper probe 1.59 °F/min, where the record's 29 clean runs climb it
2.01 (0.75-0.833, 15.8 gal, gives 2.00). The record's 24 element runs
from 2026-04-16, most with water drawn or the compressor running,
each walked from its replayed layers, read the upper probe to 7.9 °F
RMS at 0.72 against 10.2 at 0.79, so 0.72 is kept;
TankParams.upper_element_heightand the simulator's docs say so,
and no longer call it fitted. -
The heat pump's maps are read along their planes. The heat's and
the power's maps were held at the edges of the span each was fitted
on: a 45 °F air heated as 61 °F does, the condenser's water below 114
°F got the heat at 114 °F, and the heat and the power, held at
different water, gave a COP that bent where neither was measured.
physics.heat_pumpnow reads each along its plane wherever the
heat pump runs, the walk holding its reads to the controller's
envelope (stratified.Layout's newmap_air_fand
map_water_f: a cold bottom can leave the condenser's water under
the envelope the controller judges at the lower probe);heat_map_out_of_domainstill marks a recovery
whose air is outside the heat's span, its heat the plane carried past
the record. A fit's slopes take the sign the cycle gives them (the
heat rising with the air and falling with the water, the power rising
with the water;heat_pump_fit.HEAT_SLOPE_SIGNSand
POWER_SLOPE_SIGNS): the plane is the least-squares one so
constrained, the best of those with each slope free or pinned at 0
that keep every sign.heat_pump_fit.violationschecks a map over
the controller's envelope (41 to 113 °F air, 50 to 149 °F water; its
newair_fandwater_f, andfit_heat_pump's, which both
sim_calibrateandcalibration.calibratepass from the
parameters' controller) rather than its
domains, its COP against the ideal heat pump's between the
evaporator's coil and the water (heat_pump.refrigerant_carnot_cop,
the coilEVAPORATOR_APPROACH_F, 20.5 °F, under the air: as close
as it ran in 90 % of 145,286 steady running minutes on the record).
Against the coil as read, the site's map runs a steady 0.43 of that
ideal and never over 0.46; the check is still 0.6. The site's tank is
refitted with the maps so read (the bottom's loss 0.168 to 0.445
BTU/h·°F, the condenser's span 0.143-0.351 to 0.148-0.323, 2.52 to
2.50 °F RMS over the events), and its power map on that tank (408.9 W,
+1.03 per °F of air, +2.70 per °F of water). Held out after
2026-09-18, the 11 draw-free recoveries' duration error went from 6.9
to 5.7 min and their electricity's from 5.0 % to 4.5 %, its bias from
0.4 % high to 0.6 % low; the 16 with draws went from 47.2 to 48.2 min,
and the lower probe after a draw stayed at 2.16 °F RMS. -
The power is fitted where it is read. The walk reads the power's
map at the condenser's water, which is below the lower probe the map
was fitted on (4.7 °F on the record's climbs on average): read there,
the map priced them at 407.2 W against the 418.2 W metered.
sim_calibratenow reads each metered block at the condenser's
water on the record's replay (heat_pump_fit.in_condenser_water,
overtank_fit.condenser_water, one replay through the record: the
water each step read the map at, after its draws and the start's
stir, weighted by the hours it ran,stratified.Books' new
compressor_handcompressor_water_f_h), and the power's plane
is fitted there;power_blocks' new
water_fis the water a block is read at, the lower probe's with
no record to replay, andcalibration.calibratetakes the
evidenceto fit on. The site's map is refitted so (408.6 W at 65
°F air and 120 °F water, +1.04 per °F of air, +2.70 per °F of water,
3,562 blocks, 2026-04-16 to 10-26, 12.9 W RMS); its air span is 53
to 90 °F, the runs before 2026-04-16 having no record to replay. On
the 11 held-out draw-free recoveries the electricity went from 2.7 %
low to 0.4 % high on average and its mean absolute error from 4.5 %
to 5.0 %. The heat's map is left on the mixed climbs: on the record's
replay a draw-free recovery's layers gain 1.006 times the heat the
map credits, and a refit on the probes through the replayed layers
lengthened the held-out draw-free recoveries' error from 6.9 to 7.6
min or more. -
What a recovery gets wrong, documented.
:doc:/explanation/simulatorgives two errors in a heat-pump recovery
that partly cancel. After 40 minutes or more of heating the upper and
lower probes read about 0.5 and 0.9 °F higher than at rest, and the
model's probes carry nothing for it, so the stop, judged against an
overshoot measured on the reading, comes late. The power was read at
the condenser's water: 392 W against the 417 W metered, pooled over
the 11 held-out draw-free recoveries. Modelling both cut those
recoveries' bias from 6.6 to 2.5 min but raised their mean absolute
error from 11.9 to 12.7 min and the electricity's from 5.7 % to 9.9 %,
so the offset is not applied; the power is now fitted at the
condenser's water (above). :doc:/explanation/recovery-speedgives the
probes' step at a quiet stop, and that the heat pump cools its room a
little: the air rose 0.49 °F less on average over a run than over the
same hours on nearby days with nothing running (90 % interval
0.04-0.95 less). -
Another installation's mains are the rating test's 58 °F
(defaults.RATING_INLET_F, 10 CFR 430, Subpart B, Appendix E), in
default_params, in place of one installation's September 72.4 °F:
colder than most of the year's mains away from the northern tier, so
its draws err the safe way. GiveDrawParams.climate_fto place
them by the day. -
Hot water at the tap from the tank's layers (breaking).
Deliverable gallons (delivery.deliverable), the delivered
temperature along a walk (delivery_along), draw windows and
readinessread the tank's layers (HeaterState.layers_f):
the water leaving the top in turn, each gallon through the mixing
valve, to the first the valve delivers under the line, the mains at
the scenario's inlet. Without layers they read the profile the two
probes stand for and saytank_profile_estimated.
Deliverable.basisis"layers"or"probes".
readinesstakesinlet_f.time_to_readywalks the scenario
in each mode, the mode written at the start, for up to a day
(READY_HORIZON), to the first instant its layers deliver the
bath, the energy the walk's to then, under the TOU windows the heater
keeps; the heat pump's walk's reasons are the answer's, and an
element mode's row carries what its walk adds to them. It raises
ValueErrorfor a setpoint written out of range, assimulate
does. On 34 deliveries held out of the fit, none of
which crossed the line, the replayed layers under-promised 3 % of
them, by 1.17 gal on average, against 3 % and 0.91 gal from the
probes alone. Of the three draws in the record that took the upper
probe across the line, the layered tank as first fitted crossed the
probe at 26.1 and 31.3 gal against the recorded 23.3 and 31.3 (the
third started below the line).
sim_scorescores deliveries from the replayed layers. -
The tank's keeper from Home Assistant is
HomeAssistantTank
(wasHomeAssistantBook): it holds the replay's 24 hours
(TANK_WINDOW) and no longer reads the inlet or the draw demand.
nwp-cli simulate runreads it withread_tankand says when a
scenario that does not start now walks from the two probes. -
A history's steps are
steps_from_historyand
steps_from_changes(werebook_steps_from_*), on
STEP_GRID_S(wasBOOK_GRID_S). A draw is a flow over
DrawParams.draw_gpm(0.3 gal/min). -
A parameters document fitted with the heat book still reads. Its
bookpart and its readinessevidence,delivered_lag_gal
andfall_f_per_galare named byunknown_fieldsand left out,
its book'sdraw_gpmread asDrawParams.draw_gpm;bookand
readinessin itsunfittedare dropped (RETIRED_TERMS).
A result document holds unbounded gallons (a mains at or above the
line) as null. -
A recorded history's
Steplives innwp500.sim.history,
withhouse_gpm,FlowVolume,AMBIENT_DEFAULT_Fand the
replay's span,REPLAY(fromsim.tank_fit, which still imports
them). A step says when it starts (Step.at), which
steps_from_historyandsteps_from_changesfill. -
Calibration fits the layered tank (breaking).
sim_calibrate.calibrateandcalibrate_changesfit the tank's
terms (tank_fit.TERMS: the sides', bottom's and top's losses, the
column's conductance, each probe's height and spread, the lower
probe's offset, the inlet's mixing) on the record's quiet windows and,
given a meter'sdraws, its draws, with where the condenser heats
(CONDENSER_TERMS) on 5 recoveries or more; then the heat pump, its
heat priced against the fitted tank's loss. The tank needs 10 windows
and draws (MIN_TANK_EVENTS) and the air.Calibrated.tank
(TankCalibration) says what its fit saw, andmap_spreads its
trials over processes (nwp-cli simulate calibrateuses one per
core).keep_betterjudges the tank as one group,tank, on the
latest fifth of its events (tank_fit.event_rmse), in place of
standbyandgap_tau, andHeldOutholds those events
(tank,record). The report's tank rows replace the loss law's
and the gap's.HeaterParams.unfittedand the reasons name
tank(tank_uncalibrated) in place ofstandbyand
gap_tau(standby_uncalibrated); a document of the two-zone
tank's reads withtankunfitted, its tank the priors'.
sim.calibration.calibratefits the heat pump alone, and
Calibrationhas nostandby.heat_pump_fit.climb_heatand
evidencetake theHeaterParamswhose tank prices the loss.The defaults' tank is that fit on one NWP500-65's record, 2026-04-16
to 2026-10-26 (266 quiet windows, 254 metered draws, 118 recoveries):
the sides 2.89, the bottom 0.89 and the top 0.68 BTU/hr per °F, the
probes at 0.890 and 0.478 of the volume. Calibrated on the record to
2026-09-18 and scored after it, a draw-free recovery's duration is
out a mean 11.8 min (7.3-16.6; the two zones 7.9), its electricity
5.8 % (7.2 %), a coast's upper probe 0.255 °F (0.299) and its lower
0.548 °F (0.630), the probes after a draw 0.290 and 2.09 °F (0.281
and 2.16). Started from the priors, the fit went from 6.92 to 2.68 °F
RMS in 25 trials and scored within each of those intervals. The
record does not split the bottom's loss from the sides' well: the
bottom came out 0.89 to 2.35 across the fits. Refitted against the
layered loss, the heat map moves 0.05 % at 65 °F air and 120 °F
water and under 0.2 % across its span, and the shipped one stays. -
The simulator walks the layered tank (breaking).
tank_physics.TankPhysicsis the stratified tank
(physics.stratified) its parameters lay out, and the engine walks
it through everything at once:Physics.advancetakes the tank from
one instant to another with what is running (Running: the
compressor from its start, heating after its hold; the elements, their
heat on its way for their dead time after they stop; credited none of
it inside a withheld draw window), taking each draw at its instant and
the circulation's runs as water through the tank, and stops where the
engine asks. A recovery runs until the lower probe reaches its stop;
the engine no longer places a recovery's integrated climb on a draw's
response. Elements put their electricity into the water: the two
zones had credited them 1.81 and 1.24 times it. A draw takes its
water's heat, and 100 gal in an hour now empties the top.A
RecoveryModel's duration is served as the pace of the heat
pump's heat for that recovery (heat_pump_recovery.rate_scale,
corrected once on a walk at that pace), so its path is the layers'
at the model's speed.RecoveryContextcarries the layers
(layers_f) and has nodraw_load;PhysicsRecoveryhas no
path.TankPhysics.recoveryis the physics' climb,
recoverthe served one with its trajectory
(heating_trajectory.from_walk),scalethe pace alone and
layersa state of given layers.forecastandactive_cycle
walk the layers too; a recovery short of its target after
RECOVERY_BOUND(16 h) is refused, andsimulateruns a heater
that cannot reach its target on, as a real one does.Held out from 2026-09-18 on one NWP500-65, through
simulateand
standby, each event started from the layers its history left and
the layered terms fitted on the days before: a draw-free recovery's
duration is out a mean 12.3 min (7.6-17.3; the two zones 7.9), a mean
7.2 min long, its electricity 5.8 % (7.2 %), one with draws in it
47.0 min (43.6); a coast's upper probe 0.261 °F (0.299) and its lower
0.552 °F (0.630); the probes after a draw 0.289 and 2.078 °F (0.281
and 2.161). Started from the two probes alone, a draw-free recovery is
out 15 min.The golden walks moved: a coast holds its upper probe longer, so the
TOU opening's start comes later; an element runs until its own heat
brings its zone to the off point, longer than the two zones'
credited heat did, and Electric's elements run upper then lower; a
shower's draws take the lower probe further, so a start inside the
TOU window waits for its close, and a 26 gal draw at a window's close
starts a recovery. -
The engine carries the tank as its physics' state (breaking for
a customPhysics).Physicsgainsestimate(the state two
probe readings stand for) andobserve(the probes on a state),
and itsadvanceanddrawtake and give a state
(TankState) in place of the two zones' temperatures; each
Pointcarries the state it reached. The engine reads the tank
only throughobserve. -
The heat pump is a performance map (breaking). The heat into the
tank and the electricity drawn are each a plane in the air at the
heater and the lower tank's water (CompressorParams.heat_btu_h,
power_wand their slopes), fitted on the heater's own recoveries
and each read inside the span it was fitted over
(heat_domain_*,power_domain_*). The condenser gives what
the evaporator takes plus the electricity, by construction; a fit
claiming a COP over 0.6 of Carnot's, heat given to the air, or under
50 W anywhere in that span is not applied. It replaces the R-134a
cycle:physics.refrigerant, the compressor constant, the
evaporator's approach and slope, the condenser approach, clearance,
exponent, airflow and latent load, andtwo_zone. Held out from
2026-09-18 on one NWP500-65, a draw-free recovery's duration is out a
mean 7.9 min (13.6 before) and its electricity 7.2 % (12.8 % before,
a mean 8.7 % low; now 1.5 % low). See
:doc:/explanation/simulator-methodology. -
A recovery holds at its start and runs on past its target. At
the instant the condenser's heating begins the walk stirs the water
it wraps with the cold water below it (physics.stratified.stir,
byLayout.stir_weights), the same heat spread over the pool, and
the lower probe holds until the condenser has heated the pool back
past its reading: longer the longer the compressor was off and the
colder the bottom has grown, as the record's holds are (0 to 22 min),
with nothing fitted to them. A compressor already running at the
walk's start is not stirred again (insimulate,active_cycle
andforecast) unless some of arunning_holdis left or
simulate's newrunning_startssays it starts there (a
recorded start replayed, assim_scoredoes), nor one that stops
before its heating begins.forecastkeeps a running compressor
going, ready early, where stopping it and starting again later (a
start, stirred) could not be ready by the target. On the record's 84 clean starts the
model holds a median 3, 23, 18, 18 and 21 min after under 15, 60,
180 and 720 min off and beyond, against 0, 5, 10, 18 and 22; the
upper probe holds still through the record's holds, as the model's
does.CompressorParams.start_hold_minimposes no hold by default
(it was 0, 5, 10, 18 and 22 min, credited no heat, and cost a start
after a day off 165 Wh and 3 °F of tank); a caller'sstart_hold
orrunning_holdstill applies, andsimulateuses it with or
without aRecoveryModel. Fitted and scored on the record
(2026-04-16 to 10-26) with the stir and no hold, against the model
with the hold: the held-out draw-free recoveries' mean absolute error
11.9 to 8.7 min and their electricity's 5.7 to 4.4 %, the standby
probes' RMS 0.254/0.564 to 0.202/0.534 °F, the draws' lower probe
2.08 to 2.28 °F, the recoveries with draws unchanged. The climb
runs toControllerParams.hp_stop_overshoot_f(0.9 °F) past the
target insimulateandactive_cycle, and inforecastto
the heater's own threshold (not a partial charge or a TOU cap's
level); aRecoveryModelis asked with that stop as its
target_f. Recorded durations already ran from start to stop, so the old
compressor constant had absorbed both. -
Parameters are
heater-params/2(breaking). A
heater-params/1document is refused: calibrate again with
nwp-cli simulate calibrate. -
sim_calibrate's groups areheat_map,power_mapand
run_onin place ofcompressor,power_scaleandcop, each refitted and judged on the latest
fifth withkeep_better.Calibration.heat_pump
(heat_pump_fit.HeatPumpFit) replacescompressorandpower. -
A recovery's trajectory and COP are the map's. The electricity is
the map's power over the run; the COP is the map's heat over it. -
element_mode(scenario, params, *, mode, setpoint_f=None, ready_upper_f=None, switch_back_after_minutes=None, slot_minutes=10.0, recovery=None)walks the scenario with the mode written at its
start, astime_to_readywalks each mode: to the end of the
recovery the write sets off (every element and the heat pump stopped)
withinREADY_HORIZON, or to a switch-back. It takes aScenarioin place of
state,settingsandat, and nohp_lower_minutes: the
heat pump heats the layers in the same walk.ElementModeResult
carries the walk's times, the elements' minutes and kWh, the heat
pump's kWh (heat_pump_kwh), aprofileofElementInterval
slots with both, the tank at the end with its layers, and
reasons;planis gone. A recovery part of which a later mode
write or the heater's drop at a window stops, or whose compressor a
setpoint write stops short, has nofull_minutes
(recovery_cut_short), and a write of the mode already in force
saysmode_already_in_force. From 125/110 °F at a 140.9 °F
setpoint the High Demand and Energy Saver recoveries end 127 and 128
minutes out, with the heat pump's, which the element model left
unknown; Electric's in 45. -
calibration.fit_element_ratesandElementRateFitare
fit_element_wandElementPowerFit, the element's power alone;
ElementLagFit.upper_f_per_minisrise_f_per_min, a
measurement no parameter reads.
Fixed
-
The record's replay (
tank_fit) heated every element minute in the
upper element's layer, the lower element's too:TankRecordnow
holds the lower element's runs (lower_element, from a frame's
lower_element_on, which the history frames andStepnow
carry), and the replay heats those in the lower element's layer. On
one installation (2026-04-16 to 10-26) the lower element ran 11 times,
62.8 min; the record's 24 element runs, each replayed from the layers
at its start, read the upper probe to 4.4 degF RMS against 8.2 and the
lower to 6.8 against 8.7. The recoveries' energy before 2026-09-18
comes 20.1 % off against 20.9; nothing held out moved. -
sim_scoresplits every draw the detector times that is under way
at an event's start, where it split only one at
DrawParams.undiluted_gpmor faster. A slower one was replayed
whole at its onset, the start's probes laid over the replay, and the
event's walk did not take the rest: a recovery starting 89 s into a
10.9 gal draw was walked without 9 gal of it. The rest is now all at
the start, as the score walks a slower draw at its onset. Measured
2026-11-02 on the parameters fitted before 2026-09-18: of the 116
recoveries 2026-04-17 to 10-26, 44 had a slower one under way. Held
out, the 16 with water in use came out 18.3 min off (12.3-25.3)
against 21.2, a mean 1.7 short against 13.1; before 2026-09-18 the 79
came out 27.7 min off (21.5-35.3) against 25.3, a mean 25.2 long
against 9.2. The draw-free and the electricity did not move, nor the
days: no local midnight of the record falls inside a timed draw. -
A recorded recovery is walked with each change of the setpoint inside
it as a write at its instant (sim_score.Recovery.setpoints), where
it was walked to the setpoint in force at its start; a restart a write
causes before the recorded end continues the scored run, as the heater
ran on through it. One raised from 110.3 to 141.8 °F 3.5 min in had
been scored 284 min short; on the site's held-out record, the 16
recoveries with water in use came out 31.4 min off against 42.5. -
The 50 and 80 gal defaults resize the 65's tank to theirs
(defaults.sized,defaults.tank_column_in), where they scaled
only the volume. The manual's outside dimensions give each model's
water column: the 50 is narrower than the 65 and as tall, the 80 as
wide and taller. The sides' and bottom's loss, the axial conductance,
the probes' spreads and the inlet zone follow the walls and the
cross-section. The tanks lose 3.55 and 4.74 BTU/h·°F (each was
4.12). The 50's first draw from a tank mixed at 125 °F to a 110 °F
line is 37.5 gal (34.3 before), 14.8 under the 65's against the
ratings' 15 gal of first-hour rating. The 80's is unchanged at 65.2
gal: 12.9 over the 65's, where the ratings differ by 5. -
The inlet zone is the same at every flow, and the methodology says
why: the record's draws of 4 gal or more all ran at 0.7-1.3 gal/min,
and with the gallons held the flow moves the lower probe's error
within about two standard errors. The measurement is in
:doc:/explanation/simulator-tried. -
:doc:
/how-to/calibrate-simulatorsays how a change to the
simulator is scored: the record's pinned span and split, the site's
installation (in code), and the code before the change scored the
same way beside it. :doc:/explanation/simulator's accuracy says
when it was measured. :doc:/explanation/simulator-triedadds the
circulation's earlier return forms, the upper element at 0.79, the
heat map fitted on the model's probes, the condenser's span fitted
on recoveries alone, and events started from the two probes. -
The new :doc:
/explanation/simulator-triedpage lists the simulator's changes
tried against the record and not kept, each with what it found and
where it is written up. The methodology says the layer count is part
of the fitted model: with the 65's terms held, 6 to 96 layers move a
recovery from 253 to 281 min and back, and 24 layers, refitted,
scored no better than 12 on the held-out record (recoveries 38.2 min
off against 37.7, both from the defaults' installation) and walk
1.55 times as long. A test holds the
default at 12. -
The methodology says why the air is an input and not a state: the
heat pump takes about 3,000 BTU/h from its room, which cooled 0.49 °F
on average over a run at one installation; at the heat map's air
slope that costs 0.6 % of the heat into the tank. A replay's air
carries it, and a forecast's as its anchor does. -
recovery-speedsays why the heat map stops at 61 °F of air: the
winter's 48,000 compressor minutes at 45-60 °F have no draw detector
behind them, and the heater's own flag, the only stand-in, reads the
heat 14 % high and its rise with the air a third flat on the months
the detector covers. The record shows no frost either. -
The methodology's "The air" said a recovery runs in its span's mean
air; every step reads the air at its start, coast and recovery alike.
The mean judges only the heat map's domain and is a learned
duration's context. -
A mark changes nothing. A mark, a resume cut or any event the walk
stops at read the tank there by the controller's rules, and some of
those rules read it otherwise along the path, so a mark moved 83 of
600 random walks' heating. Each now reads the tank the same way:- The upper element does not start where it would stop at once:
Energy Saver's 104.9 °F later start is over its off point, 1.62 °F
under a setpoint below 106.6 °F, so at an event it started and
stopped in the same instant and on the path never. There it starts
as High Demand's does, 4.1 °F short of the setpoint, as Electric's
does (controller.later_on); neither is measured there. - The compressor's lower trigger reads no nearer than the setpoint,
hp_stop_overshoot_fshort of where a recovery stops: over it,
a recovery started at each event and stopped at once
(lower_trigger_at_setpoint). The heater's own threshold is
104.9 °F on the record for setpoints of 106.7 to 149 °F. - A recovery stopped short of the trigger (by a write, say) is read
from just after its stop and starts again after the lockout. A
coast from the stop started nothing to its end, and one from a
later event did. - A start a coast's look-ahead finds past the upper element's own
start is not kept as a pending start: the element changes the tank
first. It fired at a TOU window's opening whatever the tank did. - The heat pump back in its range with too little left for it, the
elements finish the recovery as judged where it came back
(controller.Element.finishes), through the upper element's
handover to the lower, not again at each event; a setpoint or mode
write calls for heat anew and has it judged again.
On 1,500 random walks, hourly marks now move no start or stop by a
second or more (a crossing is interpolated a fraction of a second
apart), and marks every 20 min one start in 300 by a minute, where a
draw lands each minute and which of them crossed the trigger is read
to the step. Marks off the minute grid split a walk step, which moves
a slow crossing by up to 35 s. - The upper element does not start where it would stop at once:
-
A circulation known part way is known wherever a walk is cut. A
span starting afterLoads.circulation_known_untilcharged
nothing, judged for each walk, so a run past it was charged by a walk
begun before it and dropped by one begun after: a write that changed
nothing, a draw or a mark inside a recovery took it away. With runs
at 0.2 and 1 h known to 0.5 h, a write of the same setpoint at 0.7 h
left the tank 3 °F warmer at 3 h. Which runs are known is now judged
for each run: one starting byknown_untilis charged whole, one
after it is not, and the answer sayscirculation_unknownas
before.Circulation.overreturns the known runs, no longer None. -
A recovery from a cold tank says its heat is carried past the
record. The heat's map was fitted on mixed climbs against the
probes' mean, 114 to 144 °F on the site's record, and is read along
its plane below that, butheat_map_out_of_domainjudged only the
air. A 58 °F tank at 67.5 °F air heats at a COP of 6.35 over its
first hour and 4.45 to a 126 °F lower probe, its probes' mean 93 °F
over its heating, and said nothing. A recovery whose probes' mean
over its heating is outside the heat's water span now says
heat_map_out_of_domaintoo, insimulateand in the recovery
forecasts (forecast,active_cycle), as its mean air is judged
against the air's (stratified.Books' newcompressor_heat_h,
the hours the condenser's heat went in, andcompressor_mean_f_h).
simulatejudges it over the whole run where the compressor stops,
not each stretch an event cuts it into, and a walk going on from one
carries the run's heating so far (Resume); a walk that ends inside
a run judges what it heated.
From the heater's 104.9 °F lower trigger, at 67.5 °F air, a recovery
says so at setpoints of 117 °F and under and not at 118 °F and over.
The condenser's water the map is read at runs under the mean in a
layered tank (from that trigger to a 130 °F setpoint it starts at
102.1 °F and averages 114.3 °F, the probes' mean 121.9 °F), and is
not what is judged. The map is not changed: what the heat pump does
with water that cold is not on the record. -
A draw runs over its time. A draw was taken all at its instant,
anddraw_forecast.forecast_loadsdropped each expected draw's
time drawing, so a forecast shower emptied the top of the tank at its
onset.Drawtakesduration_s: the walk takes it at its onset
and at each whole minute before its end, each piece the gallons of
its span, which walks exactly as a meter's per-minute draws, and
forecast_loads(anddraws_first, whose draws carry their
seconds) gives each expected draw its own. A 60 gal shower over
30 minutes from a full tank at a 130 °F setpoint now starts the
compressor 12 minutes later than at once, and High Demand's upper
element 17 minutes later. A draw with no duration is taken at its
instant, as before.next_recoveryandtime_to_ready, whose
recovery forecast takes no draws, take the rest of a draw under way
at the recovery's start there, a recovery already running at the
call included; that one left out a draw at its start before, one
at an instant too. -
Electric heats again after its first run. A walk in Electric
heated only on entering the mode, so a tank left in it coasted to the
room and drew nothing (element_later_starts_not_modelled). Its
later starts are not on the record, so they follow the elements in the
heat pump's place: the heater's own triggers start them by Electric's
entry rule, the upper element then the lower, from the tank a draw at
that instant leaves, and the upper element starts again 4.1 °F short
of the setpoint, as High Demand's does (controller.later_start).
Each walk that uses them sayselectric_later_starts_unverified,
and so does one going on from such a run. Two days in Electric at a
130 °F setpoint from 128 °F now keep the upper probe between 124.9
and 132.05 °F, for 4.54 kWh with no draws and 13.55 kWh with a
household's, where
before they heated nothing and ended at 104.6 and 72.9 °F. Across 800
random walks on one NWP500-65's parameters, 427 with Electric in
force at some point change: 93 only in their reasons, 319 heat more
(a median +8.6 kWh), and 15 less, by up to 6.5 kWh, where the tank
was warmer when a later mode took over or the elements in the heat
pump's place answered a low setpoint less often (below). -
The elements answer the lower trigger no nearer than 4.1 °F under
the setpoint. They stop at the setpoint, not past it as the
compressor does, so withhp_lower_on_fat or near the setpoint
the trigger started them again at once: in the heat pump's place,
about 110 starts in two days at a 104 °F setpoint and a 104.9 °F
threshold, most of them seconds long. They now answer it at the
upper element's restart when that is lower (12 starts), and
standbyreads it there too, in Electric or with the heat pump
outside its range where the trigger would fire, its
lower_margin_fwith it (controller.lower_trigger_f). Of the 800
walks, 37 that never enter Electric change: 26 heat less and 10 more,
the energy moving −7.5 to +0.5 kWh (a median −0.15) and the end tank
up to 6.3 °F. -
The upper element's later start is found on a recovery's path.
High Demand's start of the upper element when the upper probe falls
short, and Energy Saver's, were found along a coast but only read at
the walk's events during a recovery, so a mark or a call's end moved
them. Across 800 random walks on one NWP500-65's parameters, 66
change: the element starts 0.2–60 min earlier in most and up to
650 min in a few, starts in 6 that had none, and the energy moves
−259 to +1249 Wh and the tank at the end up to 6.1 °F. -
An element in the heat pump's place runs to the horizon. The air
read past the walk's end ended its window there, so the element
stopped asheat_pump, often with a compressor interval of no
length after it; it now stops ashorizon. 44 of the 800 walks
change; the tank does not. -
TOU windows past the end. A scenario's windows were laid out to
its end, so a recovery running there was judged without a window
opening after it; they now reach as far past the end as a recovery
judges its cap (engine.RECOVERY_AHEAD, 16 h). -
tou_cap_projectedis said where the cap stops a recovery, in the
walk it stops in. It was said where a recovery's cap was projected,
so a walk ending before the cap, or one whose projection a draw
undid, said it of a recovery the cap never stopped. -
A start TOU defers past the walk's end stays pending, for a walk
going on from there;controller.TriggerDeferredhas no
horizon. No single walk changes. -
sim_scorereplays every draw before an event's start, and leaves
a delivery's own draw out. The replay an event starts from stops at
the start of the minute the event starts in.- A delivery: the meter's delivery starts and the detector's draw
onsets run on different clocks, the onset 5–10 s earlier and, in 3
of 34 held-out deliveries, in the minute before, so the replay took
the delivery's own draw from the tank it then scored. A draw whose
onset falls in the settle before a delivery
(calibration.DRAW_SETTLE_S, when the meter saw no flow) is now
left out. - A recovery: it starts at a compressor edge, any second of a
minute, and keeps the draws from its start, so a draw earlier in
that minute reached neither its replay nor its walk. It is now
replayed at that minute's start, then corrected to the probes the
recovery starts from, as one a minute earlier is. - A quiet window: a draw in its first minute, before its start,
was dropped. It is now replayed the same way.
No held-out metric moves on this heater's record.
- A delivery: the meter's delivery starts and the detector's draw
-
In Electric, an upper element a lowered setpoint stops at its new off
point hands over to the lower element, as one stopping at its own
does; the lower zone was left where it was. Not when a mode write at
the same instant changes the mode at once: the new mode's rule
decides; a mode write held to a TOU window's end decides nothing
then. -
A setpoint raise in Energy Saver written with a mode that a TOU window
holds to its end starts the upper element, as a raise alone does: the
held mode leaves Energy Saver in force. It started nothing until the
window closed. -
An element heating in the heat pump's place stops with
HeatingInterval.stopheat_pumpwhen the heat pump takes the
recovery back, notmode, soelement_modedoes not count that
handover as cutting its recovery short. -
An element stops where its zone reaches its off point. Its end
was set from its fitted rate alone, so where something else lifted the
zone too, it ran on at the setpoint, charged and lifting nothing. With
the compressor running, High Demand's lower element ran 7 min at
122 °F. An inverted tank, its lower zone above the upper, mixes in the
coast's first step: the upper element, on at 110 °F over a 135 °F
lower zone, ran 6 min at the setpoint, and a write or a boundary
mid-run moved its end. The walk now stops it where the zone reaches
its off point, taking an inverted tank's mix at once, with a book
attached as without. -
next_recoverytakes the draw that trips the trigger. A draw
at the heater's own start was left out of both the tank the recovery
starts from andcoast_draw_gal; one a second earlier was smeared
across its coast step. The tank is now the one the draw leaves, from
a coast run exactly to the start, and the draw's gallons are the
coast's.recovery_as_oftakes them off a copy kept past its start.
Removed
calibration.fit_displaced_gal,DISPLACED_BOUNDSand
DISPLACED_GRID_POINTS: the pipe's volume is measured
(measure_pipe), not fitted.- The element-mode planner:
physics.element.mode_recovery,
ModeRecovery,mode_plan,ModePlan,coast_f,
upper_rise_fandMAX_PROFILE_SLOTS(api.MAX_PROFILE_SLOTS
capselement_mode's profile);ElementParams.upper_f_per_min
andlower_f_per_min, which a document still carrying them is
read without (unknown_fieldsnames them); the reasons
energy_saver_later_starts_not_modelled,
upper_zone_coasts_short_of_the_setpoint,hp_lower_not_given,
lower_zone_not_projected_under_heat_pump,
switch_back_lands_before_the_recovery_finishesand
plan_ends_when_the_elements_stop_not_at_switch_back. - The heat book:
sim.book,BookParams,Booked,
full_tank_book,HeaterParams.bookandbook_terms,
SimulationResult.booked, and thebookarguments of
simulate,readinessandtime_to_ready;
sim_adapters.book_from_history,book_from_changesand
full_tank_book;sim_home_assistant.BookAnswer,
BOOK_WINDOWandHomeAssistantBook.book;
sim_calibrate.fit_book,BookTermsFit,MIN_BOOK_PAIRSand
MIN_RUN_PAIRS; calibration's book, zone-mix, empty-point and
evidence fits;cold_supply.on_the_mains;
circulation.CirculationLoss;CirculationParams.loss_btu_per_gal
andbook_circuit_gal;Recovery.coast_btu,coast_draw_gal
andhp_btu_per_h; the reasonsno_heat_book,
heat_book_aged,book_uncalibrated,
readiness_uncalibrated,upper_probe_stand_in,
capped_at_observed_volumeand
bath_ready_within_estimate_error. - The readiness evidence and the outlet lag:
ReadinessParams.evidence,
delivered_lag_galandfall_f_per_gal;
delivery.EXAMPLE_EVIDENCE,with_example_evidence,
probes_gal,booked_deliverable,zone_delivery_f,
demonstrated_gal,observed_max_galand
Deliverable.at_observed_max. The water above the upper probe is
the layered tank's own. - The two-zone tank:
physics.tankandphysics.compressor;
TankParams.lower_zone_shareandstratification_gap_tau_h;
LossParams.ua_btu_hr_f,ua_exponent,ua_ref_delta_t_f,
ua_baseline_btu_hr_f,bottom_loss_btu_hrand
bottom_gate_f(ua_side_btu_hr_fis the sides' own, 5.0
BTU/hr per °F unfitted);params.UA_REF_DELTA_T_F,
UA_MIN_DELTA_T_FandTAU_GAP_PRIOR_H. - The two zones' draw response:
DrawParams.observability,
reach_gate_f,reach_offset_f,reach_gal,
upper_f_per_gal,lower_gate_f,pool_f_per_hourand
ungated_observability;physics.draw.probes_after_draw,
lower_zone_drop_f,draw_energy_btu,LEGACY_TMV_DELIVERY_F
and theDEFAULT_constants of those terms and of the draws'
range. - The standby fits:
calibration.fit_standby,StandbyFit,
StandbyWindow,fit_window,fit_ua_law,fit_loss,
LossFit,loss_windows,standby_ends,standby_error,
with_standby,fit_gap_tau,gap_paths,gap_error,
walk_tau,circulation_windows,CirculationWindow,
fit_ungated_observabilityand their bounds and thresholds;
calibrate'sua_exponent. decay.project_decay,DecayProjection,Draw,
draw_stepandpartitions.simulate'sledger,lead_drawsandphysics_usable;
History.heated_at; the reasontau_gap_prior; and the refusal
of draws on parameters with no fitted draw response
(draw_model_unsupported): the layers take any draw.extensions.HeatLedgerand its value types (Stand,
LedgerZones,ZoneBound,DrawTaken,AfterDraw,
DrawsAhead,UpperAhead);book.BookedHeatLedger,
zone_after_draw_f,zone_mixed_f,zone_cannot_moveand
FALL_F_PER_GAL;walk_booked'scarryandkeep,
WalkCarryandBookedPath.carry.engine.DRAW_LEAD;TankPhysics'coast,heat,
TwoZone,Coast,Heat,FlowingDraws,tau_gap_hours
andphysics_usable;REFINE_TOL_MINUTES;
heat_pump_recovery.physics_recovery,recoveryand
Recovery;heating_trajectory.build_heating_trajectory;
circulation.CirculationDrawsandrun_draws.PowerParamsandHeaterParams.power(power_scale,
cop_scale,startup_wh,cop_lift_coeff);
Forecast.startup_energy_whandRecovery.startup_energy_wh;
thestartup_wharguments of the engine;evaporator_fof
active_cycleandsim_adapters.evaporator_f.calibration.fit_compressor,CompressorFit,
recovery_minutes,split_recoveries,with_compressor,
PowerFit,fit_power,with_power,power_cycles,
cop_cycles,CopCycle,path_to_endand the COP solvers;
physics.compressor.predict_physics,heating_trajectory.path_cop
andheat_pump_recovery.DEFAULT_HUMIDITY.