Преглед на файлове

dynamic DEVSTONE models for benchmarking

Snej69420 преди 3 месеца
родител
ревизия
187c0f7335
променени са 36 файла, в които са добавени 309 реда и са изтрити 93 реда
  1. BIN
      devstone/Plots/DS/DS-HI-Compared_depth_10.png
  2. BIN
      devstone/Plots/DS/DS-HI-Compared_depth_20.png
  3. BIN
      devstone/Plots/DS/DS-HI-Compared_width_10.png
  4. BIN
      devstone/Plots/DS/DS-HI-Compared_width_20.png
  5. BIN
      devstone/Plots/DS/DS-LI-Compared_depth_10.png
  6. BIN
      devstone/Plots/DS/DS-LI-Compared_depth_20.png
  7. BIN
      devstone/Plots/DS/DS-LI-Compared_width_10.png
  8. BIN
      devstone/Plots/DS/DS-LI-Compared_width_20.png
  9. BIN
      devstone/Plots/DS/DS_depth_10.png
  10. BIN
      devstone/Plots/DS/DS_depth_20.png
  11. BIN
      devstone/Plots/DS/DS_width_10.png
  12. BIN
      devstone/Plots/DS/DS_width_20.png
  13. 0 0
      devstone/Plots/HI/HI-Compared_depth_10.png
  14. 0 0
      devstone/Plots/HI/HI-Compared_depth_20.png
  15. 0 0
      devstone/Plots/HI/HI-Compared_width_10.png
  16. 0 0
      devstone/Plots/HI/HI-Compared_width_20.png
  17. 0 0
      devstone/Plots/LI/LI-Compared_depth_10.png
  18. 0 0
      devstone/Plots/LI/LI-Compared_depth_20.png
  19. 0 0
      devstone/Plots/LI/LI-Compared_width_10.png
  20. 0 0
      devstone/Plots/LI/LI-Compared_width_20.png
  21. BIN
      devstone/Results-Plotted/DS/DS-HI-Compared_depth_10.png
  22. BIN
      devstone/Results-Plotted/DS/DS-HI-Compared_depth_20.png
  23. BIN
      devstone/Results-Plotted/DS/DS-HI-Compared_width_10.png
  24. BIN
      devstone/Results-Plotted/DS/DS-HI-Compared_width_20.png
  25. BIN
      devstone/Results-Plotted/DS/DS-LI-Compared_depth_10.png
  26. BIN
      devstone/Results-Plotted/DS/DS-LI-Compared_depth_20.png
  27. BIN
      devstone/Results-Plotted/DS/DS-LI-Compared_width_10.png
  28. BIN
      devstone/Results-Plotted/DS/DS-LI-Compared_width_20.png
  29. BIN
      devstone/Results-Plotted/DS/DS_depth_10.png
  30. BIN
      devstone/Results-Plotted/DS/DS_depth_20.png
  31. BIN
      devstone/Results-Plotted/DS/DS_width_10.png
  32. BIN
      devstone/Results-Plotted/DS/DS_width_20.png
  33. 63 0
      devstone/compare.py
  34. 194 50
      devstone/pythonpdevs/devstone.py
  35. 9 3
      devstone/pythonpdevs/main.py
  36. 43 40
      devstone/testPyPDEVS.py

BIN
devstone/Plots/DS/DS-HI-Compared_depth_10.png


BIN
devstone/Plots/DS/DS-HI-Compared_depth_20.png


BIN
devstone/Plots/DS/DS-HI-Compared_width_10.png


BIN
devstone/Plots/DS/DS-HI-Compared_width_20.png


BIN
devstone/Plots/DS/DS-LI-Compared_depth_10.png


BIN
devstone/Plots/DS/DS-LI-Compared_depth_20.png


BIN
devstone/Plots/DS/DS-LI-Compared_width_10.png


BIN
devstone/Plots/DS/DS-LI-Compared_width_20.png


BIN
devstone/Plots/DS/DS_depth_10.png


BIN
devstone/Plots/DS/DS_depth_20.png


BIN
devstone/Plots/DS/DS_width_10.png


BIN
devstone/Plots/DS/DS_width_20.png


devstone/Results-Plotted/HI/HI-Compared_depth_10.png → devstone/Plots/HI/HI-Compared_depth_10.png


devstone/Results-Plotted/HI/HI-Compared_depth_20.png → devstone/Plots/HI/HI-Compared_depth_20.png


devstone/Results-Plotted/HI/HI-Compared_width_10.png → devstone/Plots/HI/HI-Compared_width_10.png


devstone/Results-Plotted/HI/HI-Compared_width_20.png → devstone/Plots/HI/HI-Compared_width_20.png


devstone/Results-Plotted/LI/LI-Compared_depth_10.png → devstone/Plots/LI/LI-Compared_depth_10.png


devstone/Results-Plotted/LI/LI-Compared_depth_20.png → devstone/Plots/LI/LI-Compared_depth_20.png


devstone/Results-Plotted/LI/LI-Compared_width_10.png → devstone/Plots/LI/LI-Compared_width_10.png


devstone/Results-Plotted/LI/LI-Compared_width_20.png → devstone/Plots/LI/LI-Compared_width_20.png


BIN
devstone/Results-Plotted/DS/DS-HI-Compared_depth_10.png


BIN
devstone/Results-Plotted/DS/DS-HI-Compared_depth_20.png


BIN
devstone/Results-Plotted/DS/DS-HI-Compared_width_10.png


BIN
devstone/Results-Plotted/DS/DS-HI-Compared_width_20.png


BIN
devstone/Results-Plotted/DS/DS-LI-Compared_depth_10.png


BIN
devstone/Results-Plotted/DS/DS-LI-Compared_depth_20.png


BIN
devstone/Results-Plotted/DS/DS-LI-Compared_width_10.png


BIN
devstone/Results-Plotted/DS/DS-LI-Compared_width_20.png


BIN
devstone/Results-Plotted/DS/DS_depth_10.png


BIN
devstone/Results-Plotted/DS/DS_depth_20.png


BIN
devstone/Results-Plotted/DS/DS_width_10.png


BIN
devstone/Results-Plotted/DS/DS_width_20.png


+ 63 - 0
devstone/compare.py

@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+import os
+import argparse
+import pandas as pd
+import matplotlib.pyplot as plt
+from pathlib import Path
+
+def load_and_filter(csv_path, fixed_param, fixed_value):
+    """Load a CSV file and filter rows by the fixed parameter."""
+    df = pd.read_csv(csv_path)
+    df = df[df[fixed_param] == fixed_value]
+    return df
+
+def process_files(csv_files, fixed_param, fixed_value):
+    """Combine and average simulation times by the varying parameter."""
+    results = {}
+    varying_param = "depth" if fixed_param == "width" else "width"
+
+    for path in csv_files:
+        df = load_and_filter(path, fixed_param, fixed_value)
+        # Average simulation times for identical setups
+        grouped = df.groupby(varying_param, as_index=False)["simulation_time"].mean()
+        results[path.parent.name + "-" + path.stem] = grouped
+
+    return results, varying_param
+
+def plot_results(results, varying_param, fixed_param, fixed_value, name):
+    plt.figure(figsize=(8, 6))
+    for label, df in results.items():
+        plt.plot(df[varying_param], df["simulation_time"], marker='o', label=label)
+
+    plt.title(f"Average Simulation Time vs {varying_param.capitalize()} (fixed {fixed_param}={fixed_value})")
+    plt.xlabel(varying_param.capitalize())
+    plt.ylabel("Average Simulation Time")
+    plt.legend()
+    plt.grid(True)
+
+    path = "../Plots/"
+    output_name = f"{name}_{fixed_param}_{fixed_value}.png"
+    plt.tight_layout()
+    plt.savefig(path + output_name, dpi=300)
+    print(f"✅ Saved plot as {output_name}")
+
+
+def main():
+    parser = argparse.ArgumentParser(description="Compare simulation results from multiple CSV files.")
+    parser.add_argument("csv_files", nargs="+", type=Path, help="Paths to the result CSV files.")
+    parser.add_argument("-n", "--name", default="DEVS", type=str, help="Name of the output file.")
+    group = parser.add_mutually_exclusive_group(required=True)
+    group.add_argument("-w", "--width", type=int, help="Fixed width value.")
+    group.add_argument("-d", "--depth", type=int, help="Fixed depth value.")
+
+    args = parser.parse_args()
+    if args.width is not None:
+        fixed_param, fixed_value = "width", args.width
+    else:
+        fixed_param, fixed_value = "depth", args.depth
+
+    results, varying_param = process_files(args.csv_files, fixed_param, fixed_value)
+    plot_results(results, varying_param, fixed_param, fixed_value, args.name)
+
+if __name__ == "__main__":
+    main()

+ 194 - 50
devstone/pythonpdevs/devstone.py

@@ -8,7 +8,7 @@ from pypdevs.simulator import Simulator
 
 
 class DelayedAtomic(AtomicDEVS):
-    def __init__(self, name: str, int_delay: float, ext_delay: float, add_out_port: bool = False, prep_time=0):
+    def __init__(self, name: str, int_delay: float, ext_delay: float, add_out_port: bool = False, prep_time=0, mode:str = ""):
         super().__init__(name)
 
         self.int_delay = int_delay
@@ -16,12 +16,12 @@ class DelayedAtomic(AtomicDEVS):
         self.prep_time = prep_time
 
         self.i_in = self.addInPort("i_in")
-        if add_out_port:
+        if add_out_port: # for HI and HO models
             self.o_out = self.addOutPort("o_out")
 
-        # used for dynamic structure models
-        self.in_ports = []
-        self.out_ports = []
+        # Dynamic Structure extras
+        self.mode = mode
+        self.out_ports = [] # used for dynamic structure model LI2HI
 
     def intTransition(self):
         if self.int_delay:
@@ -36,8 +36,14 @@ class DelayedAtomic(AtomicDEVS):
             return INFINITY
 
     def outputFnc(self):
-        if hasattr(self, "o_out"):
+        if hasattr(self, "o_out") and self.o_out is not None:
             return {self.o_out: [0]}
+
+        # for DSDEVS LI2HI
+        if len(self.out_ports) == 1 and self.out_ports[0].outline != []:
+            # print(f"{self.name}: should be last call!!")
+            # print("Send to ", self.out_ports[0].outline)
+            return {self.out_ports[0]: [0]}
         return {}
 
     def extTransition(self, inputs):
@@ -47,19 +53,37 @@ class DelayedAtomic(AtomicDEVS):
         return "active"
 
     def modelTransition(self, state):
-        if state["LI2HI"]:
-            if self.name.split("_")[2] != "1":
-                self.in_ports.append(self.addInPort("i_HI"))
-            if self.name.split("_")[2] != "2":
-                self.out_ports.append(self.addOutPort("o_HI"))
+        if self.mode == "LI2HI":
+            return self.LI2HI(state)
+        elif self.mode == "HI2LI":
+            return self.HI2LI(state)
+        return False
+
+    def LI2HI(self, state):
+        if len(self.out_ports) == 0 and not hasattr(self, "o_out"):
+            # every automic dev gets the extra out port
+            # even though the last atomic could miss it see fig
+            # except the atomic model in the coupled model at max depth
+            # which has a output port by default
+            state["LI2HI"] = True
+            state["name"] = self.name
+            self.out_ports.append(self.addOutPort("o_HI")) # standard LI model doesn't have by default
+            # print(f"{self.name}: should be first call!!")
             return True # send to the parent to connect ports
-        else:
-            return False
+        return False
+
+    def HI2LI(self, state):
+        if hasattr(self, "o_out") and self.o_out is not None:
+            state["HI2LI"] = True
+            state["name"] = self.name
+            self.removePort(self.o_out)
+            self.o_out = None
+        return False
 
 
 class DelayedAtomicStats(DelayedAtomic):
-    def __init__(self, name: str, int_delay: float, ext_delay: float, add_out_port: bool = False, prep_time=0):
-        super().__init__(name, int_delay, ext_delay, add_out_port, prep_time)
+    def __init__(self, name: str, int_delay: float, ext_delay: float, add_out_port: bool = False, prep_time=0, mode:str = ""):
+        super().__init__(name, int_delay, ext_delay, add_out_port, prep_time, mode)
 
         self.int_count = 0
         self.ext_count = 0
@@ -76,7 +100,7 @@ class DelayedAtomicStats(DelayedAtomic):
 class DEVStoneWrapper(CoupledDEVS, ABC):
 
     def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float,
-                 add_atomic_out_ports: bool = False, prep_time=0, stats=False):
+                 add_atomic_out_ports: bool = False, prep_time=0, stats=False, mode: str = ""):
         super().__init__(name)
 
         self.depth = depth
@@ -101,9 +125,9 @@ class DEVStoneWrapper(CoupledDEVS, ABC):
 
         if depth == 1:
             if self.stats:
-                atomic = DelayedAtomicStats("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time)
+                atomic = DelayedAtomicStats("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time, mode=mode)
             else:
-                atomic = DelayedAtomic("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time)
+                atomic = DelayedAtomic("Atomic_0_0", int_delay, ext_delay, add_out_port=True, prep_time=prep_time, mode=mode)
 
             self.addSubModel(atomic)
 
@@ -118,10 +142,10 @@ class DEVStoneWrapper(CoupledDEVS, ABC):
             for idx in range(width - 1):
                 if self.stats:
                     atomic = DelayedAtomicStats("Atomic_%d_%d" % (depth - 1, idx), int_delay, ext_delay,
-                                                add_out_port=add_atomic_out_ports, prep_time=prep_time)
+                                                add_out_port=add_atomic_out_ports, prep_time=prep_time, mode=mode)
                 else:
                     atomic = DelayedAtomic("Atomic_%d_%d" % (depth - 1, idx), int_delay, ext_delay,
-                                           add_out_port=add_atomic_out_ports, prep_time=prep_time)
+                                           add_out_port=add_atomic_out_ports, prep_time=prep_time, mode=mode)
                 self.addSubModel(atomic)
 
     @abstractmethod
@@ -270,49 +294,169 @@ class HOmod(CoupledDEVS):
 
 class LI2HI(DEVStoneWrapper):
     """
-    Dynamic DEVStone variant that starts as an LI (loosely interconnected)
-    and gradually transforms into an HI (highly interconnected) model
-    by adding connections during internal transitions.
+    Dynamic DEVStone variant that starts as an LI
+    and (gradually??) transforms into an HI model
     """
 
-    def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float,
-                 prep_time=0, stats=False):
-        super().__init__(name, depth, width, int_delay, ext_delay,
-                         add_atomic_out_ports=True, prep_time=prep_time, stats=stats)
+    def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
+        super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=False, prep_time=prep_time,
+                         stats=stats, mode="LI2HI")
+
+        for idx in range(1, len(self.component_set)):
+            assert isinstance(self.component_set[idx], AtomicDEVS)
+            self.connectPorts(self.i_in, self.component_set[idx].i_in)
+
+    def gen_coupled(self):
+        return LI2HI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
+                  prep_time=self.prep_time, stats=self.stats)
+
+    def modelTransition(self, state):
+        if state.get("LI2HI", True):
+            for i in range(len(self.component_set) - 1): # -1 because the last model can't be connected
+                # basically checks if it is an atomic model and if it got the extra output port
+                if hasattr(self.component_set[i], "out_ports") and len(self.component_set[i].out_ports) == 1:
+                    # print(f"Connect {self.component_set[i].name} to {self.component_set[i+1].name}")
+                    self.connectPorts(self.component_set[i].out_ports[0], self.component_set[i + 1].i_in)
+            return False
+        return False
+
+
+class HI2LI(DEVStoneWrapper):
+    """
+    Dynamic DEVStone variant that starts as an HI
+    and (gradually??) transforms into an LI model
+    """
+    def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
+        super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
+                         stats=stats, mode="HI2LI")
+
+        if len(self.component_set) > 1:
+            assert isinstance(self.component_set[-1], AtomicDEVS)
+            self.connectPorts(self.i_in, self.component_set[-1].i_in)
+
+        for idx in range(1, len(self.component_set) - 1):
+            assert isinstance(self.component_set[idx], AtomicDEVS)
+            self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
+            self.connectPorts(self.i_in, self.component_set[idx].i_in)
+
+    def gen_coupled(self):
+        return HI2LI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
+                  prep_time=self.prep_time, stats=self.stats)
+
+
+class Fertilizer(AtomicDEVS):
+    def __init__(self, name: str, period: float = 1.0, repeat: int = 1):
+        super().__init__(name)
+        self.period = period
+        self.repeat = repeat
+
+        self._counter = 0
+        self._active = True
+
+    def intTransition(self):
+        return "passive" # state is unimportant
+
+    def timeAdvance(self):
+        # stop when counter >= repeat
+        if self._counter >= self.repeat:
+            return INFINITY
+        return self.period
+
+    def outputFnc(self):
+        return {}
+
+    def modelTransition(self, state):
+        # every time TA elapses, this will be called on the atomic:
+        # set a flag the parent coupled can read
+        # increment counter and request modelTransition (return True)
+        if self._counter >= self.repeat:
+            return False
+        self._counter += 1
+        state["generate"] = True
+        return True
 
-        # transformation state tracking
-        self.converted_connections = 0
-        self.total_connections = max(0, len(self.component_set) - 2)
 
-        # initially LI-like connections
+class dLI(DEVStoneWrapper):
+    """
+    A LI model which grows in width
+    """
+    def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
+        super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=False, prep_time=prep_time,
+                         stats=stats, mode="LI-Dynamic")
+
+        self.max_gen = 1
         for idx in range(1, len(self.component_set)):
             assert isinstance(self.component_set[idx], AtomicDEVS)
             self.connectPorts(self.i_in, self.component_set[idx].i_in)
 
-        # output connection
+        self.generator = self.addSubModel(Fertilizer(self.name + "_gen", repeat=self.max_gen))
+
+    def gen_coupled(self):
+        return dLI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
+                   prep_time=self.prep_time, stats=self.stats)
+
+    def modelTransition(self, state):
+        if state.get("generate", True):
+            name = "Atomic_%d_%d" % (self.depth - 1, self.width) if self.depth > 1 else "Atomic_0_%d" % self.width
+            if self.stats:
+                new_atom = DelayedAtomicStats(name, self.int_delay, self.ext_delay,
+                                              add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
+            else:
+                new_atom = DelayedAtomic(name, self.int_delay, self.ext_delay,
+                                         add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
+            self.addSubModel(new_atom)
+            self.connectPorts(self.i_in, new_atom.i_in)
+
+            self.width += 1
+            # print(f"{self.name}: added {new_atom.name} (width is now {self.width})")
+            return False
+        return False
+
+
+class dHI(DEVStoneWrapper):
+    """
+    A HI model which grows in width
+    """
+    def __init__(self, name: str, depth: int, width: int, int_delay: float, ext_delay: float, prep_time=0, stats=False):
+        super().__init__(name, depth, width, int_delay, ext_delay, add_atomic_out_ports=True, prep_time=prep_time,
+                         stats=stats, mode="HI-Dynamic")
+
+        self.max_gen = 10
         if len(self.component_set) > 1:
-            self.connectPorts(self.component_set[-1].o_out, self.o_out)
+            assert isinstance(self.component_set[-1], AtomicDEVS)
+            self.connectPorts(self.i_in, self.component_set[-1].i_in)
+
+        for idx in range(1, len(self.component_set) - 1):
+            assert isinstance(self.component_set[idx], AtomicDEVS)
+            self.connectPorts(self.component_set[idx].o_out, self.component_set[idx + 1].i_in)
+            self.connectPorts(self.i_in, self.component_set[idx].i_in)
+
+        self.generator = self.addSubModel(Fertilizer(self.name + "_gen", repeat=self.max_gen))
 
     def gen_coupled(self):
-        return LI2HI("Coupled_%d" % (self.depth - 1),
-                           self.depth - 1, self.width, self.int_delay, self.ext_delay,
-                           prep_time=self.prep_time, stats=self.stats)
+        return dHI("Coupled_%d" % (self.depth - 1), self.depth - 1, self.width, self.int_delay, self.ext_delay,
+                   prep_time=self.prep_time, stats=self.stats)
 
     def modelTransition(self, state):
-        """
-        Gradually adds HI-style interconnections between atomics.
-        Called automatically during internal transitions.
-        """
-        print("CHANGE")
-
-        # Each step adds one connection
-        if self.converted_connections < self.total_connections:
-            src_idx = self.converted_connections + 1
-            if src_idx < len(self.component_set) - 1:
-                src = self.component_set[src_idx]
-                dst = self.component_set[src_idx + 1]
-                self.connectPorts(src.o_out, dst.i_in)
-            self.converted_connections += 1
+        if state.get("generate", True):
+            name = "Atomic_%d_%d" % (self.depth - 1, self.width) if self.depth > 1 else "Atomic_0_%d" % self.width
+            if self.stats:
+                new_atom = DelayedAtomicStats(name, self.int_delay, self.ext_delay,
+                                              add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
+            else:
+                new_atom = DelayedAtomic(name, self.int_delay, self.ext_delay,
+                                         add_out_port=self.add_atomic_out_ports, prep_time=self.prep_time, mode="LI-Dynamic")
+
+            last = self.component_set[-2]
+
+            self.addSubModel(new_atom)
+            if isinstance(last, AtomicDEVS) and hasattr(last, "o_out"):
+                self.connectPorts(last.o_out, new_atom.i_in)
+            self.connectPorts(self.i_in, new_atom.i_in)
+
+            self.width += 1
+            # print(f"{self.name}: added {new_atom.name} (width is now {self.width})")
+            return False
         return False
 
 

+ 9 - 3
devstone/pythonpdevs/main.py

@@ -4,7 +4,7 @@ import time
 from pypdevs.DEVS import CoupledDEVS
 from pypdevs.simulator import Simulator
 
-from devstone import LI, HI, HO, HOmod, LI2HI
+from devstone import LI, HI, HO, HOmod, LI2HI, HI2LI, dLI, dHI
 from generator import Generator
 
 sys.setrecursionlimit(10000)
@@ -24,13 +24,13 @@ class DEVStoneEnvironment(CoupledDEVS):
             self.connectPorts(generator.o_out, devstone_model.i_in2)
 
 
-MODEL_TYPES = ("LI", "HI", "HO", "HOmod", "LI2HI", "HI2LI")
+MODEL_TYPES = ("LI", "HI", "HO", "HOmod", "LI2HI", "HI2LI", "dLI", "dHI")
 
 
 def parse_args():
     parser = argparse.ArgumentParser(description='Script to compare DEVStone implementations with different engines')
 
-    parser.add_argument('-m', '--model-type', required=True, help='DEVStone model type (LI, HI, HO, HOmod)')
+    parser.add_argument('-m', '--model-type', required=True, help='DEVStone model type (LI, HI, HO, HOmod, LI2HI, HI2LI)')
     parser.add_argument('-d', '--depth', type=int, required=True, help='Number of recursive levels of the model.')
     parser.add_argument('-w', '--width', type=int, required=True, help='Width of each coupled model.')
     parser.add_argument('-i', '--int-cycles', type=int, default=0, help='Dhrystone cycles executed in internal transitions')
@@ -65,6 +65,12 @@ if __name__ == '__main__':
         devstone_model = HOmod("HOmod_root", args.depth, args.width, args.int_cycles, args.ext_cycles, prep_time=1)
     elif args.model_type == "LI2HI":
         devstone_model = LI2HI("LI2HI_root", args.depth, args.width, args.int_cycles, args.ext_cycles, prep_time=1)
+    elif args.model_type == "HI2LI":
+        devstone_model = HI2LI("HI2LI_root", args.depth, args.width, args.int_cycles, args.ext_cycles, prep_time=1)
+    elif args.model_type == "dLI":
+        devstone_model = dLI("dLI_root", args.depth, args.width, args.int_cycles, args.ext_cycles, prep_time=1)
+    elif args.model_type == "dHI":
+        devstone_model = dHI("dHI_root", args.depth, args.width, args.int_cycles, args.ext_cycles, prep_time=1)
 
     env = DEVStoneEnvironment("DEVStoneEnvironment", devstone_model)
     model_created_time = time.time()

+ 43 - 40
devstone/testPyPDEVS.py

@@ -7,13 +7,12 @@ from itertools import product
 from datetime import datetime
 
 MODEL_TYPES = ["LI", "HI", "HO", "HOmod"]
-DS_MODEL_TYPES = ["LI2HI"]
+DS_MODEL_TYPES = ["LI2HI", "HI2LI", "LI-HI", "dLI", "dHI"]
 INT_CYCLES = 0
 EXT_CYCLES = 0
 
 PyPDEVS = "./pythonpdevs/main.py"
 MinimalPyPDEVS = "./pythonpdevs-minimal/main.py"
-OUTPUT_FILE = "./Results-PyPDEVS/DEVSTONE"
 
 # Regex to capture times from script output
 TIME_PATTERN = re.compile(
@@ -43,18 +42,18 @@ def run_devstone(model_type, depth, width, minimal=False, classic=False, dynamic
         cmd.extend(["-C"])
     result = subprocess.run(cmd, capture_output=True, text=True)
     output = result.stdout.strip()
+    # print(output)
     errors = result.stderr.strip()
-    if errors:
-        print(errors)
     match = TIME_PATTERN.search(output)
 
     if match:
         creation, setup, sim = map(float, match.groups())
         return creation, setup, sim, output
-    else:
-        print("Failed to parse output for", cmd)
-        print(output)
-        return None, None, None, output
+
+    print("Failed to run: ", cmd)
+    print(output)
+    print(errors)
+    return None, None, None, output
 
 class EnsureDS(Action):
     def __call__(self, parser, namespace, values, option_string=None):
@@ -64,6 +63,9 @@ class EnsureDS(Action):
 
 def parse_args():
     parser = ArgumentParser()
+    parser.add_argument("-p", "--path", type=str, default="./Results-PyPDEVS/", help="Path to the results directory")
+    parser.add_argument("-f", '--filename', type=str, default="", help="Name of the output file")
+
     parser.add_argument("-M", "--minimal", action="store_true", default=False, help="Uses the minimal DEVS kernel. (Less features => less overhead)")
     parser.add_argument("-C", "--classic", action="store_true", default=False, help="Selects the Classic DEVS Simulator")
     parser.add_argument("-D", "--dynamic", action="store_true", default=False, help="Enables Dynamic Structure DEVS")
@@ -92,35 +94,36 @@ if __name__ == "__main__":
     if w[0] % w[1] != 0:
         raise RuntimeError("Invalid width stepsize.")
 
-    appendix = ""
-    if args.minimal:
-        appendix += "-minimal"
-    if args.classic:
-        appendix += "-classic"
-    if args.dynamic:
-        appendix += "-dynamic"
-
-    with open(OUTPUT_FILE + appendix + ".csv", "w", newline="") as csvfile:
-        fieldnames = ["model_type", "depth", "width", "run",
-                      "creation_time", "setup_time", "simulation_time"]
-        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
-        writer.writeheader()
-
-        for model_type, d, w in product(args.model_structure, range(d[1], d[0]+1, d[1]), range(w[1], w[0]+1, w[1])):
-            for run in range(0, args.repetitions):
-                creation, setup, sim, output = run_devstone(model_type, d, w, args.minimal, args.classic, args.dynamic)
-                if creation is not None:
-                    writer.writerow({
-                        "model_type": model_type,
-                        "depth": d,
-                        "width": w,
-                        "run": run,
-                        "creation_time": creation,
-                        "setup_time": setup,
-                        "simulation_time": sim
-                    })
-                    csvfile.flush()
-                else:
-                    print("Skipping due to parse failure")
-
-    print(f"\n Benchmarking complete. Results saved to {OUTPUT_FILE}")
+    fieldnames = ["model_type", "depth", "width", "run",
+                  "creation_time", "setup_time", "simulation_time"]
+
+    for model_type in args.model_structure:
+        model_filename = f"{args.path}{model_type}"
+        if args.filename:
+            model_filename += f"_{args.filename}"
+        else:
+            model_filename += ".csv"
+        with open(model_filename, "w", newline="") as csvfile:
+            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
+            writer.writeheader()
+
+            for depth in range(d[1], d[0] + 1, d[1]):
+                for width in range(w[1], w[0] + 1, w[1]):
+                    for run in range(args.repetitions):
+                        creation, setup, sim, output = run_devstone(
+                            model_type, depth, width,
+                            args.minimal, args.classic, args.dynamic
+                        )
+                        if creation is not None:
+                            writer.writerow({
+                                "model_type": model_type,
+                                "depth": depth,
+                                "width": width,
+                                "run": run,
+                                "creation_time": creation,
+                                "setup_time": setup,
+                                "simulation_time": sim
+                            })
+                            csvfile.flush()
+        print(f"✅ Saved {model_type} results to {model_filename}")
+