|
@@ -0,0 +1,808 @@
|
|
|
+<?xml version="1.0" ?>
|
|
|
+<diagram author="Yentl Van Tendeloo" name="DEVS simulator">
|
|
|
+ <description>
|
|
|
+ A restricted PythonPDEVS simulator modelled in SCCD
|
|
|
+ </description>
|
|
|
+ <top>
|
|
|
+ import cPickle as pickle
|
|
|
+ import time
|
|
|
+
|
|
|
+ # We basically just interface with the basesimulator
|
|
|
+ from scheduler import Scheduler
|
|
|
+ from DEVS import directConnect, CoupledDEVS, AtomicDEVS, RootDEVS
|
|
|
+
|
|
|
+ class Breakpoint(object):
|
|
|
+ def __init__(self, breakpoint_id, function, enabled, disable_on_trigger):
|
|
|
+ self.id = breakpoint_id
|
|
|
+ self.function = function
|
|
|
+ self.enabled = enabled
|
|
|
+ self.disable_on_trigger = disable_on_trigger
|
|
|
+ </top>
|
|
|
+ <inport name="request"/>
|
|
|
+ <outport name="reply"/>
|
|
|
+ <class name="SCCDSimulator" default="true">
|
|
|
+ <!-- Define the constructor, taking the model to simulate as a parameter -->
|
|
|
+ <constructor>
|
|
|
+ <parameter name="model"/>
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ # Simulation variables
|
|
|
+ self.termination_time = None
|
|
|
+ self.termination_condition = None
|
|
|
+ self.simulation_time = (0.0, 0)
|
|
|
+ self.model = model
|
|
|
+ self.root_model = model
|
|
|
+ self.realtime_scale = 1.0
|
|
|
+
|
|
|
+ # Values to be set during simulation
|
|
|
+ self.realtime_starttime = None
|
|
|
+ self.inject_queue = []
|
|
|
+
|
|
|
+ # Model initialization
|
|
|
+ self.model_ids = []
|
|
|
+ self.model.finalize(name="", model_counter=0, model_ids=self.model_ids, locations={None: []}, selectHierarchy=[])
|
|
|
+
|
|
|
+ # Direct connection
|
|
|
+ if isinstance(self.model, CoupledDEVS):
|
|
|
+ self.model.componentSet = directConnect(self.model.componentSet, True)
|
|
|
+ self.model = RootDEVS(self.model.componentSet, self.model.componentSet, None)
|
|
|
+ for m in self.model.componentSet:
|
|
|
+ m.timeLast = (-m.elapsed, 1)
|
|
|
+ ta = m.timeAdvance()
|
|
|
+ m.timeNext = (m.timeLast[0] + ta, 1)
|
|
|
+ m.old_states = [(m.timeLast, pickle.dumps(m.state))]
|
|
|
+ elif isinstance(self.model, AtomicDEVS):
|
|
|
+ for p in self.model.IPorts:
|
|
|
+ p.routingInLine = []
|
|
|
+ p.routingOutLine = []
|
|
|
+ for p in self.model.OPorts:
|
|
|
+ p.routingInLine = []
|
|
|
+ p.routingOutLine = []
|
|
|
+ self.model = RootDEVS([self.model], [self.model], None)
|
|
|
+ self.model.timeLast = (-self.model.elapsed, 1)
|
|
|
+ ta = self.model.timeAdvance()
|
|
|
+ self.model.timeNext = (self.model.timeLast[0] + ta, 1)
|
|
|
+
|
|
|
+ # Fixed configuration options
|
|
|
+ self.model.scheduler = Scheduler(self.model.componentSet, 1e-6, len(self.model.models))
|
|
|
+ self.timeNext = self.model.scheduler.readFirst()
|
|
|
+
|
|
|
+ # Cached values
|
|
|
+ self.imminents = None
|
|
|
+ self.outbags = None
|
|
|
+ self.inbags = None
|
|
|
+ self.transitioning = None
|
|
|
+ self.new_states = None
|
|
|
+ self.new_tn = None
|
|
|
+
|
|
|
+ # Verbose trace file
|
|
|
+ self.trace_file = None
|
|
|
+
|
|
|
+ # Breakpoint management
|
|
|
+ self.breakpoints = []
|
|
|
+
|
|
|
+ # For a reset
|
|
|
+ self.save_model = {model: (model.elapsed, pickle.dumps(model.state, pickle.HIGHEST_PROTOCOL)) for model in self.model.componentSet}
|
|
|
+ self.transition_times = []
|
|
|
+
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </constructor>
|
|
|
+ <!-- Serialize the output values in something human-readable instead of internal objects.
|
|
|
+ Note that this doesn't alter the internal structure, as that is still used for simulation. -->
|
|
|
+ <method name="serialize">
|
|
|
+ <parameter name="type"/>
|
|
|
+ <parameter name="object"/>
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ if type == "imminents":
|
|
|
+ return [m.getModelFullName() for m in object]
|
|
|
+ elif type == "inbags" or type == "outbags":
|
|
|
+ return {m.getPortFullName(): object[m] for m in object}
|
|
|
+ elif type == "new_tn" or type == "new_states":
|
|
|
+ return {m.getModelFullName(): object[m] for m in object}
|
|
|
+ elif type == "transitioning":
|
|
|
+ return {m.getModelFullName(): {1: "INTERNAL", 2: "EXTERNAL", 3: "CONFLUENT"}[object[m]] for m in object}
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Find a port based on a fully qualified name. -->
|
|
|
+ <method name="find_port_with_name">
|
|
|
+ <parameter name="name"/>
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ for model in self.model.componentSet:
|
|
|
+ if name.startswith(model.getModelFullName()):
|
|
|
+ # Found a potential model
|
|
|
+ # We can't simply check for equality, as portnames might contain dots too
|
|
|
+ for port in model.IPorts:
|
|
|
+ if port.getPortFullName() == name:
|
|
|
+ # Now everything matches
|
|
|
+ return port
|
|
|
+ # Nothing found
|
|
|
+ return None
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Find a model based on a fully qualified name. -->
|
|
|
+ <method name="find_model_with_name">
|
|
|
+ <parameter name="name"/>
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ for model in self.model.componentSet:
|
|
|
+ if name == model.getModelFullName():
|
|
|
+ # Found exact match
|
|
|
+ return model
|
|
|
+ return None
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Calculate the time to wait before triggering the next transition.
|
|
|
+ This method is also called in non-realtime simulation, so make sure that it returns infinity in that case. -->
|
|
|
+ <method name="calculate_after">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ try:
|
|
|
+ # Process in parts of 100 milliseconds to repeatedly check the termination condition
|
|
|
+ nexttime = (self.timeNext[0] - (time.time() - self.realtime_starttime) / self.realtime_scale) * self.realtime_scale
|
|
|
+ x = min(0.1, nexttime)
|
|
|
+ return x
|
|
|
+ except TypeError, AttributeError:
|
|
|
+ # We are probably not simulating in realtime...
|
|
|
+ return float('inf')
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Parse a list of options that can be passed together with the request -->
|
|
|
+ <method name="parse_options">
|
|
|
+ <parameter name="configuration"/>
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ self.termination_condition = None if "termination_condition" not in configuration else configuration["termination_condition"]
|
|
|
+ self.termination_time = None if "termination_time" not in configuration else configuration["termination_time"]
|
|
|
+ self.realtime_scale = 1.0 if "realtime_scale" not in configuration else 1.0/configuration["realtime_scale"]
|
|
|
+ # Subtract the current simulation time to allow for pausing
|
|
|
+ self.realtime_starttime = (time.time() - self.simulation_time[0]*self.realtime_scale)
|
|
|
+ # Reset the time used in the waiting, as it might not get recomputed
|
|
|
+ self.the_time = 0.00001
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Utility function to determine whether or not simulation is finished. -->
|
|
|
+ <method name="should_terminate">
|
|
|
+ <parameter name="realtime"/>
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ # Now that it includes breakpoints, results are to be interpretted as follows:
|
|
|
+ # -2 --> continue simulation
|
|
|
+ # -1 --> termination condition
|
|
|
+ # else --> breakpoint
|
|
|
+
|
|
|
+ if realtime:
|
|
|
+ check_time = self.simulation_time
|
|
|
+ else:
|
|
|
+ self.compute_timeNext()
|
|
|
+ check_time = self.timeNext
|
|
|
+
|
|
|
+ # Just access the 'transitioned' dictionary
|
|
|
+ # Kind of dirty though...
|
|
|
+ if self.transitioning is None:
|
|
|
+ transitioned = set()
|
|
|
+ else:
|
|
|
+ transitioned = set(self.transitioning.keys())
|
|
|
+
|
|
|
+ if check_time[0] == float('inf'):
|
|
|
+ # Always terminate if we have reached infinity
|
|
|
+ terminate = True
|
|
|
+ elif self.termination_condition is not None:
|
|
|
+ terminate = self.termination_condition(check_time, self.root_model, transitioned)
|
|
|
+ else:
|
|
|
+ terminate = self.termination_time < check_time[0]
|
|
|
+
|
|
|
+ if terminate:
|
|
|
+ # Always terminate, so don't check breakpoints
|
|
|
+ return -1
|
|
|
+ else:
|
|
|
+ # Have to check breakpoints for termination
|
|
|
+ for bp in self.breakpoints:
|
|
|
+ if not bp.enabled:
|
|
|
+ continue
|
|
|
+ # Include the function in the scope
|
|
|
+ exec(bp.function)
|
|
|
+ # And execute it, note that the breakpoint thus has to start with "def breakpoint("
|
|
|
+ if breakpoint(check_time, self.root_model, transitioned):
|
|
|
+ # Triggered!
|
|
|
+ return bp.id
|
|
|
+ else:
|
|
|
+ # Not triggered, so continue
|
|
|
+ continue
|
|
|
+ return -2
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Compute all models of which the internal transition function has to be triggered. -->
|
|
|
+ <method name="find_internal_imminents">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ self.imminents = self.model.scheduler.getImminent(self.simulation_time)
|
|
|
+ self.transition_times.append(self.simulation_time)
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Trigger all output functions of imminent models. -->
|
|
|
+ <method name="compute_outputfunction">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ self.outbags = {}
|
|
|
+ for model in self.imminents:
|
|
|
+ self.outbags.update(model.outputFnc())
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Route all messages and apply the required transfer functions. -->
|
|
|
+ <method name="route_messages">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ self.inbags = {}
|
|
|
+ for outport in self.outbags:
|
|
|
+ payload = self.outbags[outport]
|
|
|
+ for inport, z in outport.routingOutLine:
|
|
|
+ if z is not None:
|
|
|
+ payload = [z(pickle.loads(pickle.dumps(m))) for m in payload]
|
|
|
+ self.inbags.setdefault(inport, []).extend(payload)
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Process the injects on the inject_queue. -->
|
|
|
+ <method name="process_injects">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ while self.inject_queue:
|
|
|
+ if self.inject_queue[0]["time"] > self.simulation_time:
|
|
|
+ break
|
|
|
+ config = self.inject_queue.pop(0)
|
|
|
+ portname = config["port"]
|
|
|
+ event = config["event"]
|
|
|
+ port = self.find_port_with_name(portname)
|
|
|
+ self.inbags.setdefault(port, []).append(event)
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Combine the information from the routed messages to determine the external, internal and confluent transition functions to trigger. -->
|
|
|
+ <method name="find_all_imminents">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ # Internal codes:
|
|
|
+ # 1 --> internal transition
|
|
|
+ # 2 --> external transition
|
|
|
+ # 3 --> confluent transition
|
|
|
+ # These codes are a legacy of efficient PyPDEVS, but is kept here for consistency
|
|
|
+ self.transitioning = {model: 1 for model in self.imminents}
|
|
|
+ for inport in self.inbags:
|
|
|
+ aDEVS = inport.hostDEVS
|
|
|
+ aDEVS.myInput[inport] = self.inbags[inport]
|
|
|
+ if aDEVS in self.transitioning:
|
|
|
+ self.transitioning[aDEVS] = 3
|
|
|
+ else:
|
|
|
+ self.transitioning[aDEVS] = 2
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Trigger all transition functions. -->
|
|
|
+ <method name="compute_transitions">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ self.new_states = {}
|
|
|
+ for aDEVS in self.transitioning:
|
|
|
+ aDEVS.myInput = {key: pickle.loads(pickle.dumps(aDEVS.myInput[key], pickle.HIGHEST_PROTOCOL)) for key in aDEVS.myInput}
|
|
|
+ if self.transitioning[aDEVS] == 1:
|
|
|
+ aDEVS.state = aDEVS.intTransition()
|
|
|
+ elif self.transitioning[aDEVS] == 2:
|
|
|
+ aDEVS.elapsed = self.simulation_time[0] - aDEVS.timeLast[0]
|
|
|
+ aDEVS.state = aDEVS.extTransition(aDEVS.myInput)
|
|
|
+ elif self.transitioning[aDEVS] == 3:
|
|
|
+ aDEVS.elapsed = 0.
|
|
|
+ aDEVS.state = aDEVS.confTransition(aDEVS.myInput)
|
|
|
+ aDEVS.old_states.append((self.simulation_time, pickle.dumps(aDEVS.state)))
|
|
|
+ aDEVS.myInput = {}
|
|
|
+ self.new_states[aDEVS] = aDEVS.state
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Trigger all timeAdvance functions. -->
|
|
|
+ <method name="compute_ta">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ self.new_tn = {}
|
|
|
+ t, age = self.simulation_time
|
|
|
+ for aDEVS in self.transitioning:
|
|
|
+ ta = aDEVS.timeAdvance()
|
|
|
+ aDEVS.timeLast = self.simulation_time
|
|
|
+ aDEVS.timeNext = (t + ta, 1 if ta else (age + 1))
|
|
|
+ self.new_tn[aDEVS] = aDEVS.timeNext
|
|
|
+ self.trace(self.transitioning[aDEVS], aDEVS)
|
|
|
+ self.model.scheduler.massReschedule(self.transitioning)
|
|
|
+ self.timeNext = self.model.scheduler.readFirst()
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <!-- Verbose tracing. -->
|
|
|
+ <method name="trace">
|
|
|
+ <parameter name="type"/>
|
|
|
+ <parameter name="model"/>
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ type_map = {1: "INTERNAL", 2: "EXTERNAL", 3: "CONFLUENT"}
|
|
|
+ type = type_map[type]
|
|
|
+ if self.trace_file is not None:
|
|
|
+ self.trace_file.write("%s TRANSITION in <%s> @ %s\n" % (type, model.getModelFullName(), model.timeLast[0]))
|
|
|
+ self.trace_file.write(" NEW STATE <%s>\n" % (model.state))
|
|
|
+ if type != "EXTERNAL":
|
|
|
+ self.trace_file.write(" OUTPUTFNC returned %s\n" % model.myOutput)
|
|
|
+ elif type != "INTERNAL":
|
|
|
+ self.trace_file.write(" inputs were %s\n" % model.myInput)
|
|
|
+ self.trace_file.write(" timeNext: %s (ta: %s)\n" % (model.timeNext[0], model.timeNext[0] - model.timeLast[0]))
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <method name="flush_file">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ if self.trace_file is not None:
|
|
|
+ self.trace_file.flush()
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <method name="process_breakpoints">
|
|
|
+ <parameter name="realtime"/>
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ breakpoint_id = self.should_terminate(realtime)
|
|
|
+ for breakpoint in self.breakpoints:
|
|
|
+ if breakpoint.id == breakpoint_id:
|
|
|
+ if breakpoint.disable_on_trigger:
|
|
|
+ breakpoint.enabled = False
|
|
|
+ return breakpoint_id
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <method name="compute_timeNext">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ model_timeNext = self.model.scheduler.readFirst()
|
|
|
+ if len(self.inject_queue) > 0:
|
|
|
+ self.timeNext = min(model_timeNext, self.inject_queue[0]["time"])
|
|
|
+ else:
|
|
|
+ self.timeNext = model_timeNext
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <method name="rollback_step">
|
|
|
+ <body>
|
|
|
+ <![CDATA[
|
|
|
+ if len(self.transition_times) == 0:
|
|
|
+ return
|
|
|
+ new_time = self.transition_times.pop()
|
|
|
+ for model in self.model.componentSet:
|
|
|
+ if model.old_states[-1][0] == new_time:
|
|
|
+ # Remove the current state
|
|
|
+ del model.old_states[-1]
|
|
|
+ # Set the new (old...) state
|
|
|
+ new_state = model.old_states[-1]
|
|
|
+ model.state = pickle.loads(new_state[1])
|
|
|
+ model.timeLast = new_state[0]
|
|
|
+ ta = model.timeAdvance()
|
|
|
+ model.timeNext = (model.timeLast[0] + ta, model.timeLast[1] + 1 if ta == 0 else 0)
|
|
|
+ self.model.scheduler.massReschedule([model])
|
|
|
+ self.simulation_time = self.transition_times[-1] if len(self.transition_times) > 0 else (0.0, 0)
|
|
|
+ ]]>
|
|
|
+ </body>
|
|
|
+ </method>
|
|
|
+ <scxml initial="main">
|
|
|
+ <!-- Parallel states: one of them controls the simulation flow, the other the type of simulation being performed. -->
|
|
|
+ <parallel id="main">
|
|
|
+ <!-- When an injection is received, just append it to the list of pending injections.
|
|
|
+ These will be processed as soon as the current simulation step is finished.
|
|
|
+ Afterwards, return to the previous state, as there was no change of state. -->
|
|
|
+ <state id="injection_monitor">
|
|
|
+ <state id="inject">
|
|
|
+ <transition port="request" event="inject" target=".">
|
|
|
+ <parameter name="configuration"/>
|
|
|
+ <script>
|
|
|
+ configuration["time"] = (configuration["time"], 1)
|
|
|
+ self.inject_queue.append(configuration)
|
|
|
+ self.inject_queue.sort(key=lambda i: i["time"])
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="inject_ok"/>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ </state>
|
|
|
+ <state id="tracer_monitor">
|
|
|
+ <state id="trace">
|
|
|
+ <transition port="request" event="trace" target=".">
|
|
|
+ <parameter name="filename"/>
|
|
|
+ <script>
|
|
|
+ if filename is not None:
|
|
|
+ self.trace_file = open(filename, 'w')
|
|
|
+ else:
|
|
|
+ self.trace_file = None
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="trace_config_ok"/>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ </state>
|
|
|
+ <!-- The main parallel component: the simulation flow. -->
|
|
|
+ <state id="simulation_flow" initial="initialize">
|
|
|
+ <state id="initialize">
|
|
|
+ <transition target="../check_termination">
|
|
|
+ <raise scope="output" port="reply" event="all_states">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="{m.getModelFullName(): (m.timeNext, m.state) for m in self.model.componentSet}"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ <state id="check_termination" initial="workaround">
|
|
|
+ <onexit>
|
|
|
+ <script>
|
|
|
+ self.simulation_time = self.timeNext
|
|
|
+ </script>
|
|
|
+ </onexit>
|
|
|
+
|
|
|
+ <state id="workaround">
|
|
|
+ <transition after="0" target="../check_termination"/>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="wait">
|
|
|
+ <onexit>
|
|
|
+ <script>
|
|
|
+ diff = time.time() - self.realtime_starttime
|
|
|
+ self.simulation_time = (diff / self.realtime_scale, 1)
|
|
|
+ </script>
|
|
|
+ </onexit>
|
|
|
+ <transition after="self.calculate_after()" target="../check_termination"/>
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/paused')" target="../check_termination"/>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="small_step_check">
|
|
|
+ <transition cond="self.should_terminate(False) == -2" target="../../do_simulation"/>
|
|
|
+ <transition cond="self.should_terminate(False) == -1" target="../check_termination">
|
|
|
+ <raise event="termination_condition"/>
|
|
|
+ </transition>
|
|
|
+ <transition cond="self.should_terminate(False) > -1" target="../check_termination">
|
|
|
+ <script>
|
|
|
+ breakpoint_id = self.process_breakpoints(False)
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="breakpoint_triggered">
|
|
|
+ <parameter expr="breakpoint_id"/>
|
|
|
+ </raise>
|
|
|
+ <raise event="termination_condition"/>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="check_termination">
|
|
|
+ <onentry>
|
|
|
+ <script>
|
|
|
+ self.compute_timeNext()
|
|
|
+ self.the_time = self.calculate_after()
|
|
|
+ </script>
|
|
|
+ </onentry>
|
|
|
+ <!-- Continue simulation -->
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/continuous') and (self.should_terminate(False) == -2)" target="../../do_simulation"/>
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/big_step') and (self.should_terminate(False) == -2)" target="../../do_simulation"/>
|
|
|
+
|
|
|
+ <!-- Realtime and didn't reach the required timeNext yet -->
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/realtime') and (self.should_terminate(True) == -2) and (self.the_time > 0.0)" target="../wait"/>
|
|
|
+
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/realtime') and (self.should_terminate(True) == -2) and (self.the_time <= 0.0)" target="../../do_simulation"/>
|
|
|
+
|
|
|
+ <transition port="request" event="small_step" target="../small_step_check">
|
|
|
+ <parameter name="configuration" type="dict" default="{}"/>
|
|
|
+ <script>
|
|
|
+ self.parse_options(configuration)
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+
|
|
|
+ <!-- Pause simulation -->
|
|
|
+ <transition cond="(not INSTATE('../../../simulation_state/paused') and INSTATE('../../../simulation_state/realtime') and (self.should_terminate(True) == -1))" target="../workaround">
|
|
|
+ <raise event="termination_condition"/>
|
|
|
+ </transition>
|
|
|
+ <transition cond="(not INSTATE('../../../simulation_state/paused') and not INSTATE('../../../simulation_state/realtime') and (self.should_terminate(False) == -1))" target="../workaround">
|
|
|
+ <raise event="termination_condition"/>
|
|
|
+ </transition>
|
|
|
+ <transition cond="(not INSTATE('../../../simulation_state/paused')) and INSTATE('../../../simulation_state/realtime') and (self.should_terminate(True) > -1)" target="../workaround">
|
|
|
+ <script>
|
|
|
+ breakpoint_id = self.process_breakpoints(True)
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="breakpoint_triggered">
|
|
|
+ <parameter expr="breakpoint_id"/>
|
|
|
+ </raise>
|
|
|
+ <raise event="termination_condition"/>
|
|
|
+ </transition>
|
|
|
+ <transition cond="(not INSTATE('../../../simulation_state/paused')) and not INSTATE('../../../simulation_state/realtime') and (self.should_terminate(False) > -1)" target="../workaround">
|
|
|
+ <script>
|
|
|
+ breakpoint_id = self.process_breakpoints(False)
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="breakpoint_triggered">
|
|
|
+ <parameter expr="breakpoint_id"/>
|
|
|
+ </raise>
|
|
|
+ <raise event="termination_condition"/>
|
|
|
+ </transition>
|
|
|
+
|
|
|
+ <!-- Process god event -->
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/paused')" port="request" event="god_event" target="../workaround">
|
|
|
+ <parameter name="configuration"/>
|
|
|
+ <script>
|
|
|
+ modelname = configuration["model"]
|
|
|
+ state_attribute = configuration["attribute"]
|
|
|
+ new_value = configuration["value"]
|
|
|
+ model = self.find_model_with_name(modelname)
|
|
|
+ setattr(model.state, state_attribute, new_value)
|
|
|
+ # Call the timeadvance method again and compute new ta
|
|
|
+ ta = model.timeAdvance()
|
|
|
+ model.timeNext = (model.timeLast[0] + ta, 1 if ta else (model.timeLast[1] + 1))
|
|
|
+ self.model.scheduler.massReschedule([model])
|
|
|
+ # self.simulation_time = self.model.scheduler.readFirst()
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="god_event_ok">
|
|
|
+ <parameter expr="{model.getModelFullName(): str(model.state)}"/>
|
|
|
+ </raise>
|
|
|
+ <raise scope="output" port="reply" event="new_tn">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="{model.getModelFullName(): model.timeNext}"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+
|
|
|
+ <!-- Omniscient debugging -->
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/paused')" port="request" event="backwards_step" target="../workaround">
|
|
|
+ <script>
|
|
|
+ self.rollback_step()
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="all_states">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="{m.getModelFullName(): (m.timeNext, m.state) for m in self.model.componentSet}"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+
|
|
|
+ </state>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="do_simulation" initial="init">
|
|
|
+ <state id="init">
|
|
|
+ <onexit>
|
|
|
+ <script>
|
|
|
+ self.find_internal_imminents()
|
|
|
+ </script>
|
|
|
+ </onexit>
|
|
|
+ <transition cond="not INSTATE('../../../simulation_state/paused')" target="../found_internal_imminents"/>
|
|
|
+ <!-- Always output this if paused, as we only got here because a small step was fired previously -->
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/paused')" target="../found_internal_imminents">
|
|
|
+ <raise scope="output" port="reply" event="imminents">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="self.serialize('imminents', self.imminents)"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="found_internal_imminents">
|
|
|
+ <onexit>
|
|
|
+ <script>
|
|
|
+ self.compute_outputfunction()
|
|
|
+ </script>
|
|
|
+ </onexit>
|
|
|
+ <transition cond="not INSTATE('../../../simulation_state/paused')" target="../computed_outputfunction"/>
|
|
|
+ <transition port="request" event="small_step" target="../computed_outputfunction">
|
|
|
+ <raise scope="output" port="reply" event="outbags">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="self.serialize('outbags', self.outbags)"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="computed_outputfunction">
|
|
|
+ <onexit>
|
|
|
+ <script>
|
|
|
+ self.route_messages()
|
|
|
+ self.process_injects()
|
|
|
+ </script>
|
|
|
+ </onexit>
|
|
|
+ <transition cond="not INSTATE('../../../simulation_state/paused')" target="../routed_messages">
|
|
|
+ </transition>
|
|
|
+ <transition port="request" event="small_step" target="../routed_messages">
|
|
|
+ <raise scope="output" port="reply" event="inbags">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="self.serialize('inbags', self.inbags)"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="routed_messages">
|
|
|
+ <onexit>
|
|
|
+ <script>
|
|
|
+ self.find_all_imminents()
|
|
|
+ </script>
|
|
|
+ </onexit>
|
|
|
+ <transition cond="not INSTATE('../../../simulation_state/paused')" target="../found_all_imminents">
|
|
|
+ </transition>
|
|
|
+ <transition port="request" event="small_step" target="../found_all_imminents">
|
|
|
+ <raise scope="output" port="reply" event="transitioning">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="self.serialize('transitioning', self.transitioning)"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="found_all_imminents">
|
|
|
+ <onexit>
|
|
|
+ <script>
|
|
|
+ self.compute_transitions()
|
|
|
+ </script>
|
|
|
+ </onexit>
|
|
|
+ <transition cond="not INSTATE('../../../simulation_state/paused')" target="../computed_transitions"/>
|
|
|
+ <transition port="request" event="small_step" target="../computed_transitions">
|
|
|
+ <raise scope="output" port="reply" event="new_states">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="self.serialize('new_states', self.new_states)"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="computed_transitions">
|
|
|
+ <onexit>
|
|
|
+ <script>
|
|
|
+ self.compute_ta()
|
|
|
+ </script>
|
|
|
+ </onexit>
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/continuous')" target="../../check_termination"/>
|
|
|
+ <transition port="request" event="small_step" target="../../check_termination">
|
|
|
+ <raise scope="output" port="reply" event="new_tn">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="self.serialize('new_tn', self.new_tn)"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ <transition cond="INSTATE('../../../simulation_state/realtime') or INSTATE('../../../simulation_state/big_step')" target="../../check_termination">
|
|
|
+ <raise event="big_step_done"/>
|
|
|
+ <raise scope="output" port="reply" event="new_states">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="self.serialize('new_states', self.new_states)"/>
|
|
|
+ </raise>
|
|
|
+ <raise scope="output" port="reply" event="new_tn">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="self.serialize('new_tn', self.new_tn)"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ </state>
|
|
|
+ </state>
|
|
|
+
|
|
|
+ <state id="simulation_state" initial="paused">
|
|
|
+ <state id="paused">
|
|
|
+ <transition port="request" event="simulate" target="../continuous">
|
|
|
+ <parameter name="configuration" type="dict" default="{}"/>
|
|
|
+ <script>
|
|
|
+ self.parse_options(configuration)
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ <transition port="request" event="realtime" target="../realtime">
|
|
|
+ <parameter name="configuration" type="dict" default="{}"/>
|
|
|
+ <script>
|
|
|
+ self.parse_options(configuration)
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ <transition port="request" event="big_step" target="../big_step">
|
|
|
+ <parameter name="configuration" type="dict" default="{}"/>
|
|
|
+ <script>
|
|
|
+ self.parse_options(configuration)
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ <state id="continuous">
|
|
|
+ <transition port="request" event="pause" target=".">
|
|
|
+ <script>
|
|
|
+ # Just override termination condition
|
|
|
+ self.termination_condition = lambda i, j, k : True
|
|
|
+ self.termination_time = None
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ <transition event="termination_condition" target="../paused">
|
|
|
+ <raise port="reply" event="terminate">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ </raise>
|
|
|
+ <script>
|
|
|
+ self.flush_file()
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="all_states">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ <parameter expr="{m.getModelFullName(): (m.timeNext, m.state) for m in self.model.componentSet}"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ <state id="realtime">
|
|
|
+ <transition port="request" event="pause" target=".">
|
|
|
+ <script>
|
|
|
+ # Just override termination condition
|
|
|
+ self.termination_condition = lambda i, j, k : True
|
|
|
+ self.termination_time = None
|
|
|
+
|
|
|
+ # Don't forget to correctly set the simulation time
|
|
|
+ diff = time.time() - self.realtime_starttime
|
|
|
+ self.simulation_time = (diff / self.realtime_scale, 1)
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ <transition event="termination_condition" target="../paused">
|
|
|
+ <raise port="reply" event="terminate">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ </raise>
|
|
|
+ <script>
|
|
|
+ self.flush_file()
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ <state id="big_step">
|
|
|
+ <transition event="big_step_done" target="../paused"/>
|
|
|
+ <transition event="termination_condition" target="../paused">
|
|
|
+ <raise port="reply" event="terminate">
|
|
|
+ <parameter expr="self.simulation_time"/>
|
|
|
+ </raise>
|
|
|
+ <script>
|
|
|
+ self.flush_file()
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ </state>
|
|
|
+ <state id="breakpoint_manager">
|
|
|
+ <state id="breakpoint_manage">
|
|
|
+ <transition port="request" event="add_breakpoint" target=".">
|
|
|
+ <parameter name="breakpoint_id"/>
|
|
|
+ <parameter name="function"/>
|
|
|
+ <parameter name="enabled"/>
|
|
|
+ <parameter name="disable_on_trigger"/>
|
|
|
+ <script>
|
|
|
+ self.breakpoints.append(Breakpoint(breakpoint_id, function, enabled, disable_on_trigger))
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ <transition port="request" event="del_breakpoint" target=".">
|
|
|
+ <parameter name="del_breakpoint_id"/>
|
|
|
+ <script>
|
|
|
+ self.breakpoints = [breakpoint for breakpoint in self.breakpoints if breakpoint.id != del_breakpoint_id]
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ <transition port="request" event="toggle_breakpoint" target=".">
|
|
|
+ <parameter name="breakpoint_id"/>
|
|
|
+ <parameter name="enabled"/>
|
|
|
+ <script>
|
|
|
+ for breakpoint in self.breakpoints:
|
|
|
+ if breakpoint.id == breakpoint_id:
|
|
|
+ breakpoint.enabled = enabled
|
|
|
+ break
|
|
|
+ </script>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ </state>
|
|
|
+ <state id="reset">
|
|
|
+ <state id="reset">
|
|
|
+ <transition port="request" event="reset" target=".">
|
|
|
+ <script>
|
|
|
+ for model in self.model.componentSet:
|
|
|
+ model.state = pickle.loads(self.save_model[model][1])
|
|
|
+ model.elapsed = self.save_model[model][0]
|
|
|
+ model.timeLast = (-model.elapsed, 1)
|
|
|
+ ta = model.timeAdvance()
|
|
|
+ model.timeNext = (model.timeLast[0] + ta, 1)
|
|
|
+ self.simulation_time = (0.0, 0)
|
|
|
+ self.model.scheduler.massReschedule(self.model.componentSet)
|
|
|
+
|
|
|
+ # Reset trace file
|
|
|
+ if self.trace_file is not None:
|
|
|
+ self.trace_file = open(self.trace_file.name, 'w')
|
|
|
+ </script>
|
|
|
+ <raise scope="output" port="reply" event="all_states">
|
|
|
+ <parameter expr="(0.0, 0)"/>
|
|
|
+ <parameter expr="{m.getModelFullName(): (m.timeNext, m.state) for m in self.model.componentSet}"/>
|
|
|
+ </raise>
|
|
|
+ </transition>
|
|
|
+ </state>
|
|
|
+ </state>
|
|
|
+ </parallel>
|
|
|
+ </scxml>
|
|
|
+ </class>
|
|
|
+</diagram>
|