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
+3 -3
View File
@@ -33,14 +33,14 @@ if __name__ == '__main__':
}
}
sud = Sud("sude/sud_0010.json")
plant_params = {
"C" : 4190,
"M" : 20,
**sud.derive_plant_params(),
"L" : 0.2,
"Td" : 30
}
sud = Sud("sude/sud_0010.json")
ctrl = TempController(dt, ctrl_params, plant_params, theta_amb)
plant = Pot(dt, plant_params, theta_amb)
stirrer = StirrerSim(dt)
+3 -3
View File
@@ -1,10 +1,10 @@
{
"Name" : "Sud-0010",
"Description" : "Rotfraenkisch, Dunkles Lager",
"pot_weight_kg" : 5.960,
"pot_mass" : 5.960,
"pot_material" : "Edelstahl 18/10",
"Schuettung_kg" : 5.21,
"Wasser_kg" : 22,
"grain_mass" : 5.21,
"water_mass" : 22,
"schedule":
[
{