#!/usr/bin/env python # -*- coding: utf-8 -*- """ *********************************************************************************** tutorial13.py DAE Tools: pyDAE module, www.daetools.com Copyright (C) Dragan Nikolic *********************************************************************************** DAE Tools is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation. DAE Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the DAE Tools software; if not, see <http://www.gnu.org/licenses/>. ************************************************************************************ """ __doc__ = """ This tutorial introduces the following concepts: - The event ports - ON_CONDITION() function illustrating the types of actions that can be executed during state transitions - ON_EVENT() function illustrating the types of actions that can be executed when an event is triggered - User defined actions In this example we use the very similar model as in the tutorial 5. The simulation output should show the following messages at t=100s and t=350s: .. code-block:: none ... ******************************************************** simpleUserAction2 message: This message should be fired when the time is 100s. ******************************************************** ... ******************************************************** simpleUserAction executed; input data = 427.464093129832 ******************************************************** ... The plot of the 'event' variable: .. image:: _static/tutorial13-results.png :width: 500px The temperature plot: .. image:: _static/tutorial13-results2.png :width: 500px """ import sys from time import localtime, strftime from daetools.pyDAE import * # Standard variable types are defined in variable_types.py from pyUnits import m, kg, s, K, Pa, mol, J, W # User defined action executed in OnEvent handler # The __init__ function accepts an event port as an argument that is used # to retrieve the value of the event data class simpleUserAction(daeAction): def __init__(self, eventPort): daeAction.__init__(self) self.eventPort = eventPort def Execute(self): # The floating point value of the data sent with the event can be retrieved # using daeEventPort property EventData msg = 'simpleUserAction executed; input data = {0}'.format(self.eventPort.EventData) # Try to show a message box if there is application instance already defined # that is we are in the 'gui' mode try: daeQtMessage('tutorial13', msg) finally: print('********************************************************') print(msg) print('********************************************************') class simpleUserAction2(daeAction): def __init__(self, msg): daeAction.__init__(self) self.msg = msg def Execute(self): # Try to show a message box if there is application instance already defined # that is we are in the 'gui' mode try: daeQtMessage('tutorial13', self.msg) finally: print('********************************************************') print('simpleUserAction2 message: ') print(self.msg) print('********************************************************') class modTutorial(daeModel): def __init__(self, Name, Parent = None, Description = ""): daeModel.__init__(self, Name, Parent, Description) self.m = daeParameter("m", kg, self, "Mass of the copper plate") self.cp = daeParameter("c_p", J/(kg*K), self, "Specific heat capacity of the plate") self.alpha = daeParameter("α", W/((m**2)*K), self, "Heat transfer coefficient") self.A = daeParameter("A", m**2, self, "Area of the plate") self.Tsurr = daeParameter("T_surr", K, self, "Temperature of the surroundings") self.Qin = daeVariable("Q_in", power_t, self, "Power of the heater") self.T = daeVariable("T", temperature_t, self, "Temperature of the plate") self.event = daeVariable("event", no_t, self, "Variable which value is set in ON_EVENT function") # Here we create two event ports (inlet and outlet) and connect them. # It makes no sense in reality, but this is example is just an example - in the real application # they would be defined in separate models. # Event ports can be connected/disconnected at any time. self.epIn = daeEventPort("epIn", eInletPort, self, "Inlet event port") self.epOut = daeEventPort("epOut", eOutletPort, self, "Outlet event port") self.ConnectEventPorts(self.epIn, self.epOut) def DeclareEquations(self): daeModel.DeclareEquations(self) eq = self.CreateEquation("HeatBalance", "Integral heat balance equation") eq.Residual = self.m() * self.cp() * dt(self.T()) - self.Qin() + self.alpha() * self.A() * (self.T() - self.Tsurr()) self.stnRegulator = self.STN("Regulator") self.STATE("Heating") eq = self.CreateEquation("Q_in", "The heater is on") eq.Residual = self.Qin() - Constant(1500 * W) """ ON_CONDITION() function Arguments: - Condition that triggers the actions - 'switchToStates' is a list of python tuples (STN-name-relative-to-model, State-name) that will be set active when the condition is satisified - 'triggerEvents' is a list of python tuples (outlet-event-port, expression), where the first part is the event-port object and the second a value to be sent when the event is trigerred - 'setVariableValues' is a list of python tuples (variable, expression); if the variable is differential it will be reinitialized (using ReInitialize() function), otherwise it will be reassigned (using ReAssign() function) - 'userDefinedActions' is a list of user defined daeAction-derived objects """ self.ON_CONDITION(self.T() > Constant(340*K), switchToStates = [ ('Regulator', 'Cooling') ], setVariableValues = [ (self.event, 100) ], # event variable is dimensionless triggerEvents = [], userDefinedActions = [] ) self.ON_CONDITION(Time() > Constant(350*s), switchToStates = [ ('Regulator', 'HeaterOff') ], setVariableValues = [], triggerEvents = [ (self.epOut, self.T() + Constant(5.0*K)) ], userDefinedActions = [] ) self.STATE("Cooling") eq = self.CreateEquation("Q_in", "The heater is off") eq.Residual = self.Qin() self.ON_CONDITION(self.T() < Constant(320*K), switchToStates = [ ('Regulator', 'Heating') ], setVariableValues = [ (self.event, 200) ], # event variable is dimensionless triggerEvents = [], userDefinedActions = [] ) self.ON_CONDITION(Time() > Constant(350*s), switchToStates = [ ('Regulator', 'HeaterOff') ], setVariableValues = [], triggerEvents = [ (self.epOut, self.T() + Constant(6.0*K)) ], userDefinedActions = [] ) self.STATE("HeaterOff") eq = self.CreateEquation("Q_in", "The heater is off") eq.Residual = self.Qin() self.END_STN() # Users are responsible for creating/deleting of user actions and have to ensure # that they still exist until the end of simulation. self.action = simpleUserAction(self.epIn) self.action2 = simpleUserAction2('This message should be fired when the time is 100s.') """ The ON_CONDITION() function can be directly called from the DeclareEquations() and in that case the OnCondition handler belongs to the model and is always active. """ self.ON_CONDITION(Time() == Constant(100*s), switchToStates = [], setVariableValues = [], triggerEvents = [], userDefinedActions = [self.action2] ) """ ON_EVENT() function The actions executed when the event on the inlet epIn event port is received. OnEvent handlers can be also specified as a part of the state definition and then they are active only when that particular state is active. Arguments: - Event port (could be either inlet or outlet) - 'switchToStates' is a list of python tuples (STN-name-relative-to-model, State-name) - 'triggerEvents' is a list of python tuples (outlet-event-port, expression) - 'setVariableValues' is a list of python tuples (variable, expression) - 'userDefinedActions' is a list of daeAction derived classes daeEventPort defines the operator() which returns adouble object that can be evaluated at the moment when the action is executed to get the value of the event data (ie. to set a new value of a variable). """ self.ON_EVENT(self.epIn, switchToStates = [ ('Regulator', 'HeaterOff')], setVariableValues = [ (self.event, self.epIn()) ], triggerEvents = [], userDefinedActions = [self.action]) class simTutorial(daeSimulation): def __init__(self): daeSimulation.__init__(self) self.m = modTutorial("tutorial13") self.m.Description = __doc__ def SetUpParametersAndDomains(self): self.m.cp.SetValue(385 * J/(kg*K)) self.m.m.SetValue(1 * kg) self.m.alpha.SetValue(200 * W/((m**2)*K)) self.m.A.SetValue(0.1 * m**2) self.m.Tsurr.SetValue(283 * K) def SetUpVariables(self): # Set the state active at the beginning (the default is the first declared state; here 'Heating') self.m.stnRegulator.ActiveState = "Heating" self.m.T.SetInitialCondition(283 * K) self.m.event.AssignValue(0.0) def print_list_of_events(simulation, log): # Print the list of events print('\n') log.Message('Events occured at the {0} event port:'.format(simulation.m.epOut.CanonicalName), 0) log.Message(str(simulation.m.epOut.Events), 0) log.Message('Events occured at the {0} event port:'.format(simulation.m.epIn.CanonicalName), 0) log.Message(str(simulation.m.epIn.Events), 0) def run(**kwargs): simulation = simTutorial() return daeActivity.simulate(simulation, reportingInterval = 2, timeHorizon = 500, run_after_simulation_fn = print_list_of_events, **kwargs) if __name__ == "__main__": guiRun = False if (len(sys.argv) > 1 and sys.argv[1] == 'console') else True run(guiRun = guiRun)