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:
@@ -2,6 +2,15 @@ import json
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from utils.value import AttributeChange
|
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):
|
class SudState(Enum):
|
||||||
IDLE = 0
|
IDLE = 0
|
||||||
@@ -21,12 +30,31 @@ class Sud(AttributeChange):
|
|||||||
self.description = data.get('Description', '')
|
self.description = data.get('Description', '')
|
||||||
self.schedule = data['schedule']
|
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.index = -1
|
||||||
self.hold_remaining = 0.0
|
self.hold_remaining = 0.0
|
||||||
self.state = SudState.IDLE
|
self.state = SudState.IDLE
|
||||||
self.step = None
|
self.step = None
|
||||||
self.user_message = 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):
|
def start(self):
|
||||||
if self.state != SudState.IDLE:
|
if self.state != SudState.IDLE:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ if __name__ == '__main__':
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sud = Sud("sude/sud_0010.json")
|
||||||
|
|
||||||
plant_params = {
|
plant_params = {
|
||||||
"C" : 4190,
|
**sud.derive_plant_params(),
|
||||||
"M" : 20,
|
|
||||||
"L" : 0.2,
|
"L" : 0.2,
|
||||||
"Td" : 30
|
"Td" : 30
|
||||||
}
|
}
|
||||||
|
|
||||||
sud = Sud("sude/sud_0010.json")
|
|
||||||
ctrl = TempController(dt, ctrl_params, plant_params, theta_amb)
|
ctrl = TempController(dt, ctrl_params, plant_params, theta_amb)
|
||||||
plant = Pot(dt, plant_params, theta_amb)
|
plant = Pot(dt, plant_params, theta_amb)
|
||||||
stirrer = StirrerSim(dt)
|
stirrer = StirrerSim(dt)
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"Name" : "Sud-0010",
|
"Name" : "Sud-0010",
|
||||||
"Description" : "Rotfraenkisch, Dunkles Lager",
|
"Description" : "Rotfraenkisch, Dunkles Lager",
|
||||||
"pot_weight_kg" : 5.960,
|
"pot_mass" : 5.960,
|
||||||
"pot_material" : "Edelstahl 18/10",
|
"pot_material" : "Edelstahl 18/10",
|
||||||
"Schuettung_kg" : 5.21,
|
"grain_mass" : 5.21,
|
||||||
"Wasser_kg" : 22,
|
"water_mass" : 22,
|
||||||
"schedule":
|
"schedule":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user