Derive Pot's L/Td from the Sud doc instead of a fixed server default
Sud now parses top-level L/Td fields from sude/*.json (defaulting to 0.2/30, matching the old hardcoded server constants), and derive_plant_params() returns them alongside M/C - constant for the whole brew, unlike M/C which vary per step with grain/water mass. All sude/*.json docs gain explicit L/Td fields. Callers (tasks/sud.py, SudForecastEstimator, demo_sud.py) switch from the narrow set_thermal_params(M, C)/set_model_params(M, C) to the full set_plant_params(params)/set_model_plant_params(params), since derive_plant_params() now always returns all four keys; both narrow setters are removed as dead code. Caught along the way: Pot.set_plant_params() unconditionally rebuilt the Delay ring buffer, which was harmless when only ever called once at construction - but now running on every Sud step change, it was wiping in-flight delayed power at each step boundary. Fixed to only rebuild when Td actually changes; verified this restores the exact original forecast result for sud_0010.json. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DkkuG48uHFCGKe6dPSERFk
This commit is contained in:
+23
-12
@@ -61,6 +61,11 @@ EMPTY_SUD = {
|
||||
'Description': '',
|
||||
'pot_mass': 0,
|
||||
'pot_material': None,
|
||||
# Pot energy loss coefficient [W/(kg*K)] and transport propagation
|
||||
# delay [s] - see derive_plant_params(). Defaults match the generic
|
||||
# values brewpi.py used to hardcode for every brew alike.
|
||||
'L': 0.2,
|
||||
'Td': 30,
|
||||
'steps': [],
|
||||
}
|
||||
|
||||
@@ -74,8 +79,8 @@ class Sud(AttributeChange):
|
||||
AttributeChange.__init__(self)
|
||||
self._data = EMPTY_SUD
|
||||
|
||||
(self.name, self.description, self.schedule,
|
||||
self.pot_mass, self.pot_material) = self._parse_data(self._data)
|
||||
(self.name, self.description, self.schedule, self.pot_mass,
|
||||
self.pot_material, self.L, self.Td) = self._parse_data(self._data)
|
||||
|
||||
self._paused_from = None
|
||||
self._reset_run_state()
|
||||
@@ -83,9 +88,9 @@ class Sud(AttributeChange):
|
||||
@staticmethod
|
||||
def _parse_data(data):
|
||||
"""Parses a sud.json document into the (name, description, schedule,
|
||||
pot_mass, pot_material) tuple Sud needs. Computed up front rather
|
||||
than assigned straight onto self, so a malformed load() can't
|
||||
leave a half-applied schedule in place."""
|
||||
pot_mass, pot_material, L, Td) tuple Sud needs. Computed up front
|
||||
rather than assigned straight onto self, so a malformed load()
|
||||
can't leave a half-applied schedule in place."""
|
||||
name = data.get('Name', '')
|
||||
description = data.get('Description', '')
|
||||
|
||||
@@ -94,8 +99,10 @@ class Sud(AttributeChange):
|
||||
|
||||
pot_mass = data.get('pot_mass', 0)
|
||||
pot_material = data.get('pot_material')
|
||||
L = data.get('L', EMPTY_SUD['L'])
|
||||
Td = data.get('Td', EMPTY_SUD['Td'])
|
||||
|
||||
return name, description, schedule, pot_mass, pot_material
|
||||
return name, description, schedule, pot_mass, pot_material, L, Td
|
||||
|
||||
def _reset_run_state(self):
|
||||
"""Resets run-time progress back to a freshly-loaded, not-yet-started
|
||||
@@ -128,18 +135,20 @@ class Sud(AttributeChange):
|
||||
except (KeyError, TypeError):
|
||||
return False
|
||||
|
||||
self.name, self.description, self.schedule, self.pot_mass, self.pot_material = parsed
|
||||
(self.name, self.description, self.schedule, self.pot_mass,
|
||||
self.pot_material, self.L, self.Td) = parsed
|
||||
self._data = data
|
||||
|
||||
self._reset_run_state()
|
||||
return True
|
||||
|
||||
def derive_plant_params(self, grain_mass, water_mass):
|
||||
"""Lumped (mass, specific heat) lifted from pot_mass/pot_material and
|
||||
the current step's grain_mass/water_mass, for use as Pot's "M"/"C"
|
||||
params. grain_mass/water_mass vary per step (e.g. malt going in,
|
||||
water boiling off), so this is recomputed on every step change
|
||||
rather than once at startup."""
|
||||
"""Full Pot plant params - "M"/"C" lumped (mass, specific heat)
|
||||
from pot_mass/pot_material and the current step's grain_mass/
|
||||
water_mass (these vary per step, e.g. malt going in, water
|
||||
boiling off, so this is recomputed on every step change rather
|
||||
than once at startup), plus "L"/"Td" straight from this Sud's
|
||||
own doc (constant for the whole brew - see Sud.load())."""
|
||||
c_pot = SPECIFIC_HEAT_BY_MATERIAL.get(self.pot_material, SPECIFIC_HEAT_WATER)
|
||||
mass = water_mass + grain_mass + self.pot_mass
|
||||
capacitance = (water_mass * SPECIFIC_HEAT_WATER
|
||||
@@ -149,6 +158,8 @@ class Sud(AttributeChange):
|
||||
return {
|
||||
'M': mass,
|
||||
'C': capacitance / mass if mass > 0 else SPECIFIC_HEAT_WATER,
|
||||
'L': self.L,
|
||||
'Td': self.Td,
|
||||
}
|
||||
|
||||
def start(self):
|
||||
|
||||
Reference in New Issue
Block a user