Derive Pot's M/C from pot/grain/water mass instead of hardcoding them

Add Sud.derive_plant_params(), which combines pot_mass/pot_material/
grain_mass/water_mass into a single lumped (mass, specific heat) pair
using approximate specific-heat constants for water, grain, and (by a
small material lookup table) the pot itself. demo_sud.py now builds
its plant_params from this instead of hardcoded C/M values, keeping
L/Td as the only manually-tuned plant parameters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 19:34:15 +02:00
co-authored by Claude Sonnet 4.6
parent 18d9af960f
commit b6551c6433
3 changed files with 34 additions and 6 deletions
+28
View File
@@ -2,6 +2,15 @@ import json
from enum import Enum
from utils.value import AttributeChange
# Specific heat capacities [J/(kg*K)] used to derive a single lumped
# (mass, specific heat) pair for the pot's contents from pot_mass/
# grain_mass/water_mass. Approximate, typical values.
SPECIFIC_HEAT_WATER = 4190
SPECIFIC_HEAT_GRAIN = 1800
SPECIFIC_HEAT_BY_MATERIAL = {
"Edelstahl 18/10": 500,
}
class SudState(Enum):
IDLE = 0
@@ -21,12 +30,31 @@ class Sud(AttributeChange):
self.description = data.get('Description', '')
self.schedule = data['schedule']
self.pot_mass = data.get('pot_mass', 0)
self.pot_material = data.get('pot_material')
self.grain_mass = data.get('grain_mass', 0)
self.water_mass = data.get('water_mass', 0)
self.index = -1
self.hold_remaining = 0.0
self.state = SudState.IDLE
self.step = None
self.user_message = None
def derive_plant_params(self):
"""Lumped (mass, specific heat) lifted from pot_mass/pot_material/
grain_mass/water_mass, for use as Pot's "M"/"C" params."""
c_pot = SPECIFIC_HEAT_BY_MATERIAL.get(self.pot_material, SPECIFIC_HEAT_WATER)
mass = self.water_mass + self.grain_mass + self.pot_mass
capacitance = (self.water_mass * SPECIFIC_HEAT_WATER
+ self.grain_mass * SPECIFIC_HEAT_GRAIN
+ self.pot_mass * c_pot)
return {
'M': mass,
'C': capacitance / mass if mass > 0 else SPECIFIC_HEAT_WATER,
}
def start(self):
if self.state != SudState.IDLE:
return