Browse Source

Add PyPDEVS service

Yentl Van Tendeloo 7 years ago
parent
commit
355ac36136

+ 161 - 0
interface/PDEVS/pypdevs/examples/client.py

@@ -0,0 +1,161 @@
+print("SCCD PythonPDEVS CLI interface")
+import sys
+import time
+from pprint import pprint
+sys.path.append("../src/")
+from sccd.runtime.statecharts_core import Event
+
+import simulator
+from ps_model import Root
+controller = simulator.Controller(Root())
+reply_port = controller.addOutputListener('reply')
+
+def set_defaults(inp, defaultlist):
+    for i, v in enumerate(defaultlist):
+        if len(inp) == i + 1:
+            inp.append(v)
+
+def get_bool(val):
+    if val.lower() in ["true", "yes", "1"]:
+        return True
+    else:
+        return False
+
+def poll_port(port):
+    while 1:
+        try:
+            event = port.fetch(-1)
+            print("%s" % event.getName())
+            pprint(event.getParameters())
+        except:
+            pass
+
+def generate_input():
+    print("READY for input")
+    print("Type 'help' for information")
+    while 1:
+        inp = raw_input().split(" ")
+        action = inp[0]
+        if inp[0] == "simulate":
+            set_defaults(inp, ['inf'])
+            params = [{"termination_time": float(inp[1])}]
+        elif inp[0] == "big_step":
+            set_defaults(inp, ['inf'])
+            params = [{"termination_time": float(inp[1])}]
+        elif inp[0] == "realtime":
+            set_defaults(inp, ['inf', 1.0])
+            params = [{"termination_time": float(inp[1]), "realtime_scale": float(inp[2])}]
+        elif inp[0] == "small_step":
+            set_defaults(inp, ['inf'])
+            params = [{"termination_time": float(inp[1])}]
+        elif inp[0] == "god_event":
+            if len(inp) != 4:
+                print("God Events require 3 parameters!")
+                continue
+            params = [{"model": inp[1], "attribute": inp[2], "value": inp[3]}]
+        elif inp[0] == "inject":
+            if len(inp) != 4:
+                print("Injects require 3 parameters!")
+                continue
+            params = [{"time": float(inp[1]), "port": inp[2], "event": inp[3]}]
+        elif inp[0] == "trace":
+            set_defaults(inp, [None])
+            params = [inp[1]]
+        elif inp[0] == "pause":
+            params = []
+        elif inp[0] == "exit" or inp[0] == "quit":
+            break
+        elif inp[0] == "add_breakpoint":
+            if len(inp) < 5:
+                print("Breakpoint removal requires 2 parameters!")
+                continue
+            # Merge together the final part again
+            inp = [inp[0], inp[1], " ".join(inp[2:-2]).replace("\\n", "\n"), get_bool(inp[-2]), get_bool(inp[-1])]
+            print("Generated parameters: " + str(inp))
+            params = inp[1:]
+        elif inp[0] == "del_breakpoint":
+            if len(inp) < 2:
+                print("Breakpoint removal requires 1 parameter!")
+                continue
+            params = [inp[1]]
+        elif inp[0] == "enable_breakpoint":
+            action = "toggle_breakpoint"
+            params = [inp[1], True]
+        elif inp[0] == "disable_breakpoint":
+            action = "toggle_breakpoint"
+            params = [inp[1], False]
+        elif inp[0] == "reset":
+            params = []
+        elif inp[0] == "backwards_step":
+            params = []
+        elif inp[0] == "help":
+            print("Supported operations:")
+            print("  simulate [termination_time]")
+            print("   --> Simulate until termination time is reached")
+            print("  big_step [termination_time]")
+            print("   --> Simulate a single step, unless termination time is reached")
+            print("  small_step [termination_time]")
+            print("   --> Simulate a single internal simulation step")
+            print("       Termination time is ONLY checked at the")
+            print("       check_termination phase")
+            print("  backwards_step")
+            print("   --> Step back to the previous time")
+            print("  realtime [termination_time [realtime_scale]]")
+            print("   --> Simulate in realtime until simulation time is reached")
+            print("       realtime_scale can speed up or slow down the pace")
+            print("  god_event model_name attribute_name new_value")
+            print("   --> Modify the internal state of an arbitrary model")
+            print("       model_name should be the fully qualified name")
+            print("       attribute_name is the name of the attribute to alter")
+            print("       new_value is the value to assign")
+            print("       new_value can only be a string due to string-only input")
+            print("  inject time port_name event")
+            print("   --> Put a user-defined event on an input port")
+            print("       port_name should be a fully qualified port name")
+            print("       event should be the event to put on it, string only")
+            print("  trace [filename]")
+            print("   --> Write out trace information to the specified file.")
+            print("       Leave empty to disable tracing.")
+            print("  add_breakpoint id function enabled disable_on_trigger")
+            print("   --> Add a breakpoint that will pause simulation when function returns True")
+            print("       the function should contain a definition of the 'breakpoint' function")
+            print("       and must take 3 parameters: time, model and transitioned")
+            print("       enabled indicates whether or not the breakpoint should be checked")
+            print("       disable_on_trigger determines if the breakpoint should auto-disable")
+            print("       after being triggered")
+            print("  del_breakpoint id")
+            print("   --> Remove a breakpoint")
+            print("  enable_breakpoint id")
+            print("   --> Enable the provided breakpoint")
+            print("  disable_breakpoint id")
+            print("   --> Disable the provided breakpoint")
+            print("  reset")
+            print("   --> Reset the simulation")
+            print("  exit")
+            print("   --> Stop the client")
+            print("  pause")
+            print("   --> Pause the simulation")
+            print("  quit")
+            print("   --> Stop the client")
+            print("")
+            print("Defaults: ")
+            print("  termination_time --> 'inf'")
+            print("  realtime_scale   --> 1.0")
+            continue
+        else:
+            print("Command not understood: " + str(inp))
+            continue
+        controller.addInput(Event(action, "request", params))
+      
+import threading
+poll_thrd = threading.Thread(target=poll_port,args=[reply_port])
+poll_thrd.daemon = True
+poll_thrd.start()
+inp_thrd = threading.Thread(target=generate_input)
+inp_thrd.daemon = True
+inp_thrd.start()
+          
+try:
+    controller.start()
+finally:
+    controller.stop()

+ 36 - 0
interface/PDEVS/pypdevs/examples/experiment.py

@@ -0,0 +1,36 @@
+import sys
+import time
+sys.path.append("../src/")
+from python_runtime.statecharts_core import Event
+
+import sccd
+from model import TrafficSystem
+controller = sccd.Controller(TrafficSystem(name="MyTrafficSystem"))
+controller.start()
+reply_port = controller.addOutputListener('reply')
+"""
+params = [{"termination_time": 10, "realtime_scale": 3.0}]
+controller.addInput(Event("realtime", "request", params))
+print(reply_port.fetch(-1))
+params = [{"termination_time": 20, "realtime_scale": 0.333}]
+controller.addInput(Event("realtime", "request", params))
+print(reply_port.fetch(-1))
+params = [{"termination_time": 50, "realtime_scale": 1.0}]
+controller.addInput(Event("simulate", "request", params))
+print(reply_port.fetch(-1))
+"""
+params = [{"termination_time": 50}]
+controller.addInput(Event("big_step", "request", params))
+print(reply_port.fetch(-1))
+for _ in range(10):
+    controller.addInput(Event("small_step", "request", params))
+    #print(reply_port.fetch(-1))
+controller.addInput(Event("big_step", "request", params))
+print(reply_port.fetch(-1))
+params = [{"model": "MyTrafficSystem.trafficLight", "attribute": "colour", "value": "green"}]
+controller.addInput(Event("god_event", "request", params))
+print(reply_port.fetch(-1))
+params = [{"termination_time": 5000}]
+controller.addInput(Event("simulate", "request", params))
+print(reply_port.fetch(-1))
+controller.stop()

+ 129 - 0
interface/PDEVS/pypdevs/examples/exported_model.py

@@ -0,0 +1,129 @@
+from DEVS import *
+from infinity import INFINITY
+
+class Generator(AtomicDEVS):
+        def __init__(self, name="Generator"):
+                AtomicDEVS.__init__(self, name)
+                self.state = {'name': "generating"}
+                self.my_ports = {"g_out": self.addOutPort("g_out")}
+        def timeAdvance(self):
+
+                if self.state['name'] == 'generating':
+                        return 1
+
+        def outputFnc(self):
+
+                if self.state['name'] == 'generating':
+                        return {self.my_ports['g_out']: [{'type': 'Job', 'repr': {'duration': 0.3}}]}
+
+        def intTransition(self):
+
+                if self.state['name'] == 'generating':
+                        new_state = {}
+                        new_state['name'] = 'generating'
+                        return new_state
+
+        def extTransition(self, my_inputs):
+
+                inputs = {k.getPortName(): v for k, v in my_inputs.iteritems()}
+                return AtomicDEVS.extTransition(self, my_inputs)
+
+        def confTransition(self, my_inputs):
+
+                inputs = {k.getPortName(): v for k, v in my_inputs.iteritems()}
+                return AtomicDEVS.confTransition(self, my_inputs)
+
+class Processor(AtomicDEVS):
+        def __init__(self, name="Processor"):
+                AtomicDEVS.__init__(self, name)
+                self.state = {'name': "idle"}
+                self.my_ports = {"p_in": self.addInPort("p_in"), "p_out": self.addOutPort("p_out")}
+        def timeAdvance(self):
+
+                if self.state['name'] == 'processing':
+                        return self.state['job']['duration']
+                if self.state['name'] == 'idle':
+                        return INFINITY
+
+        def outputFnc(self):
+
+                if self.state['name'] == 'processing':
+                        return {self.my_ports['p_out']: [{'type': 'Job', 'repr': self.state['job']}]}
+                if self.state['name'] == 'idle':
+                        return {}
+
+        def intTransition(self):
+
+                if self.state['name'] == 'processing':
+                        new_state = {}
+                        new_state['name'] = 'idle'
+                        return new_state
+
+        def extTransition(self, my_inputs):
+
+                inputs = {k.getPortName(): v for k, v in my_inputs.iteritems()}
+                if self.state['name'] == 'idle':
+                        new_state = {}
+                        new_state['name'] = 'processing'
+                        new_state['job'] = inputs['p_in'][0]['repr']
+                        return new_state
+                else:
+                        return AtomicDEVS.extTransition(self, my_inputs)
+
+        def confTransition(self, my_inputs):
+
+                inputs = {k.getPortName(): v for k, v in my_inputs.iteritems()}
+                return AtomicDEVS.confTransition(self, my_inputs)
+
+class Collector(AtomicDEVS):
+        def __init__(self, name="Collector"):
+                AtomicDEVS.__init__(self, name)
+                self.state = {'name': "waiting"}
+                self.my_ports = {"c_in": self.addInPort("c_in")}
+        def timeAdvance(self):
+
+                if self.state['name'] == 'waiting':
+                        return INFINITY
+
+        def outputFnc(self):
+
+                if self.state['name'] == 'waiting':
+                        return {}
+
+        def intTransition(self):
+
+                return AtomicDEVS.intTransition(self, my_inputs)
+
+        def extTransition(self, my_inputs):
+
+                inputs = {k.getPortName(): v for k, v in my_inputs.iteritems()}
+                if self.state['name'] == 'waiting':
+                        new_state = {}
+                        new_state['name'] = 'waiting'
+                        new_state['nr_of_jobs'] = self.state['nr_of_jobs'] + 1
+                        return new_state
+                else:
+                        return AtomicDEVS.extTransition(self, my_inputs)
+
+        def confTransition(self, my_inputs):
+
+                inputs = {k.getPortName(): v for k, v in my_inputs.iteritems()}
+                return AtomicDEVS.confTransition(self, my_inputs)
+
+class CoupledProcessor(CoupledDEVS):
+        def __init__(self, name="CoupledProcessor"):
+                CoupledDEVS.__init__(self, name)
+                self.my_ports = {"cp_out": self.addOutPort("cp_out"), "cp_in": self.addInPort("cp_in")}
+                self.submodels = {"p1": self.addSubModel(Processor(name="p1")), "p2": self.addSubModel(Processor(name="p2"))}
+                self.connectPorts(self.my_ports["cp_in"], self.submodels["p1"].my_ports["p_in"])
+                self.connectPorts(self.submodels["p1"].my_ports["p_out"], self.submodels["p2"].my_ports["p_in"])
+                self.connectPorts(self.submodels["p2"].my_ports["p_out"], self.my_ports["cp_out"])
+
+class Root(CoupledDEVS):
+        def __init__(self, name="Root"):
+                CoupledDEVS.__init__(self, name)
+                self.my_ports = {}
+                self.submodels = {"generator": self.addSubModel(Generator(name="generator")), "coupledprocessor": self.addSubModel(CoupledProcessor(name="coupledprocessor")), "processor": self.addSubModel(Processor(name="processor")), "collector": self.addSubModel(Collector(name="collector"))}
+                self.connectPorts(self.submodels["coupledprocessor"].my_ports["cp_out"], self.submodels["processor"].my_ports["p_in"])
+                self.connectPorts(self.submodels["generator"].my_ports["g_out"], self.submodels["coupledprocessor"].my_ports["cp_in"])
+                self.connectPorts(self.submodels["processor"].my_ports["p_out"], self.submodels["collector"].my_ports["c_in"])

+ 29 - 0
interface/PDEVS/pypdevs/examples/helpers.py

@@ -0,0 +1,29 @@
+def canRiotAPC(queue):
+    t_q = [i['type'] for i in queue]
+    return t_q.count('Wheel') >= 4 and t_q.count('Body') >= 1 and t_q.count('WaterCannon') >= 1
+
+def isRiotAPC(queue):
+    if canRiotAPC(queue):
+        toremove = ['Wheel','Wheel','Wheel','Wheel','Body','WaterCannon']
+        while toremove:
+            tofind = toremove.pop()
+            for idx, it in enumerate(queue):
+                if (it['type'] == tofind):
+                    break
+            del queue[idx]
+        return True
+
+def canWarAPC(queue):
+    t_q = [i['type'] for i in queue]
+    return t_q.count('Tracks') >= 2 and t_q.count('Body') >= 1 and t_q.count('MachineGun') >= 1
+        
+def isWarAPC(queue):
+    if canWarAPC(queue):
+        toremove = ['Tracks','Tracks','Body','MachineGun']
+        while toremove:
+            tofind = toremove.pop()
+            for idx, it in enumerate(queue):
+                if (it['type'] == tofind):
+                    break
+            del queue[idx]
+        return True

+ 1 - 0
interface/PDEVS/pypdevs/examples/infinity.py

@@ -0,0 +1 @@
+INFINITY = float('inf')

+ 286 - 0
interface/PDEVS/pypdevs/examples/model.py

@@ -0,0 +1,286 @@
+# Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at 
+# McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+
+# Import code for DEVS model representation:
+from DEVS import *
+from infinity import INFINITY
+
+class TrafficLightMode:
+    """
+    Encapsulates the system's state
+    """
+
+    def __init__(self, current="red"):
+        """
+        Constructor (parameterizable).
+        """
+        self.set(current)
+
+    def set(self, value="red"):
+        self.__colour=value
+
+    def get(self):
+        return self.__colour
+
+    def __str__(self):
+        return self.get()
+
+class TrafficLight(AtomicDEVS):
+    """
+    A traffic light 
+    """
+  
+    def __init__(self, name=None):
+        """
+        Constructor (parameterizable).
+        """
+        # Always call parent class' constructor FIRST:
+        AtomicDEVS.__init__(self, name)
+    
+        # STATE:
+        #  Define 'state' attribute (initial sate):
+        self.state = TrafficLightMode("red") 
+
+        # ELAPSED TIME:
+        #  Initialize 'elapsed time' attribute if required
+        #  (by default, value is 0.0):
+        self.elapsed = 1.5 
+        # with elapsed time initially 1.5 and initially in 
+        # state "red", which has a time advance of 60,
+        # there are 60-1.5 = 58.5time-units  remaining until the first 
+        # internal transition 
+    
+        # PORTS:
+        #  Declare as many input and output ports as desired
+        #  (usually store returned references in local variables):
+        self.INTERRUPT = self.addInPort(name="INTERRUPT")
+        self.OBSERVED = self.addOutPort(name="OBSERVED")
+
+    def extTransition(self, inputs):
+        """
+        External Transition Function.
+        """
+        # Compute the new state 'Snew' based (typically) on current
+        # State, Elapsed time parameters and calls to 'self.peek(self.IN)'.
+        input = inputs.get(self.INTERRUPT)[0]
+
+        state = self.state.get()
+
+        if input == "toManual":
+            if state == "manual":
+                # staying in manual mode
+                return TrafficLightMode("manual")
+            elif state in ("red", "green", "yellow"):
+                return TrafficLightMode("manual")
+        elif input == "toAutonomous":
+            if state == "manual":
+                return TrafficLightMode("red")
+            elif state in ("red", "green", "yellow"):
+                # If toAutonomous is given while still autonomous, just stay in this state
+                return self.state
+        raise DEVSException(\
+            "unknown state <%s> in TrafficLight external transition function"\
+            % state) 
+
+    def intTransition(self):
+        """
+        Internal Transition Function.
+        """
+
+        state = self.state.get()
+
+        if state == "red":
+            return TrafficLightMode("green")
+        elif state == "green":
+            return TrafficLightMode("yellow")
+        elif state == "yellow":
+            return TrafficLightMode("red")
+        else:
+            raise DEVSException(\
+                "unknown state <%s> in TrafficLight internal transition function"\
+                % state)
+  
+    def outputFnc(self):
+        """
+        Output Funtion.
+        """
+   
+        # A colourblind observer sees "grey" instead of "red" or "green".
+ 
+        # BEWARE: ouput is based on the OLD state
+        # and is produced BEFORE making the transition.
+        # We'll encode an "observation" of the state the
+        # system will transition to !
+
+        # Send messages (events) to a subset of the atomic-DEVS' 
+        # output ports by means of the 'poke' method, i.e.:
+        # The content of the messages is based (typically) on current State.
+ 
+        state = self.state.get()
+
+        if state == "red":
+            return {self.OBSERVED: ["grey"]}
+        elif state == "green":
+            return {self.OBSERVED: ["yellow"]}
+        elif state == "yellow":
+            return {self.OBSERVED: ["grey"]}
+        else:
+            raise DEVSException(\
+                "unknown state <%s> in TrafficLight external transition function"\
+                % state)
+    
+    def timeAdvance(self):
+        """
+        Time-Advance Function.
+        """
+        # Compute 'ta', the time to the next scheduled internal transition,
+        # based (typically) on current State.
+        state = self.state.get()
+        if state == "red":
+            return 60 
+        elif state == "green":
+            return 50 
+        elif state == "yellow":
+            return 10 
+        elif state == "manual":
+            return INFINITY 
+        else:
+            raise DEVSException(\
+                "unknown state <%s> in TrafficLight time advance transition function"\
+                % state)
+
+class PolicemanMode:
+    """
+    Encapsulates the Policeman's state
+    """
+    def __init__(self, current="idle"):
+        """
+        Constructor (parameterizable).
+        """
+        self.set(current)
+
+    def set(self, value="idle"):
+        self.__mode=value
+
+    def get(self):
+        return self.__mode
+
+    def __str__(self):
+        return self.get()
+
+class Policeman(AtomicDEVS):
+    """
+    A policeman producing "toManual" and "toAutonomous" events:
+    "toManual" when going from "idle" to "working" mode
+    "toAutonomous" when going from "working" to "idle" mode
+    """
+    def __init__(self, name=None):
+        """
+        Constructor (parameterizable).
+        """
+    
+        # Always call parent class' constructor FIRST:
+        AtomicDEVS.__init__(self, name)
+    
+        # STATE:
+        #  Define 'state' attribute (initial sate):
+        self.state = PolicemanMode("idle") 
+
+        # ELAPSED TIME:
+        #  Initialize 'elapsed time' attribute if required
+        #  (by default, value is 0.0):
+        self.elapsed = 0 
+    
+        # PORTS:
+        #  Declare as many input and output ports as desired
+        #  (usually store returned references in local variables):
+        self.OUT = self.addOutPort(name="OUT")
+
+    def intTransition(self):
+        """
+        Internal Transition Function.
+        The policeman works forever, so only one mode. 
+        """
+  
+        state = self.state.get()
+
+        if state == "idle":
+            return PolicemanMode("working")
+        elif state == "working":
+            return PolicemanMode("idle")
+        else:
+            raise DEVSException(\
+                "unknown state <%s> in Policeman internal transition function"\
+                % state)
+    
+    def outputFnc(self):
+        """
+        Output Funtion.
+        """
+        # Send messages (events) to a subset of the atomic-DEVS' 
+        # output ports by means of the 'poke' method, i.e.:
+        # The content of the messages is based (typically) on current State.
+        state = self.state.get()
+        if state == "idle":
+            return {self.OUT: ["toManual"]}
+        elif state == "working":
+            return {self.OUT: ["toAutonomous"]}
+        else:
+            raise DEVSException(\
+                "unknown state <%s> in Policeman output function"\
+                % state)
+    
+    def timeAdvance(self):
+        """
+        Time-Advance Function.
+        """
+        # Compute 'ta', the time to the next scheduled internal transition,
+        # based (typically) on current State.
+    
+        state = self.state.get()
+
+        if state == "idle":
+            return 200 
+        elif state == "working":
+            return 100 
+        else:
+            raise DEVSException(\
+                "unknown state <%s> in Policeman time advance function"\
+                % state)
+
+class Root(CoupledDEVS):
+    def __init__(self, name="Root"):
+        """
+        A simple traffic system consisting of a Policeman and a TrafficLight.
+        """
+        # Always call parent class' constructor FIRST:
+        CoupledDEVS.__init__(self, name)
+
+        # Declare the coupled model's output ports:
+        # Autonomous, so no output ports
+
+        # Declare the coupled model's sub-models:
+
+        # The Policeman generating interrupts 
+        self.policeman = self.addSubModel(Policeman(name="policeman"))
+
+        # The TrafficLight 
+        self.trafficLight = self.addSubModel(TrafficLight(name="trafficLight"))
+
+        # Only connect ...
+        self.connectPorts(self.policeman.OUT, self.trafficLight.INTERRUPT)
+

File diff suppressed because it is too large
+ 430 - 0
interface/PDEVS/pypdevs/examples/ps_model.py


+ 536 - 0
interface/PDEVS/pypdevs/src/DEVS.py

@@ -0,0 +1,536 @@
+# -*- coding: Latin-1 -*-
+"""
+Classes and tools for DEVS model specification
+"""
+
+from devsexception import DEVSException
+
+class BaseDEVS(object):
+    """
+    Abstract base class for AtomicDEVS and CoupledDEVS classes.
+  
+    This class provides basic DEVS attributes and query/set methods.
+    """
+    def __init__(self, name):
+        """
+        Constructor
+
+        :param name: the name of the DEVS model
+        """
+    
+        # Prevent any attempt to instantiate this abstract class
+        if self.__class__ == BaseDEVS:
+            raise DEVSException ("Cannot instantiate abstract class '%s' ... " 
+                                 % (self.__class__.__name__))
+
+        # The parent of the current model
+        self.parent = None
+        # The local name of the model
+        self.name = name
+        self.IPorts  = []  
+        self.OPorts   = []
+        self.ports = []
+
+        # Initialise the times
+        self.timeLast = (0.0, 0)
+        self.timeNext = (0.0, 1)
+
+        self.location = None
+    
+        # Variables used for optimisations
+        self.myInput = {}  
+        self.myOutput = {}
+
+        # The state queue, used for time warp
+        self.oldStates = []
+        # List of all memoized states, only useful in distributed simulation 
+        #   with memoization enabled
+        self.memo = []
+
+    def modelTransition(self, state):
+        """
+        DEFAULT function for Dynamic Structure DEVS, always returning False (thus indicating that no structural change is needed)
+
+        :param state: a dict that can be used to save some kind of state, this object is maintained by the kernel and will be passed each time
+        :returns: bool -- whether or not a structural change is necessary
+        """
+        return False
+
+    def addPort(self, name, isInput):
+        """
+        Utility function to create a new port and add it everywhere where it is necessary
+
+        :param name: the name of the port
+        :param isInput: whether or not this is an input port
+        """
+        name = name if name is not None else "port%s" % len(self.ports)
+        port = Port(isInput=isInput, name=name) 
+        if isInput:
+            self.IPorts.append(port)
+        else:
+            self.OPorts.append(port)
+        port.port_id = len(self.ports)
+        self.ports.append(port)
+        port.hostDEVS = self
+        return port
+      
+    def addInPort(self, name=None):
+        """
+        Add an input port to the DEVS model.
+        
+        addInPort is the only proper way to add input ports to a DEVS model. 
+        As for the CoupledDEVS.addSubModel method, calls
+        to addInPort and addOutPort can appear in any DEVS'
+        descriptive class constructor, or the methods can be used with an
+        instantiated object.
+    
+        The methods add a reference to the new port in the DEVS' IPorts 
+        attributes and set the port's hostDEVS attribute. The modeler
+        should typically save the returned reference somewhere.
+
+        :param name: the name of the port. A unique ID will be generated in case None is passed
+        :returns: port -- the generated port
+        """
+        return self.addPort(name, True)
+      
+    def addOutPort(self, name=None):
+        """Add an output port to the DEVS model.
+
+        addOutPort is the only proper way to
+        add output ports to DEVS. As for the CoupledDEVS.addSubModel method, calls
+        to addInPort and addOutPort can appear in any DEVS'
+        descriptive class constructor, or the methods can be used with an
+        instantiated object.
+    
+        The methods add a reference to the new port in the DEVS'
+        OPorts attributes and set the port's hostDEVS attribute. The modeler
+        should typically save the returned reference somewhere.
+
+        :param name: the name of the port. A unique ID will be generated in case None is passed
+        :returns: port -- the generated port
+        """
+        return self.addPort(name, False)
+    
+    def getModelName(self):
+        """
+        Get the local model name
+
+        :returns: string -- the name of the model
+        """
+        return str(self.name)
+
+    def getModelFullName(self):
+        """
+        Get the full model name, including the path from the root
+
+        :returns: string -- the fully qualified name of the model
+        """
+        return self.fullName
+
+class AtomicDEVS(BaseDEVS):
+    """
+    Abstract base class for all atomic-DEVS descriptive classes.
+    """
+  
+    def __init__(self, name=None):
+        """
+        Constructor for an AtomicDEVS model
+
+        :param name: name of the model, can be None to have an automatically generated name
+        """
+        # Prevent any attempt to instantiate this abstract class
+        if self.__class__ == AtomicDEVS:
+            raise DEVSException("Cannot instantiate abstract class '%s' ... " 
+                                % (self.__class__.__name__))
+
+        # The minimal constructor shall first call the superclass
+        # (i.e., BaseDEVS) constructor.
+        BaseDEVS.__init__(self, name)
+    
+        self.elapsed = 0.0 
+        self.state = None
+
+    def extTransition(self, inputs):
+        """
+        DEFAULT External Transition Function.
+  
+        Accesses state and elapsed attributes, as well as inputs
+        through the passed dictionary. Returns the new state.
+
+        .. note:: Should only write to the *state* attribute.
+
+        :param inputs: dictionary containing all ports and their corresponding outputs
+        :returns: state -- the new state of the model
+        """
+        return self.state
+    
+    def intTransition(self):
+        """
+        DEFAULT Internal Transition Function.
+ 
+        .. note:: Should only write to the *state* attribute.
+
+        :returns: state -- the new state of the model
+
+        .. versionchanged:: 2.1 The *elapsed* attribute is no longer guaranteed to be correct as this isn't required by the DEVS formalism.
+
+        """
+        return self.state
+
+    def confTransition(self, inputs):
+        """
+        DEFAULT Confluent Transition Function.
+  
+        Accesses state and elapsed attributes, as well as inputs
+        through the passed dictionary. Returns the new state.
+
+        .. note:: Should only write to the *state* attribute.
+
+        :param inputs: dictionary containing all ports and their corresponding outputs
+        :returns: state -- the new state of the model
+        """
+        self.state = self.intTransition()
+        self.state = self.extTransition(inputs)
+        return self.state
+  
+    def outputFnc(self):
+        """
+        DEFAULT Output Function.
+  
+        Accesses only state attribute. Returns the output on the different ports as a dictionary.
+
+        .. note:: Should **not** write to any attribute.
+
+        :returns: dictionary containing output ports as keys and lists of output on that port as value
+
+        .. versionchanged:: 2.1 The *elapsed* attribute is no longer guaranteed to be correct as this isn't required by the DEVS formalism.
+
+        """
+        return {}
+  
+    def timeAdvance(self):
+        """
+        DEFAULT Time Advance Function.
+    
+        .. note:: Should ideally be deterministic, though this is not mandatory for simulation.
+
+        :returns: the time advance of the model
+
+        .. versionchanged:: 2.1 The *elapsed* attribute is no longer guaranteed to be correct as this isn't required by the DEVS formalism.
+
+        """
+        # By default, return infinity 
+        return float('inf')
+
+    def finalize(self, name, model_counter, model_ids, locations, selectHierarchy):
+        """
+        Finalize the model hierarchy by doing all pre-simulation configuration
+
+        .. note:: Parameters *model_ids* and *locations* are updated by reference.
+
+        :param name: the name of the hierarchy above
+        :param model_counter: the model ID counter
+        :param model_ids: a list with all model_ids and their model
+        :param locations: dictionary of locations and where every model runs
+        :param selectHierarchy: hierarchy to perform selections in Classic DEVS
+
+        :returns: int -- the new model ID counter
+        """
+        # Give a name
+        self.fullName = name + str(self.getModelName())
+
+        # Give a unique ID to the model itself
+        self.model_id = model_counter
+        self.selectHierarchy = selectHierarchy + [self]
+
+        # Add the element to its designated place in the model_ids list
+        model_ids.append(self)
+
+        # Do a quick check, since this is vital to correct operation
+        if model_ids[self.model_id] != self:
+            raise DEVSException("Something went wrong while initializing models: IDs don't match")
+
+        locations[self.location].append(self.model_id)
+
+        # Return the unique ID counter, incremented so it stays unique
+        return model_counter + 1
+
+class CoupledDEVS(BaseDEVS):
+    """
+    Abstract base class for all coupled-DEVS descriptive classes.
+    """
+  
+    def __init__(self, name=None):
+        """
+        Constructor.
+
+        :param name: the name of the coupled model, can be None for an automatically generated name
+        """
+        # Prevent any attempt to instantiate this abstract class
+        if self.__class__ == CoupledDEVS:
+            raise DEVSException("Cannot instantiate abstract class '%s' ... " 
+                                % (self.__class__.__name__))
+        # The minimal constructor shall first call the superclass
+        # (i.e., BaseDEVS) constructor.
+        BaseDEVS.__init__(self, name)
+    
+        # All components of this coupled model (the submodels)
+        self.componentSet = []
+
+    def finalize(self, name, model_counter, model_ids, locations, selectHierarchy):
+        """
+        Finalize the model hierarchy by doing all pre-simulation configuration
+
+        .. note:: Parameters *model_ids* and *locations* are updated by reference.
+
+        :param name: the name of the hierarchy above
+        :param model_counter: the model ID counter
+        :param model_ids: a list with all model_ids and their model
+        :param locations: dictionary of locations and where every model runs
+        :param selectHierarchy: hierarchy to perform selections in Classic DEVS
+
+        :returns: int -- the new model ID counter
+        """
+        # Set name, even though it will never be requested
+        self.fullName = name + str(self.getModelName())
+        for i in self.componentSet:
+            model_counter = i.finalize(self.fullName + ".", model_counter, 
+                    model_ids, locations, selectHierarchy + [self])
+        return model_counter
+
+    def addSubModel(self, model, location = None):
+        """
+        Adds a specified model to the current coupled model as its child. This
+        is the function that must be used to make distributed simulation
+        possible.
+
+        :param model: the model to be added as a child
+        :param location: the location at which the child must run
+        :returns: model -- the model that was created as a child
+
+        .. versionchanged:: 2.1.3
+           model can no longer be a string, this was previously a lot more efficient in partial distribution, though this functionality was removed together with the partial distribution functionality.
+        """
+        model.parent = self
+        if location is not None:
+            location = int(location)
+        model.location = location if location is not None else self.location
+        if model.location is not None and isinstance(model, CoupledDEVS):
+            # Set the location of all children
+            for i in model.componentSet:
+                i.setLocation(model.location)
+        self.componentSet.append(model)        
+        if hasattr(self, "fullName"):
+            # Full Name is only created when starting the simulation, so we are currently in a running simulation
+            # Dynamic Structure change
+            self.server.getSelfProxy().dsScheduleModel(model)
+        return model
+
+    def connectPorts(self, p1, p2, z = None):
+        """
+        Connects two ports together. The coupling is to begin at p1 and
+        to end at p2.
+
+        :param p1: the port at the start of the new connection
+        :param p2: the port at the end of the new connection
+        :param z: the translation function for the events
+                  either input-to-input, output-to-input or output-to-output.
+        """
+        # For a coupling to be valid, two requirements must be met:
+        # 1- at least one of the DEVS the ports belong to is a child of the
+        #    coupled-DEVS (i.e., self), while the other is either the
+        #    coupled-DEVS itself or another of its children. The DEVS'
+        #    'parenthood relationship' uniquely determine the type of coupling;
+        # 2- the types of the ports are consistent with the 'parenthood' of the
+        #    associated DEVS. This validates the coupling determined above.
+
+        # Internal Coupling:
+        if ((p1.hostDEVS.parent == self and p2.hostDEVS.parent == self) and
+                (p1.type() == 'OUTPORT' and p2.type() == 'INPORT')):
+            if p1.hostDEVS is p2.hostDEVS:
+                raise DEVSException(("In coupled model '%s', connecting ports" +
+                                    " '%s' and '%s' belong to the same model" +
+                                    " '%s'. " +
+                                    " Direct feedback coupling not allowed") % (
+                                    self.getModelFullName(),
+                                    p1.getPortFullName(),
+                                    p2.getPortFullName(),
+                                    p1.hostDEVS.getModelFullName()))
+            else:
+                p1.outLine.append(p2)
+                p2.inLine.append(p1)
+        
+        # External input couplings:
+        elif ((p1.hostDEVS == self and p2.hostDEVS.parent == self) and
+              (p1.type() == p2.type() == 'INPORT')):
+            p1.outLine.append(p2)
+            p2.inLine.append(p1)
+   
+        # Eternal output couplings:
+        elif ((p1.hostDEVS.parent == self and p2.hostDEVS == self) and
+              (p1.type() == p2.type() == 'OUTPORT')):
+            p1.outLine.append(p2)
+            p2.inLine.append(p1)
+
+        # Other cases (illegal coupling):
+        else:
+            raise DEVSException(("Illegal coupling in coupled model '%s' " +
+                                "between ports '%s' and '%s'") % (
+                                self.getModelName(), p1.getPortName(), 
+                                p2.getPortName()))
+
+        p1.zFunctions[p2] = z
+        if hasattr(self, "server"):
+            self.server.getSelfProxy().dsUndoDirectConnect()
+
+class RootDEVS(BaseDEVS):
+    """
+    The artificial RootDEVS model is the only 'coupled' model in the simulation after direct connection is performed.
+    """
+    def __init__(self, components, models, schedulerType):
+        """
+        Basic constructor.
+
+        :param components: the atomic DEVS models that are the cildren, only those that are ran locally should be mentioned
+        :param models: all models that have to be passed to the scheduler, thus all models, even non-local ones
+        :param schedulerType: type of scheduler to use (string representation)
+        """
+        BaseDEVS.__init__(self, "ROOT model")
+        self.componentSet = components
+        self.timeNext = (float('inf'), 1)
+        self.local_model_ids = set()
+        for i in self.componentSet:
+            self.local_model_ids.add(i.model_id)
+        self.models = models
+        self.schedulerType = schedulerType
+        self.directConnected = True
+
+    def directConnect(self):
+        """
+        Perform direct connection on the models again
+        """
+        directConnect(self.models, True)
+
+    def setTimeNext(self):
+        """
+        Reset the timeNext
+        """
+        try:
+            self.timeNext = self.scheduler.readFirst()
+        except IndexError:
+            # No element found in the scheduler, so put it to INFINITY
+            self.timeNext = (float('inf'), 1)
+
+class Port(object):
+    """
+    Class for DEVS model ports (both input and output). This class provides basic port attributes and query methods.
+    """
+    def __init__(self, isInput, name=None):
+        """
+        Constructor. Creates an input port if isInput evaluates to True, and
+        an output port otherwise.
+
+        :param isInput: whether or not this is an input port
+        :param name: the name of the port. If None is provided, a unique ID is generated
+        """
+        self.inLine = [] 
+        self.outLine = []
+        self.hostDEVS = None 
+        self.msgcount = 0
+   
+        # The name of the port
+        self.name = name
+        self.isInput = isInput
+        self.zFunctions = {}
+
+    def getPortName(self):
+        """
+        Returns the name of the port
+
+        :returns: local name of the port
+        """
+        return self.name
+
+    def getPortFullName(self):
+        """
+        Returns the complete name of the port
+
+        :returns: fully qualified name of the port
+        """
+        return "%s.%s" % (self.hostDEVS.getModelFullName(), self.getPortName())
+
+    def type(self):
+        """
+        Returns the 'type' of the object
+
+        :returns: either 'INPORT' or 'OUTPORT'
+        """
+        if self.isInput:
+            return 'INPORT'
+        else:
+            return 'OUTPORT'
+
+def appendZ(first_z, new_z):
+    if first_z is None:
+        return new_z
+    elif new_z is None:
+        return first_z
+    else:
+        return lambda x: new_z(first_z(x))
+
+def directConnect(componentSet, local):
+    """
+    Perform direct connection on this CoupledDEVS model
+
+    :param componentSet: the iterable to direct connect
+    :param local: whether or not simulation is local; if it is, dynamic structure code will be prepared
+    :returns: the direct connected componentSet
+    """
+    newlist = []
+    for i in componentSet:
+        if isinstance(i, CoupledDEVS):
+            componentSet.extend(i.componentSet)
+        else:
+            # Found an atomic model
+            newlist.append(i)
+    componentSet = newlist
+
+    # All and only all atomic models are now direct children of this model
+    for i in componentSet:
+        # Remap the output ports
+        for outport in i.OPorts:
+            # The new contents of the line
+            outport.routingOutLine = []
+            worklist = [(p, outport.zFunctions.get(p, None)) for p in outport.outLine]
+            for outline, z in worklist:
+                # If it is a coupled model, we must expand this model
+                if isinstance(outline.hostDEVS, CoupledDEVS):
+                    for inline in outline.outLine:
+                        # Add it to the current iterating list, so we can just continue
+                        worklist.append((inline, appendZ(z, outline.zFunctions[inline])))
+                        # If it is a Coupled model, we should just continue 
+                        # expanding it and not add it to the finished line
+                        if not isinstance(inline.hostDEVS, CoupledDEVS):
+                            outport.routingOutLine.append((inline, appendZ(z, outline.zFunctions[inline])))
+                else:
+                    for ol, z in outport.routingOutLine:
+                        if ol == outline:
+                            break
+                    else:
+                        # Add to the new line if it isn't already there
+                        # Note that it isn't really mandatory to check for this, 
+                        # it is a lot cleaner to do so.
+                        # This will greatly increase the complexity of the connector though
+                        outport.routingOutLine.append((outline, z))
+        # Remap the input ports: identical to the output ports, only in the reverse direction
+        for inport in i.IPorts:
+            inport.routingInLine = []
+            for inline in inport.inLine:
+                if isinstance(inline.hostDEVS, CoupledDEVS):
+                    for outline in inline.inLine:
+                        inport.inLine.append(outline)
+                        if not isinstance(outline.hostDEVS, CoupledDEVS):
+                            inport.routingInLine.append(outline)
+                elif inline not in inport.routingInLine:
+                    inport.routingInLine.append(inline)
+    return componentSet

+ 17 - 0
interface/PDEVS/pypdevs/src/devsexception.py

@@ -0,0 +1,17 @@
+class DEVSException(Exception):
+    """
+    DEVS specific exceptions
+    """
+    def __init__(self, message="not specified in source"):
+        """
+        Constructor
+
+        :param message: error message to print
+        """
+        Exception.__init__(self, message)
+
+    def __str__(self):
+        """
+        String representation of the exception
+        """
+        return "DEVS Exception: " + str(self.message)

+ 1 - 0
interface/PDEVS/pypdevs/src/infinity.py

@@ -0,0 +1 @@
+INFINITY = float('inf')

+ 121 - 0
interface/PDEVS/pypdevs/src/mvk_widget.py

@@ -0,0 +1,121 @@
+'''
+Created on 27-jul.-2014
+
+@author: Simon
+'''
+import Tkinter as tk
+from python_runtime.statecharts_core import Event
+
+class MvKWidget:
+	controller = None
+
+	def __init__(self, configure_later=False):
+		if not configure_later:
+			self.set_bindable_and_tagorid(None, None)
+
+	def set_bindable_and_tagorid(self, bindable=None, tagorid=None):
+		if bindable is None:
+			bindable = self
+		self.bindable = bindable
+		self.mytagorid = tagorid
+		if isinstance(self, tk.Toplevel):
+			self.protocol("WM_DELETE_WINDOW", self.window_close)
+		if tagorid is not None:
+			if not isinstance(tagorid, list):
+				tagorid = [tagorid]
+			for t in tagorid:
+				self.bindable.tag_bind(t, "<Button>", self.on_click)
+				self.bindable.tag_bind(t, "<ButtonRelease>", self.on_release)
+				self.bindable.tag_bind(t, "<Motion>", self.on_motion)
+				self.bindable.tag_bind(t, "<Enter>", self.on_enter)
+				self.bindable.tag_bind(t, "<Leave>", self.on_leave)
+				self.bindable.tag_bind(t, "<Key>", self.on_key)
+				self.bindable.tag_bind(t, "<KeyRelease>", self.on_key_release)
+		else:
+			self.bindable.bind("<Button>", self.on_click)
+			self.bindable.bind("<ButtonRelease>", self.on_release)
+			self.bindable.bind("<Motion>", self.on_motion)
+			self.bindable.bind("<Enter>", self.on_enter)
+			self.bindable.bind("<Leave>", self.on_leave)
+			self.bindable.bind("<Key>", self.on_key)
+			self.bindable.bind("<KeyRelease>", self.on_key_release)
+		self.last_x = 50
+		self.last_y = 50
+
+	def on_click(self, event):
+		event_name = None
+
+		if event.num == 1:
+			event_name = "left-click"
+		elif event.num == 2:
+			event_name = "middle-click"
+		elif event.num == 3:
+			event_name = "right-click"
+
+		if event_name:
+			self.last_x = event.x
+			self.last_y = event.y
+			MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+
+	def on_release(self, event):
+		event_name = None
+
+		if event.num == 1:
+			event_name = "left-release"
+		elif event.num == 2:
+			event_name = "middle-release"
+		elif event.num == 3:
+			event_name = "right-release"
+
+		if event_name:
+			self.last_x = event.x
+			self.last_y = event.y
+			MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+
+	def on_motion(self, event):
+		self.last_x = event.x
+		self.last_y = event.y
+		MvKWidget.controller.addInput(Event("motion", "input", [id(self)]))
+
+	def on_enter(self, event):
+		MvKWidget.controller.addInput(Event("enter", "input", [id(self)]))
+
+	def on_leave(self, event):
+		MvKWidget.controller.addInput(Event("leave", "input", [id(self)]))
+
+	def on_key(self, event):
+		event_name = None
+
+		if event.keysym == 'Escape':
+			event_name = "escape"
+		elif event.keysym == 'Return':
+			event_name = "return"
+		elif event.keysym == 'Delete':
+			event_name = "delete"
+		elif event.keysym == 'Shift_L':
+			event_name = "shift"
+		elif event.keysym == 'Control_L':
+			event_name = "control"
+
+		if event_name:
+			MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+
+	def on_key_release(self, event):
+		event_name = None
+
+		if event.keysym == 'Escape':
+			event_name = "escape-release"
+		elif event.keysym == 'Return':
+			event_name = "return-release"
+		elif event.keysym == 'Delete':
+			event_name = "delete-release"
+		elif event.keysym == 'Shift_L':
+			event_name = "shift-release"
+		elif event.keysym == 'Control_L':
+			event_name = "control-release"
+
+		if event_name:
+			MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+
+	def window_close(self):
+		MvKWidget.controller.addInput(Event("window-close", "input", [id(self)]))

+ 176 - 0
interface/PDEVS/pypdevs/src/scheduler.py

@@ -0,0 +1,176 @@
+# -*- coding: Latin-1 -*-
+"""
+The Activity Heap is based on a heap, though allows for reschedules. 
+
+To allow reschedules to happen, a model is accompagnied by a flag to 
+indicate whether or not it is still valid. 
+As soon as a model is rescheduled, the flag of the previously scheduled 
+time is set and another entry is added. This causes the heap to become *dirty*, 
+requiring a check for the flag as soon as the first element is requested.
+
+Due to the possibility for a dirty heap, the heap will be cleaned up as 
+soon as the number of invalid elements becomes too high. 
+This cleanup method has O(n) complexity and is therefore only 
+ran when the heap becomes way too dirty.
+
+Another problem is that it might consume more memory than other schedulers, 
+due to invalid elements being kept in memory. 
+However, the actual model and states are not duplicated as they are references. 
+The additional memory requirement should not be a problem in most situations.
+
+The 'activity' part from the name stems from the fact that only models where 
+the *timeNext* attribute is smaller than infinity will be scheduled. 
+Since these elements are not added to the heap, they aren't taken into account 
+in the complexity. This allows for severe optimisations in situations where 
+a lot of models can be scheduled for infinity.
+
+Of all provided schedulers, this one is the most mature due to it being the 
+oldest and also the default scheduler. It is also applicable in every situation 
+and it offers sufficient performance in most cases.
+
+This scheduler is ideal in situations where (nearly) no reschedules happen 
+and where most models transition at a different time.
+
+It results in slow behaviour in situations requiring lots of rescheduling, 
+and thus lots of dirty elements.
+
+This method is also applied in the VLE simulator and is the common approach 
+to heap schedulers that require invalidation. It varies from the scheduler in 
+ADEVS due to the heap from the heapq library being used, which doesn't offer 
+functions to restructure the heap. 
+Reimplementing these methods in pure Python would be unnecessarily slow.
+"""
+from heapq import heappush, heappop, heapify
+
+class Scheduler(object):
+    """
+    Scheduler class itself
+    """
+    def __init__(self, models, epsilon, totalModels):
+        """
+        Constructor
+
+        :param models: all models in the simulation
+        """
+        self.heap = []
+        self.id_fetch = [None] * totalModels
+        for model in models:
+            if model.timeNext[0] != float('inf'):
+                self.id_fetch[model.model_id] = [model.timeNext, model.model_id, True, model]
+                heappush(self.heap, self.id_fetch[model.model_id])
+            else:
+                self.id_fetch[model.model_id] = [model.timeNext, model.model_id, False, model]
+        
+        self.invalids = 0
+        self.maxInvalids = len(models)*2
+        self.epsilon = epsilon
+
+    def schedule(self, model):
+        """
+        Schedule a model
+
+        :param model: the model to schedule
+        """
+        # Create the entry, as we have accepted the model
+        elem = [model.timeNext, model.model_id, False, model]
+        try:
+            self.id_fetch[model.model_id] = elem
+        except IndexError:
+            # A completely new model
+            self.id_fetch.append(elem)
+            self.maxInvalids += 2
+        # Check if it requires to be scheduled
+        if model.timeNext[0] != float('inf'):
+            self.id_fetch[model.model_id][2] = True
+            heappush(self.heap, self.id_fetch[model.model_id])
+
+    def unschedule(self, model):
+        """
+        Unschedule a model
+
+        :param model: model to unschedule
+        """
+        if model.timeNext != float('inf'):
+            self.invalids += 1
+        # Update the referece still in the heap
+        self.id_fetch[model.model_id][2] = False
+        # Remove the reference in our id_fetch
+        self.id_fetch[model.model_id] = None
+        self.maxInvalids -= 2
+
+    def massReschedule(self, reschedule_set):
+        """
+        Reschedule all models provided. 
+        Equivalent to calling unschedule(model); schedule(model) on every element in the iterable.
+
+        :param reschedule_set: iterable containing all models to reschedule
+        """
+        #NOTE rather dirty, though a lot faster for huge models
+        inf = float('inf')
+        for model in reschedule_set:
+            event = self.id_fetch[model.model_id]
+            if event[2]:
+                if model.timeNext == event[0]:
+                    continue
+                elif event[0][0] != inf:
+                    self.invalids += 1
+                event[2] = False
+            if model.timeNext[0] != inf:
+                self.id_fetch[model.model_id] = [model.timeNext, model.model_id, True, model]
+                heappush(self.heap, self.id_fetch[model.model_id])
+        if self.invalids >= self.maxInvalids:
+            self.heap = [i for i in self.heap if i[2] and (i[0][0] != inf)]
+            heapify(self.heap)
+            self.invalids = 0
+
+    def readFirst(self):
+        """
+        Returns the time of the first model that has to transition
+
+        :returns: timestamp of the first model
+        """
+        self.cleanFirst()
+        return self.heap[0][0]
+
+    def cleanFirst(self):
+        """
+        Clean up the invalid elements in front of the list
+        """
+        try:
+            while not self.heap[0][2]:
+                heappop(self.heap)
+                self.invalids -= 1
+        except IndexError:
+            # Nothing left, so it as clean as can be
+            pass
+
+    def getImminent(self, time):
+        """
+        Returns a list of all models that transition at the provided time, with a specified epsilon deviation allowed.
+
+        :param time: timestamp to check for models
+
+        .. warning:: For efficiency, this method only checks the **first** elements, so trying to invoke this function with a timestamp higher than the value provided with the *readFirst* method, will **always** return an empty set.
+        """
+        immChildren = []
+        t, age = time
+        try:
+            # Age must be exactly the same
+            first = self.heap[0]
+            while (first[0][0] <= t) and (first[0][1] == age):
+                # Check if the found event is actually still active
+                if(first[2]):
+                    # Active, so event is imminent
+                    immChildren.append(first[3])
+                    first[2] = False
+                else:
+                    # Wasn't active, but we will have to pop this to get the next
+                    # So we can lower the number of invalids
+                    self.invalids -= 1
+
+                # Advance the while loop
+                heappop(self.heap)
+                first = self.heap[0]
+        except IndexError:
+            pass
+        return immChildren

File diff suppressed because it is too large
+ 1021 - 0
interface/PDEVS/pypdevs/src/simulator.py


+ 808 - 0
interface/PDEVS/pypdevs/src/simulator.xml

@@ -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) &gt; -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 &gt; 0.0)" target="../wait"/>
+
+                            <transition cond="INSTATE('../../../simulation_state/realtime') and (self.should_terminate(True) == -2) and (self.the_time &lt;= 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) &gt; -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) &gt; -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>

+ 61 - 0
scripts/DEVS_service.py

@@ -0,0 +1,61 @@
+import sys
+sys.path.append("wrappers")
+from modelverse import *
+import random, re, json, uuid
+from pprint import pprint
+from multiprocessing import Process, Pipe, freeze_support
+
+#address = "msdl.uantwerpen.be:8001"
+address = "localhost:8001"
+
+sys.path.append('interface/PDEVS/pypdevs/src')
+
+from simulator import Controller
+import threading, time
+
+init(address)
+login("pypdevs_service", "my_password")
+
+def pypdevs_service(port):
+    exec service_get(port) in globals()
+    controller = Controller(Root())
+
+    def inputter():
+        print "Waiting for input..."
+        while 1:
+            input = service_get(port)
+            print 'raw input = %s' % input
+            params = json.loads(input)
+            print 'json parsed = %s' % params
+            if not isinstance(params, list):
+                params = [params]
+            print "Sending input to simulator: %s" % params
+            if (len(params) > 1):
+                controller.addInput(Event(params[0], "request", params[1:]))
+            else:
+                controller.addInput(Event(params[0], "request", []))
+    input_thread = threading.Thread(target=inputter)
+    input_thread.daemon = True
+    input_thread.start()
+
+    output_listener = controller.addOutputListener(["reply"])
+    def outputter():
+        print "Waiting for output..."
+        while 1:
+            output_val = output_listener.fetch(-1)
+            print "Got output from simulator: %s" % output_val
+            service_set(port, json.dumps({"name": output_val.getName(), "port": output_val.getPort(), "params": output_val.getParameters()}))
+    output_thread = threading.Thread(target=outputter)
+    output_thread.daemon = True
+    output_thread.start()
+
+    controller.start()
+
+service_register("pypdevs_simulator", pypdevs_service)
+
+try:
+    while True:
+        # Stay active, as we shouldn't exit while the service is running!
+        time.sleep(1)
+finally:
+    service_stop()

+ 1 - 1
wrappers/modelverse_SCCD.py

@@ -1,7 +1,7 @@
 """
 Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, and Yentl Van Tendeloo (for the inspiration)
 
-Date:   Thu Nov  9 10:14:27 2017
+Date:   Thu Nov  9 10:27:28 2017
 
 Model author: Yentl Van Tendeloo
 Model name:   MvK Server