from components.aplant import APlant from components.plant import delay class Pot(APlant): def __init__(self, dt): APlant.__init__(self) self.dt = dt self.e = 0 self.x = 0 self.p_pot = 0 # Plant specific thermal capacity [W*s/(kg*K)], mass [kg], energy # loss coefficient [W/(kg*K)] and transport propagation delay [s] # - all None until set_plant_params() is called; process() checks # for that rather than silently computing on bogus values. self.C = None self.M = None self.L = None self.Td = None self.delay = None # Plant temperature [°C] - seeded from theta_amb the first time # set_ambient_temperature() is called (see there), or overridden # explicitly via initial(). None until either happens; # process() checks for that too. self.temp = None # Ambient temperature [°C] - None until set_ambient_temperature() # is called; process() checks for that as well. self.theta_amb = None # Set power [W] self.p_in = 0 def set_plant_params(self, params): self.C = params['C'] self.M = params['M'] # Negative input power as a function of plant mass and # temperature difference T_plant and T_ambient: # P_loss = L * Mass * (T_plant - T_ambient) self.L = params['L'] # Only rebuilds the delay line if Td actually changed - this now # gets called on every Sud step change (M/C vary with grain/water # mass), not just once at construction, and rebuilding unconditionally # would zero out whatever power was already in flight through the # transport delay at every single step transition. Td = params['Td'] if Td != self.Td: self.Td = Td self.delay = delay.Delay(self.dt, self.Td, 0) def set_ambient_temperature(self, theta_amb): self.theta_amb = theta_amb # Only seeds the plant's own starting temperature the first time # this is called (i.e. there's no real temperature yet) - once a # run is in progress, changing the ambient setting must not # clobber whatever temperature has actually been simulated since. if self.temp is None: self.temp = theta_amb def initial(self, temp): self.temp = temp def is_configured(self): """Whether both set_plant_params() and set_ambient_temperature() have been called - lets a caller driving this Pot as an internal model (e.g. TempController(Smith)) check upfront and raise its own comprehensive error, rather than letting process() fail on whichever of the two happens to be missing.""" return self.delay is not None and self.theta_amb is not None def activate(self, enable): pass def process(self): if self.delay is None: raise RuntimeError("Pot.process(): plant params not set - call set_plant_params() first") if self.theta_amb is None: raise RuntimeError("Pot.process(): ambient temperature not set - call set_ambient_temperature() first") self.delay.put(self.p_in) p_loss = self.L * self.M * (self.temp - self.theta_amb) self.p_pot = self.delay.get() - p_loss self.temp = min(100, self.temp + self.p_pot/(self.M * self.C) * self.dt) def is_activated(self): return True def set_power(self, power): self.p_in = power def get_power(self): return round(self.p_in, 1) def get_temperature(self): return self.temp def get_p_pot(self): return self.p_pot