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)
 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 author: Yentl Van Tendeloo
 Model name:   MvK Server
 Model name:   MvK Server