From 4b959bc98b60b20c347991a8b3e1989b675e5efb Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 13 Dec 2024 11:58:27 +0100 Subject: [PATCH 01/10] add starting point for mosis 2024 assignment --- .gitignore | 2 + assignment/.gitignore | 1 + assignment/TIPS.txt | 24 +++++++ assignment/atomicdevs.py | 91 ++++++++++++++++++++++++++ assignment/environment.py | 96 ++++++++++++++++++++++++++++ assignment/plot_template.py | 56 ++++++++++++++++ assignment/runner.py | 109 ++++++++++++++++++++++++++++++++ assignment/system.py | 61 ++++++++++++++++++ examples/queueing/experiment.py | 1 + 9 files changed, 441 insertions(+) create mode 100644 assignment/.gitignore create mode 100644 assignment/TIPS.txt create mode 100644 assignment/atomicdevs.py create mode 100644 assignment/environment.py create mode 100644 assignment/plot_template.py create mode 100644 assignment/runner.py create mode 100644 assignment/system.py diff --git a/.gitignore b/.gitignore index 086bb48..9a9c0be 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ *build* test/output/* /.idea/* + +assignment_output/ \ No newline at end of file diff --git a/assignment/.gitignore b/assignment/.gitignore new file mode 100644 index 0000000..9f3731b --- /dev/null +++ b/assignment/.gitignore @@ -0,0 +1 @@ +*_solution.py \ No newline at end of file diff --git a/assignment/TIPS.txt b/assignment/TIPS.txt new file mode 100644 index 0000000..d16e344 --- /dev/null +++ b/assignment/TIPS.txt @@ -0,0 +1,24 @@ +CODING CONVENTIONS + +1. write your methods always in the following order: + + extTransition + timeAdvance + outputFnc + intTransition + + this reflects the order in which the methods are called by the simulator: + + extTransition always has highest priority (can interrupt anything) + timeAdvance is called before outputFnc + outputFnc is called right before intTransition + + +2. input/output port attributes start with 'in_' and 'out_' + + +TROUBLESHOOTING + + - did you forget to return `self.state` from intTransition or extTransition ? + - did you accidentally write to `self.x` instead of `self.state.x` ? + - did you modify the state in timeAdvance or outputFnc (NOT ALLOWED!!) diff --git a/assignment/atomicdevs.py b/assignment/atomicdevs.py new file mode 100644 index 0000000..f462b69 --- /dev/null +++ b/assignment/atomicdevs.py @@ -0,0 +1,91 @@ + +### EDIT THIS FILE ### + +from pypdevs.DEVS import AtomicDEVS +from environment import * +import random +import dataclasses + +class Queue(AtomicDEVS): + def __init__(self, ship_sizes): + super().__init__("Queue") + # self.state = QueueState(...) + + # def extTransition(self, inputs): + # pass + + # def timeAdvance(self): + # pass + + # def outputFnc(self): + # pass + + # def intTransition(self): + # pass + +PRIORITIZE_BIGGER_SHIPS = 0 +PRIORITIZE_SMALLER_SHIPS = 1 + +class RoundRobinLoadBalancer(AtomicDEVS): + def __init__(self, + lock_capacities=[3,2], # two locks of capacities 3 and 2. + priority=PRIORITIZE_BIGGER_SHIPS, + ): + super().__init__("RoundRobinLoadBalancer") + # self.state = LoadBalancerState(...) + + # def extTransition(self, inputs): + # pass + + # def timeAdvance(self): + # pass + + # def outputFnc(self): + # pass + + # def intTransition(self): + # pass + +class FillErUpLoadBalancer(AtomicDEVS): + def __init__(self, + lock_capacities=[3,2], # two locks of capacities 3 and 2. + priority=PRIORITIZE_BIGGER_SHIPS, + ): + super().__init__("FillErUpLoadBalancer") + # self.state = LoadBalancerState(...) + + # def extTransition(self, inputs): + # pass + + # def timeAdvance(self): + # pass + + # def outputFnc(self): + # pass + + # def intTransition(self): + # pass + +class Lock(AtomicDEVS): + def __init__(self, + capacity=2, # lock capacity (2 means: 2 ships of size 1 will fit, or 1 ship of size 2) + max_wait_duration=60.0, + passthrough_duration=60.0*15.0, # how long does it take for the lock to let a ship pass through it + ): + super().__init__("Lock") + # self.state = LockState(...) + + # def extTransition(self, inputs): + # pass + + # def timeAdvance(self): + # pass + + # def outputFnc(self): + # pass + + # def intTransition(self): + # pass + + +### EDIT THIS FILE ### diff --git a/assignment/environment.py b/assignment/environment.py new file mode 100644 index 0000000..e5fb4fc --- /dev/null +++ b/assignment/environment.py @@ -0,0 +1,96 @@ + +### DO NOT EDIT THIS FILE ### + +from pypdevs.DEVS import AtomicDEVS +import random +import dataclasses + +# The reason for annotating the *State-classes as 'dataclass', is because this automatically generates a nice __repr__-function, so that if the simulator is set to verbose, you can actually see what the state is. + +class Ship: + def __init__(self, size, creation_time): + self.size = size + self.creation_time = creation_time + + # useful in verbose mode: + def __repr__(self): + return f"Ship(size={self.size},created={self.creation_time})" + +@dataclasses.dataclass +class GeneratorState: + current_time: float + time_until_next_ship: float + to_generate: int + random: random.Random + + def __init__(self, seed=0, gen_num=1000): + self.current_time = 0.0 # for statistics only + self.time_until_next_ship = 0.0 + self.to_generate = gen_num + self.random = random.Random(seed) + +class Generator(AtomicDEVS): + def __init__(self, + seed=0, # random seed + lambd=1.0/60.0, # how often to generate a ship - in this example, once per minute + gen_types=[1,1,2], # ship sizes to generate, will be sampled uniformly - in this example, size 1 is twice as likely as size 2. + gen_num=1000, # number of ships total to generate + ): + super().__init__("Generator") + + # State (for everything that is mutable) + self.state = GeneratorState(seed=seed, gen_num=gen_num) + + # I/O + self.out_ship = self.addOutPort("out_event") + + # Parameters (read-only) + self.lambd = lambd + self.gen_types = gen_types + + def timeAdvance(self): + return self.state.time_until_next_ship + + def outputFnc(self): + size = self.state.random.choice(self.gen_types) # uniformly sample from gen_types + # watch out: outputFnc is called *before* intTransition! + creation = self.state.current_time + self.state.time_until_next_ship + return { self.out_ship: Ship(size, creation) } + + def intTransition(self): + self.state.current_time += self.state.time_until_next_ship + self.state.to_generate -= 1 + if self.state.to_generate > 0: + self.state.time_until_next_ship = self.state.random.expovariate(self.lambd) + else: + # stop generating + self.state.time_until_next_ship = float('inf') + return self.state + +@dataclasses.dataclass +class SinkState: + current_time: float + ships: list + + def __init__(self): + self.current_time = 0.0 + self.ships = [] + +class Sink(AtomicDEVS): + def __init__(self): + super().__init__("Sink") + self.state = SinkState() + self.in_ships = self.addInPort("in_ships") + + def extTransition(self, inputs): + self.state.current_time += self.elapsed + if self.in_ships in inputs: + ships = inputs[self.in_ships] + for ship in ships: + ship.finished_time = self.state.current_time + # amount of time spent in the system: + ship.queueing_duration = ship.finished_time - ship.creation_time + self.state.ships.extend(ships) + return self.state + +### DO NOT EDIT THIS FILE ### diff --git a/assignment/plot_template.py b/assignment/plot_template.py new file mode 100644 index 0000000..fced26e --- /dev/null +++ b/assignment/plot_template.py @@ -0,0 +1,56 @@ +def make_plot_ships_script(priority:str, strategy:str, max_waits:list[float], gen_num:int): + return (f""" +### priority={priority}, strategy={strategy} ### + +set terminal svg + +# plot 1. x-axis: ships, y-axis: queuing duration of ship + +set out 'plot_ships_{strategy}_{priority}.svg' +set title "Queueing duration" +set xlabel "Ship #" +set ylabel "Seconds" +#unset xlabel +#unset xtics +set key title "Max Wait" +set key bottom center out +set key horizontal + +""" +# + '\n'.join([ +# f"set style line {i+1} lw 4" +# for i in range(len(max_waits)) +# ]) + + f""" + +# set yrange [0:90000] +set xrange [0:{gen_num}] +set style fill solid + +plot 'output_{strategy}_{priority}.csv' \\\n """ + ", \\\n '' ".join([ + f"using 1:{i+1} title '{max_wait}' w boxes ls {i+1}" + for i, max_wait in enumerate(max_waits) +])) + +def make_plot_box_script(priority:str, strategy:str, max_waits:list[float], gen_num:int): + return (f""" + +# plot 2. x-axis: max-wait parameter, y-axis: queueing durations of ships + +set out 'plot_box_{strategy}_{priority}.svg' +set style fill solid 0.25 border -1 +set style boxplot outliers pointtype 7 +set style data boxplot +set key off + +set xlabel "Max Wait" +unset xrange +unset yrange + +set xtics (""" + ', '.join([ f"'{max_wait}' {i}" + for i, max_wait in enumerate(max_waits)]) + f""") + +plot 'output_{strategy}_{priority}.csv' \\\n """ + ", \\\n '' ".join([ + f"using ({i}):{i+2} title '{max_wait}'" + for i, max_wait in enumerate(max_waits) +])) \ No newline at end of file diff --git a/assignment/runner.py b/assignment/runner.py new file mode 100644 index 0000000..fc4e524 --- /dev/null +++ b/assignment/runner.py @@ -0,0 +1,109 @@ +import os + +from pypdevs.simulator import Simulator +from plot_template import make_plot_ships_script, make_plot_box_script + +# from system_solution import * # Teacher's solution +from system import * + +## Parameters ## + +gen_num = 500 # how many ships to generate + +# How often to generate a ship (on average) +gen_rate = 1/60/4 # once every 4 minutes + +# Ship size will be sampled uniformly from the following list. +gen_types = [1,1,2] # ship size '1' twice as likely to be generated as ship size '2' + +# Load balancer... +priorities = { + # you can outcomment one of these lines to reduce the number of experiments (useful for debugging): + PRIORITIZE_BIGGER_SHIPS: "bigger", + PRIORITIZE_SMALLER_SHIPS: "smaller", +} +strategies = { + # you can outcomment one of these lines to reduce the number of experiments (useful for debugging): + STRATEGY_ROUND_ROBIN: "roundrobin", + STRATEGY_FILL_ER_UP: "fillerup", +} + +# The number of locks and their capacities +lock_capacities=[3,2] # two locks, of capacity 3 and 2 + +# The different parameters to try for lock_max_wait +lock_max_waits = [ 0.0+i*120.0 for i in range(5) ] # all these values will be attempted +# lock_max_waits = [ 15.0 ] # <-- uncomment if you only want to run an experiment with this value (useful for debugging) + +# How long does it take for a ship to pass through a lock +passthrough_duration = 60.0*15 # 15 minutes + +outdir = "assignment_output" + +plots_ships = [] +plots_box = [] + +os.makedirs(outdir, exist_ok=True) + +# try all combinations of priorities and strategies (4 total) +for priority in priorities: + for strategy in strategies: + values = [] + # and in each experiment, try a bunch of different values for the 'lock_max_wait' parameter: + for lock_max_wait in lock_max_waits: + print("Run simulation:", priorities[priority], strategies[strategy], "max_wait =",lock_max_wait) + sys = LockQueueingSystem( + # See system.py for explanation of these values: + seed=0, + gen_num=gen_num, + gen_rate=gen_rate, + gen_types=gen_types, + load_balancer_strategy=strategy, + lock_capacities=lock_capacities, + priority=priority, + lock_max_wait=lock_max_wait, + passthrough_duration=passthrough_duration, + ) + sim = Simulator(sys) + sim.setClassicDEVS() + # sim.setVerbose() # <-- uncomment to see what's going on + sim.simulate() + + # all the ships that made it through + ships = sys.sink.state.ships + values.append([ship.queueing_duration for ship in ships]) + + # Write out all the ship queueuing durations for every 'lock_max_wait' parameter + # for every ship, we write a line: + # , time_max_wait0, time_max_wait1, time_max_wait2, ... time_max_wait10 + filename = f'{outdir}/output_{strategies[strategy]}_{priorities[priority]}.csv' + with open(filename, 'w') as f: + try: + for i in range(gen_num): + f.write("%s" % i) + for j in range(len(values)): + f.write(", %5f" % (values[j][i])) + f.write("\n") + except IndexError as e: + raise Exception("There was an IndexError, meaning that fewer ships have made it to the sink than expected.\nYour model is not (yet) correct.") from e + + # Generate gnuplot code: + plots_ships.append(make_plot_ships_script( + priority=priorities[priority], + strategy=strategies[strategy], + max_waits=lock_max_waits, + gen_num=gen_num, + )) + plots_box.append(make_plot_box_script( + priority=priorities[priority], + strategy=strategies[strategy], + max_waits=lock_max_waits, + gen_num=gen_num, + )) + +# Finally, write out a single gnuplot script that plots everything +with open(f'{outdir}/plot.gnuplot', 'w') as f: + # first plot the ships + f.write('\n\n'.join(plots_ships)) + # then do the box plots + f.write('\n\n'.join(plots_box)) diff --git a/assignment/system.py b/assignment/system.py new file mode 100644 index 0000000..188ce96 --- /dev/null +++ b/assignment/system.py @@ -0,0 +1,61 @@ + +### EDIT THIS FILE ### + +from pypdevs.DEVS import CoupledDEVS +from atomicdevs import * + +STRATEGY_ROUND_ROBIN = 0 +STRATEGY_FILL_ER_UP = 1 + +class LockQueueingSystem(CoupledDEVS): + def __init__(self, + # See runner.py for an explanation of these parameters!! + seed, + gen_num, + gen_rate, + gen_types, + load_balancer_strategy, + lock_capacities, + priority, + lock_max_wait, + passthrough_duration, + ): + super().__init__("LockQueueingSystem") + + # Instantiate sub-models with the right parameters, and add them to the CoupledDEVS: + + generator = self.addSubModel(Generator( + seed=seed, # random seed + lambd=gen_rate, + gen_types=gen_types, + gen_num=gen_num, + )) + + if load_balancer_strategy == STRATEGY_ROUND_ROBIN: + LoadBalancer = RoundRobinLoadBalancer + elif load_balancer_strategy == STRATEGY_FILL_ER_UP: + LoadBalancer = FillErUpLoadBalancer + + load_balancer = self.addSubModel(LoadBalancer( + lock_capacities=lock_capacities, + priority=priority, + )) + + locks = [ self.addSubModel(Lock( + capacity=lock_capacity, + max_wait_duration=lock_max_wait, + passthrough_duration=passthrough_duration)) + for lock_capacity in lock_capacities ] + + sink = self.addSubModel(Sink()) + + # Don't forget to connect the input/output ports of the different sub-models: + # for instance: + # self.connectPorts(generator.out_ship, queue.in_ship) + # ... + + # Our runner.py script needs access to the 'sink'-state after completing the simulation: + self.sink = sink + + +### EDIT THIS FILE ### diff --git a/examples/queueing/experiment.py b/examples/queueing/experiment.py index 0d47421..0d8abbc 100644 --- a/examples/queueing/experiment.py +++ b/examples/queueing/experiment.py @@ -30,6 +30,7 @@ for i in range(1, max_processors): # PythonPDEVS specific setup and configuration sim = Simulator(m) sim.setClassicDEVS() + # sim.setVerbose() # <- uncomment to see what's going on sim.simulate() # Gather information for output From f5a09a2a437ad4df6ff893fbd0692c84433c6601 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 13 Dec 2024 12:47:51 +0100 Subject: [PATCH 02/10] forgot the queue --- assignment/system.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assignment/system.py b/assignment/system.py index 188ce96..f185371 100644 --- a/assignment/system.py +++ b/assignment/system.py @@ -31,6 +31,10 @@ class LockQueueingSystem(CoupledDEVS): gen_num=gen_num, )) + queue = self.addSubModel(Queue( + ship_sizes=set(gen_types), # the queue only needs to know the different ship sizes (and create a FIFO queue for each) + )) + if load_balancer_strategy == STRATEGY_ROUND_ROBIN: LoadBalancer = RoundRobinLoadBalancer elif load_balancer_strategy == STRATEGY_FILL_ER_UP: From 15925636d7d25859068af85367a48ac0b12568be Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 13 Dec 2024 13:57:04 +0100 Subject: [PATCH 03/10] rename parameter, move TIPS, add assignment HTML --- assignment/{ => doc}/TIPS.txt | 0 assignment/doc/assignment.html | 110 +++++++++++++++++++++++++++++++++ assignment/doc/concept.svg | 3 + assignment/doc/figures.drawio | 61 ++++++++++++++++++ assignment/runner.py | 20 +++--- assignment/system.py | 4 +- 6 files changed, 186 insertions(+), 12 deletions(-) rename assignment/{ => doc}/TIPS.txt (100%) create mode 100644 assignment/doc/assignment.html create mode 100644 assignment/doc/concept.svg create mode 100644 assignment/doc/figures.drawio diff --git a/assignment/TIPS.txt b/assignment/doc/TIPS.txt similarity index 100% rename from assignment/TIPS.txt rename to assignment/doc/TIPS.txt diff --git a/assignment/doc/assignment.html b/assignment/doc/assignment.html new file mode 100644 index 0000000..66132e2 --- /dev/null +++ b/assignment/doc/assignment.html @@ -0,0 +1,110 @@ + +DEVS Assignment + + +

Introduction

+

You will use (classic) DEVS to model a queueing and load balancing system for a set of waterway locks. A conceptual view of the system is shown here:

+ +

Ships move in the direction of the arrows. A generator generates ships at pseudo-random time intervals, which are added to a queue. Whenever the queue has a ship available, and one of the locks has enough remaining capacity for that ship, the load balancer pulls a ship from the queue and sends it to that lock. A lock may fit more than one ship, so as long as it is not filled up to full capacity, it may wait for more ships to arrive before the lock doors close and the ships can pass through to the other side of the lock. At the end of the system, we have a Sink, where all ships are collected, so we can extract statistics to analyse performance.

+ +

Ships can have different sizes. For simplicity, the size of a ship is a small integer (e.g., 1 or 2). Locks can have different capacities: for instance, a lock of capacity 3 will fit either: +

    +
  • 3 ships of size 1
  • +
  • 1 ship of size 2 + 1 ship of size 1
  • +
+

+ +

Specification

+ +

We now give an overview of the different DEVS components, and their behavior, and their parameters. Although many of the parameters are fixed, your solution must work with different parameters as well. In other words, don't hardcode the parameter values in your DEVS blocks!

+ +

Atomic DEVS blocks

+
    +
  • Generator +
      +
    • seed (int): Seed for the random number generator.
    • +
    • gen_num (int): The number of ships to generate during a simulation run. This parameter is fixed at 500.
    • +
    • gen_rate (float): The average ship generation rate (in ships/second). This parameter is fixed at 1/60/4 = once every 4 minutes.
    • +
    • gen_types (list of int): The different ship sizes to generate, and their likelihood. This parameter is fixed at [1,1,2], meaning, we have ship sizes 1 and 2, and size 1 is twice as likely to be generated as 2 (sizes are uniformly sampled from this sequence)
    • +
    +
  • +
  • Queue +
      +
    • ship_sizes (set of int): The different ship sizes. We will use {1,2}. The Queue will internally create one FIFO queue per ship size. This makes it possible to give bigger ships a higher priority over smaller ships, or vice versa.
    • +
    +
  • +
  • Load Balancer +
      +
    • lock_capacities (list of int): The load balancer needs to know about the number of locks and their capacities. We will use [3,2], meaning two locks, of capacities 3 and 2.
    • +
    • priority (enum): Whether to give higher priority to bigger or smaller ships.
    • +
    +

    Further, two different load balancer strategies will be implemented. Each strategy will be implemented as a separate Atomic DEVS block:

    +
      +
    • RoundRobinLoadBalancer: will iterate over the locks, and attempt to move a ship into each lock in a round-robin fashion. The priority-parameter is taken into account: if bigger ships have higher priority, then it will first try to move the biggest ship into the lock, then the next-biggest-ship, etc. +

      TIP: This load balancer will need to remember (in its DEVS state) the lock into which a ship was moved most recently, so for the next ship, it will start with the lock after it.

      +

      Initially, the RoundRobinLoadBalancer must start with the first lock in the lock_capacities-list.

      +
    • + +
    • FillErUpLoadBalancer: will prioritize, above all, a "move" that maximally fills up a lock. + +

      Example: if ships of sizes 1 and 2 are available, and the locks have remaining capacities 3 and 2, then the possible "moves" are: +

        +
      • move ship size 1 -> lock with cap 3 => remaining lock cap = 2
      • +
      • move ship size 1 -> lock with cap 2 => remaining lock cap = 1
      • +
      • move ship size 2 -> lock with cap 3 => remaining lock cap = 1
      • +
      • move ship size 2 -> lock with cap 2 => remaining lock cap = 0
      • +
      + this strategy will prioritize the last move, because it results in the smallest remaining capacity (in this case, completely filling up the lock).

      + +

      NOTE: This load balancer also has too take into account the priority-parameter. For instance, if bigger ships are prioritized, and the following moves can be made: +

        +
      • move ship size 1 -> lock with cap 2 => remaining lock cap = 1
      • +
      • move ship size 1 -> lock with cap 1 => remaining lock cap = 0
      • +
      • move ship size 2 -> lock with cap 2 => remaining lock cap = 0
      • +
      • move ship size 2 -> lock with cap 1 => remaining lock cap = 1
      • +
      + this time, both 1->1 and 2->2 completely fill up the lock, but the move 2->2 will be chosen, because it involves a bigger ship. +

      + +

      Finally, if the same ship can be moved to different locks with equal priority (filling both locks up equally), then the earliest lock in the lock_capacities-list is chosen.

      + +
    • +
    +
  • +
  • Lock +
      +
    • capacity (int): Capacity of the lock. E.g., 3 or 2.
    • +
    • passthrough_duration (float): Time duration (in seconds) of the "passthrough"-procedure. This procedure conceptually involves closing the lock doors, changing the water level, and opening the lock doors on the other side, and the ships leaving the lock. In our simulation, it is only an amount of time during which the lock has zero remaining capacity, after which the ships are sent to the sink. For simplicity, there is no time delay between sending the ships to the sink, and the lock becoming available again (at original capacity). +
    • max_wait_duration (float): When a lock is completely filled up (zero remaining capacity), the "passthrough"-procedure starts immediately. The procedure may also start if the lock is non-empty, and a certain amount of time has passed since the first ship has entered the lock. This parameter is that amount of time. +
    • +
    +
  • +
+ +The specification of the semantics of the Atomic DEVS blocks is entirely deterministic. If you implement everything correctly, the system as-a-whole will behave 100% identical to the teacher's solution. +

+ +

Coupled DEVS

+

The system as a whole is modeled as a Coupled DEVS block. Many of its parameters are passed as-is to the underlying Atomic DEVS blocks, such as: +

    +
  • gen_num (int) -> Generator.
  • +
  • gen_rate (float) -> Generator.
  • +
  • gen_types (list of int) -> Generator and Queue. The Queue only needs this parameter to know the different ship sizes.
  • +
  • lock_capacities (list of int) -> LoadBalancer and each Lock its respective capacity.
  • +
  • priority (enum) -> LoadBalancer.
  • +
  • max_wait_duration (float) -> each Lock (same value for all locks).
  • +
  • passthrough_duration (float) -> each Lock (same value for all locks).
  • +
+

+ +

Goal: Performance Analysis

+

We will do performance analysis, comparing combinations of the following parameter values:

+
    +
  • max_wait_duration (float): we will try 0, 2, 4, 6, 8 minutes.
  • +
  • priority (enum): we will try giving priority to bigger and smaller ships.
  • +
  • strategy (enum): we will try "round-robin" and "fill-er-up".
  • +
+

More specifically, we would like to know under which (combinations of) parameter values the (avg/min/max) duration that ships spend in the system is minimized. Also, we'd like to know if one choice (e.g., prioritize bigger) always better than another choice (e.g., prioritize smaller), or does it depend on the choices made for the other parameters?

+ + + \ No newline at end of file diff --git a/assignment/doc/concept.svg b/assignment/doc/concept.svg new file mode 100644 index 0000000..df2edf2 --- /dev/null +++ b/assignment/doc/concept.svg @@ -0,0 +1,3 @@ + + +
Generator
Queue
Load Balancer
Lock
Lock
Sink
\ No newline at end of file diff --git a/assignment/doc/figures.drawio b/assignment/doc/figures.drawio new file mode 100644 index 0000000..fd69eb1 --- /dev/null +++ b/assignment/doc/figures.drawio @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assignment/runner.py b/assignment/runner.py index fc4e524..0000641 100644 --- a/assignment/runner.py +++ b/assignment/runner.py @@ -31,9 +31,9 @@ strategies = { # The number of locks and their capacities lock_capacities=[3,2] # two locks, of capacity 3 and 2 -# The different parameters to try for lock_max_wait -lock_max_waits = [ 0.0+i*120.0 for i in range(5) ] # all these values will be attempted -# lock_max_waits = [ 15.0 ] # <-- uncomment if you only want to run an experiment with this value (useful for debugging) +# The different parameters to try for max_wait_duration +max_wait_durations = [ 0.0+i*120.0 for i in range(5) ] # all these values will be attempted +# max_wait_durations = [ 15.0 ] # <-- uncomment if you only want to run an experiment with this value (useful for debugging) # How long does it take for a ship to pass through a lock passthrough_duration = 60.0*15 # 15 minutes @@ -49,9 +49,9 @@ os.makedirs(outdir, exist_ok=True) for priority in priorities: for strategy in strategies: values = [] - # and in each experiment, try a bunch of different values for the 'lock_max_wait' parameter: - for lock_max_wait in lock_max_waits: - print("Run simulation:", priorities[priority], strategies[strategy], "max_wait =",lock_max_wait) + # and in each experiment, try a bunch of different values for the 'max_wait_duration' parameter: + for max_wait_duration in max_wait_durations: + print("Run simulation:", priorities[priority], strategies[strategy], "max_wait =",max_wait_duration) sys = LockQueueingSystem( # See system.py for explanation of these values: seed=0, @@ -61,7 +61,7 @@ for priority in priorities: load_balancer_strategy=strategy, lock_capacities=lock_capacities, priority=priority, - lock_max_wait=lock_max_wait, + max_wait_duration=max_wait_duration, passthrough_duration=passthrough_duration, ) sim = Simulator(sys) @@ -73,7 +73,7 @@ for priority in priorities: ships = sys.sink.state.ships values.append([ship.queueing_duration for ship in ships]) - # Write out all the ship queueuing durations for every 'lock_max_wait' parameter + # Write out all the ship queueuing durations for every 'max_wait_duration' parameter # for every ship, we write a line: # , time_max_wait0, time_max_wait1, time_max_wait2, ... time_max_wait10 filename = f'{outdir}/output_{strategies[strategy]}_{priorities[priority]}.csv' @@ -91,13 +91,13 @@ for priority in priorities: plots_ships.append(make_plot_ships_script( priority=priorities[priority], strategy=strategies[strategy], - max_waits=lock_max_waits, + max_waits=max_wait_durations, gen_num=gen_num, )) plots_box.append(make_plot_box_script( priority=priorities[priority], strategy=strategies[strategy], - max_waits=lock_max_waits, + max_waits=max_wait_durations, gen_num=gen_num, )) diff --git a/assignment/system.py b/assignment/system.py index f185371..f55d645 100644 --- a/assignment/system.py +++ b/assignment/system.py @@ -17,7 +17,7 @@ class LockQueueingSystem(CoupledDEVS): load_balancer_strategy, lock_capacities, priority, - lock_max_wait, + max_wait_duration, passthrough_duration, ): super().__init__("LockQueueingSystem") @@ -47,7 +47,7 @@ class LockQueueingSystem(CoupledDEVS): locks = [ self.addSubModel(Lock( capacity=lock_capacity, - max_wait_duration=lock_max_wait, + max_wait_duration=max_wait_duration, passthrough_duration=passthrough_duration)) for lock_capacity in lock_capacities ] From 321cb4871e6d64988fb4c0dba1a6f165b93efcdb Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 13 Dec 2024 14:28:07 +0100 Subject: [PATCH 04/10] finish assignment --- assignment/doc/assignment.html | 52 ++++++++++++++++++++++++++++++++-- assignment/environment.py | 2 ++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/assignment/doc/assignment.html b/assignment/doc/assignment.html index 66132e2..fe49409 100644 --- a/assignment/doc/assignment.html +++ b/assignment/doc/assignment.html @@ -97,8 +97,25 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi

-

Goal: Performance Analysis

-

We will do performance analysis, comparing combinations of the following parameter values:

+

What is expected

+

First of all, you are given an implementation of the following AtomicDEVS blocks, which you must not edit:

+
    +
  • Generator +
    • out_ship (Ship): output port on which an event is sent when a ship is generated.
    +
  • +
  • Sink +
    • in_ships (list of Ship): input port on which an event is sent when ships leave a lock. For each ship, the time duration spent in the system is computed and stored.
    +
  • +
+

You will:

+
    +
  • Implement the AtomicDEVS blocks for Queue, RoundRobinLoadBalancer, FillErUpLoadBalancer and Lock.
  • +
  • Think of the interfaces of these blocks (input/output ports and their events) and the protocol spoken by them.
  • +
  • Finally, in the CoupledDEVS block representing the entire system, you will have to make the right connections (which of course depends on the input/output ports that you have defined in your AtomicDEVS).
  • +
+ +

Goal: Performance Analysis

+

Once you have implemented the system, we will do performance analysis, comparing combinations of the following parameter values:

  • max_wait_duration (float): we will try 0, 2, 4, 6, 8 minutes.
  • priority (enum): we will try giving priority to bigger and smaller ships.
  • @@ -106,5 +123,36 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi

More specifically, we would like to know under which (combinations of) parameter values the (avg/min/max) duration that ships spend in the system is minimized. Also, we'd like to know if one choice (e.g., prioritize bigger) always better than another choice (e.g., prioritize smaller), or does it depend on the choices made for the other parameters?

+

Getting Started

+
    +
  1. Clone the mosis24 branch of this git repository.
  2. +
  3. Under the assignment directory, you'll find the following files: +
      +
    • runner.py This script runs the simulation for all combinations of parameter values as described in the Performance Analysis section. It will generate .csv files with the time durations that each ship has spent in the system. Every row is a ship (500 ships, so 500 rows total), and every column represents a different value for the max_wait_duration parameter. It will also generate a plot.gnuplot file, which you can run with gnuplot as follows: +
      gnuplot plot.gnuplot
      + which will result in a number of SVG files containing plots of the CSV files. + +

      You are only allowed to make temporary changes (for debugging) to this file.

      +
    • +
    • system.py Contains the full system, modeled as CoupledDEVS. You need to edit this file.
    • +
    • atomicdevs.py Contains skeletons for the AtomicDEVS blocks that you must implement. You need to edit this file.
    • +
    • environment.py Contains implementations of the Generator, Sink and Ship types. You must not edit this file.
    • +
    +
  4. +
+ +

Attention!

+

You must stick to the rules of DEVS:

+
    +
  • The functions timeAdvance and outputFnc are purely getters! They must not mutate the DEVS state, or any variable, anywhere.
  • +
  • The functions extTransition and intTransition may mutate ONLY the DEVS state.
  • +
+

Any violation of these rules results in an incorrect solution. Points will be subtracted.

+ +

Extra Material

+
    +
  • This assignment was inspired by the queueuing example, which you can in examples/queueing.
  • +
+ \ No newline at end of file diff --git a/assignment/environment.py b/assignment/environment.py index e5fb4fc..27617d2 100644 --- a/assignment/environment.py +++ b/assignment/environment.py @@ -80,6 +80,8 @@ class Sink(AtomicDEVS): def __init__(self): super().__init__("Sink") self.state = SinkState() + + # On this input port, the Sink expects to receive a *list* of Ships. This is because a Lock can contain more than one Ship, and the Lock can send them all at once to the Sink (with a single event). self.in_ships = self.addInPort("in_ships") def extTransition(self, inputs): From c65ea0075effabf8de4c9e35e33c273f0fc995d9 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 13 Dec 2024 14:58:40 +0100 Subject: [PATCH 05/10] update assignment --- assignment/doc/TIPS.txt | 24 -------------- assignment/doc/assignment.html | 57 +++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 28 deletions(-) delete mode 100644 assignment/doc/TIPS.txt diff --git a/assignment/doc/TIPS.txt b/assignment/doc/TIPS.txt deleted file mode 100644 index d16e344..0000000 --- a/assignment/doc/TIPS.txt +++ /dev/null @@ -1,24 +0,0 @@ -CODING CONVENTIONS - -1. write your methods always in the following order: - - extTransition - timeAdvance - outputFnc - intTransition - - this reflects the order in which the methods are called by the simulator: - - extTransition always has highest priority (can interrupt anything) - timeAdvance is called before outputFnc - outputFnc is called right before intTransition - - -2. input/output port attributes start with 'in_' and 'out_' - - -TROUBLESHOOTING - - - did you forget to return `self.state` from intTransition or extTransition ? - - did you accidentally write to `self.x` instead of `self.state.x` ? - - did you modify the state in timeAdvance or outputFnc (NOT ALLOWED!!) diff --git a/assignment/doc/assignment.html b/assignment/doc/assignment.html index fe49409..6fd0bf3 100644 --- a/assignment/doc/assignment.html +++ b/assignment/doc/assignment.html @@ -1,3 +1,5 @@ + + DEVS Assignment @@ -85,7 +87,7 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi

Coupled DEVS

-

The system as a whole is modeled as a Coupled DEVS block. Many of its parameters are passed as-is to the underlying Atomic DEVS blocks, such as: +

The system as a whole is modeled as a Coupled DEVS block. Its parameters are mostly passed as-is to the underlying Atomic DEVS blocks. They are:

  • gen_num (int) -> Generator.
  • gen_rate (float) -> Generator.
  • @@ -139,20 +141,67 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi
  • environment.py Contains implementations of the Generator, Sink and Ship types. You must not edit this file.
+
  • Write a report, where you: +
      +
    • explain and motivate the interfaces/protocol of the AtomicDEVS blocks
    • +
    • show the plotted results
    • +
    • interpret the plotted results
    • +
    +
  • +
  • Submit via BlackBoard, a ZIP file, containing: +
      +
    • Your code (only the assignment directory)
    • +
    • Your report (PDF)
    • +
    • The generated CSV- and SVG-files
    • +
    +
  • -

    Attention!

    -

    You must stick to the rules of DEVS:

    +

    Attention!

    +

    You must stick to the rules of DEVS:

    • The functions timeAdvance and outputFnc are purely getters! They must not mutate the DEVS state, or any variable, anywhere.
    • The functions extTransition and intTransition may mutate ONLY the DEVS state.

    Any violation of these rules results in an incorrect solution. Points will be subtracted.

    +

    Coding Conventions

    + +

    Please follow these coding conventions:

    +
      +
    • Write your AtomicDEVS methods always in the following order: +
        +
      • extTransition
      • +
      • timeAdvance
      • +
      • outputFnc
      • +
      • intTransition
      • +
      + +

      This reflects the order in which the methods are called by the simulator:

      +
        +
      • extTransition always has highest priority (can interrupt anything)
      • +
      • timeAdvance is called before outputFnc
      • +
      • outputFnc is called right before intTransition
      • +
      +
    • +
    • Input/output port attributes start with 'in_' and 'out_'
    • +
    + +

    Troubleshooting

    + +

    Common mistakes include: +

      +
    • did you forget to return self.state from intTransition or extTransition ?
    • +
    • did you accidentally write to self.x instead of self.state.x ?
    • +
    • did you modify the state in timeAdvance or outputFnc ? (NOT ALLOWED!!)
    • +
    +

    Extra Material

    • This assignment was inspired by the queueuing example, which you can in examples/queueing.
    - \ No newline at end of file + + + \ No newline at end of file From 94b3b47e8739119eba9ce3bc25cf1fbfd2fcb4f7 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 13 Dec 2024 14:59:03 +0100 Subject: [PATCH 06/10] no need to import random, or seed anymore --- examples/queueing/experiment.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/queueing/experiment.py b/examples/queueing/experiment.py index 0d8abbc..71e0f5d 100644 --- a/examples/queueing/experiment.py +++ b/examples/queueing/experiment.py @@ -1,5 +1,4 @@ from pypdevs.simulator import Simulator -import random # Import the model we experiment with from system import QueueSystem @@ -22,7 +21,6 @@ values = [] # Loop over different configurations for i in range(1, max_processors): # Make sure each of them simulates exactly the same workload - random.seed(1) # Set up the system procs = [speed] * i m = QueueSystem(mu=1.0/time, size=size, num=num, procs=procs) @@ -30,7 +28,7 @@ for i in range(1, max_processors): # PythonPDEVS specific setup and configuration sim = Simulator(m) sim.setClassicDEVS() - # sim.setVerbose() # <- uncomment to see what's going on + sim.setVerbose() # <- uncomment to see what's going on sim.simulate() # Gather information for output From 7230fcf584727ac2fb005a5d9716d002f5ba4c5b Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 13 Dec 2024 16:57:06 +0100 Subject: [PATCH 07/10] update HTML + changes made to queueing example during tutorial --- assignment/doc/assignment.html | 3 ++- examples/queueing/generator.py | 27 ++++++++++++++++++--------- examples/queueing/job.py | 3 +++ examples/queueing/queue.py | 28 ++++++++++++++-------------- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/assignment/doc/assignment.html b/assignment/doc/assignment.html index 6fd0bf3..e1e9ac0 100644 --- a/assignment/doc/assignment.html +++ b/assignment/doc/assignment.html @@ -155,6 +155,7 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi
  • The generated CSV- and SVG-files
  • +
  • Deadline: Sunday 5 December 2025, 23:59
  • Attention!

    @@ -198,7 +199,7 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi

    Extra Material

      -
    • This assignment was inspired by the queueuing example, which you can in examples/queueing.
    • +
    • This assignment was inspired by the queueuing example.
    diff --git a/examples/queueing/generator.py b/examples/queueing/generator.py index 57423eb..05b1587 100644 --- a/examples/queueing/generator.py +++ b/examples/queueing/generator.py @@ -1,9 +1,17 @@ from pypdevs.DEVS import AtomicDEVS from job import Job import random +import dataclasses # Define the state of the generator as a structured object +@dataclasses.dataclass class GeneratorState: + current_time: float + remaining: float + to_generate: int + next_job: None + random: random.Random + def __init__(self, gen_num, seed=0): # Current simulation time (statistics) self.current_time = 0.0 @@ -34,14 +42,22 @@ class Generator(AtomicDEVS): # Determine size of the event to generate size = max(1, int(self.state.random.gauss(self.size_param, 5))) # Calculate current time (note the addition!) - creation = self.state.current_time + self.state.remaining + creation = self.state.current_time # Update state self.state.next_job = Job(size, creation) self.state.remaining = self.state.random.expovariate(self.gen_param) + def timeAdvance(self): + # Return remaining time; infinity when generated enough + return self.state.remaining + + def outputFnc(self): + # Output the new event on the output port + return {self.out_event: self.state.next_job} + def intTransition(self): # Update simulation time - self.state.current_time += self.timeAdvance() + self.state.current_time += self.state.remaining # Update number of generated events self.state.to_generate -= 1 if self.state.to_generate == 0: @@ -53,10 +69,3 @@ class Generator(AtomicDEVS): self.__nextJob() return self.state - def timeAdvance(self): - # Return remaining time; infinity when generated enough - return self.state.remaining - - def outputFnc(self): - # Output the new event on the output port - return {self.out_event: self.state.next_job} diff --git a/examples/queueing/job.py b/examples/queueing/job.py index 54a7314..d8ade30 100644 --- a/examples/queueing/job.py +++ b/examples/queueing/job.py @@ -3,3 +3,6 @@ class Job: # Jobs have a size and creation_time parameter self.size = size self.creation_time = creation_time + + def __repr__(self): + return f"Job(size={self.size},creation_time={self.creation_time})" \ No newline at end of file diff --git a/examples/queueing/queue.py b/examples/queueing/queue.py index 5b11cab..8fbe89c 100644 --- a/examples/queueing/queue.py +++ b/examples/queueing/queue.py @@ -29,20 +29,6 @@ class Queue(AtomicDEVS): self.in_event = self.addInPort("in_event") self.in_finish = self.addInPort("in_finish") - def intTransition(self): - # Is only called when we are outputting an event - # Pop the first idle processor and clear processing event - self.state.idle_procs.pop(0) - if self.state.queue and self.state.idle_procs: - # There are still queued elements, so continue - self.state.processing = self.state.queue.pop(0) - self.state.remaining_time = self.processing_time - else: - # No events left to process, so become idle - self.state.processing = None - self.state.remaining_time = float("inf") - return self.state - def extTransition(self, inputs): # Update the remaining time of this job self.state.remaining_time -= self.elapsed @@ -73,3 +59,17 @@ class Queue(AtomicDEVS): # Output the event to the processor port = self.out_proc[self.state.idle_procs[0]] return {port: self.state.processing} + + def intTransition(self): + # Is only called when we are outputting an event + # Pop the first idle processor and clear processing event + self.state.idle_procs.pop(0) + if self.state.queue and self.state.idle_procs: + # There are still queued elements, so continue + self.state.processing = self.state.queue.pop(0) + self.state.remaining_time = self.processing_time + else: + # No events left to process, so become idle + self.state.processing = None + self.state.remaining_time = float("inf") + return self.state From dfbd166fe38bfdcc013472506f9fe5f073fb4794 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Sun, 15 Dec 2024 12:57:23 +0100 Subject: [PATCH 08/10] add 'practical stuff' to assignment --- assignment/doc/assignment.html | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/assignment/doc/assignment.html b/assignment/doc/assignment.html index e1e9ac0..5beaded 100644 --- a/assignment/doc/assignment.html +++ b/assignment/doc/assignment.html @@ -4,6 +4,31 @@ DEVS Assignment +

    Practical stuff

    +
      +
    • Due Date: Sunday 5 January 2025, before 23:59 (Blackboard's clock).
    • +
    • Team Size: 2 (pair design/programming)!
      + Note that as of the 2017-2018 Academic Year, each International student should team up with "local" + (i.e., whose Bachelor degree was obtained at the University of Antwerp).
    • +
    • Submitting your solution: +
        +
      • + Only one member of each team submits a full solution (ZIP file). +
      • +
      • + The other team member must submit a single (plain text or HTML) file containing only the names of both team members. This will allow us to put in grades for both team members in BlackBoard. +
      • +
      +
    • +
    • Submission Medium: + BlackBoard. +
    • + +
    • Contact / TA: + Joeri Exelmans. +
    • +
    +

    Introduction

    You will use (classic) DEVS to model a queueing and load balancing system for a set of waterway locks. A conceptual view of the system is shown here:

    @@ -125,7 +150,7 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi

    More specifically, we would like to know under which (combinations of) parameter values the (avg/min/max) duration that ships spend in the system is minimized. Also, we'd like to know if one choice (e.g., prioritize bigger) always better than another choice (e.g., prioritize smaller), or does it depend on the choices made for the other parameters?

    -

    Getting Started

    +

    Getting Started

    1. Clone the mosis24 branch of this git repository.
    2. Under the assignment directory, you'll find the following files: @@ -148,7 +173,7 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi
    3. interpret the plotted results
    4. -
    5. Submit via BlackBoard, a ZIP file, containing: +
    6. Submit via BlackBoard, a ZIP file, containing:
      • Your code (only the assignment directory)
      • Your report (PDF)
      • From 87dfce723b8e916a41e3173f26c1897dbeb7889e Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Sun, 15 Dec 2024 13:13:15 +0100 Subject: [PATCH 09/10] add TIPS + fix HTML --- assignment/doc/assignment.html | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/assignment/doc/assignment.html b/assignment/doc/assignment.html index 5beaded..1aa013d 100644 --- a/assignment/doc/assignment.html +++ b/assignment/doc/assignment.html @@ -1,9 +1,5 @@ - -DEVS Assignment - -

        Practical stuff

        • Due Date: Sunday 5 January 2025, before 23:59 (Blackboard's clock).
        • @@ -101,15 +97,15 @@
        • Lock
          • capacity (int): Capacity of the lock. E.g., 3 or 2.
          • -
          • passthrough_duration (float): Time duration (in seconds) of the "passthrough"-procedure. This procedure conceptually involves closing the lock doors, changing the water level, and opening the lock doors on the other side, and the ships leaving the lock. In our simulation, it is only an amount of time during which the lock has zero remaining capacity, after which the ships are sent to the sink. For simplicity, there is no time delay between sending the ships to the sink, and the lock becoming available again (at original capacity). +
          • passthrough_duration (float): Time duration (in seconds) of the "passthrough"-procedure. This procedure conceptually involves closing the lock doors, changing the water level, and opening the lock doors on the other side, and the ships leaving the lock. In our simulation, it is only an amount of time during which the lock has zero remaining capacity, after which the ships are sent to the sink. +

            For simplicity, there is no time delay between sending the ships to the sink, and the lock becoming available again (at original capacity).

          • max_wait_duration (float): When a lock is completely filled up (zero remaining capacity), the "passthrough"-procedure starts immediately. The procedure may also start if the lock is non-empty, and a certain amount of time has passed since the first ship has entered the lock. This parameter is that amount of time.
        -The specification of the semantics of the Atomic DEVS blocks is entirely deterministic. If you implement everything correctly, the system as-a-whole will behave 100% identical to the teacher's solution. -

        +

        The specification of the semantics of the Atomic DEVS blocks is entirely deterministic. If you implement everything correctly, the system as-a-whole will behave 100% identical to the teacher's solution.

        Coupled DEVS

        The system as a whole is modeled as a Coupled DEVS block. Its parameters are mostly passed as-is to the underlying Atomic DEVS blocks. They are: @@ -140,6 +136,7 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi

      • Think of the interfaces of these blocks (input/output ports and their events) and the protocol spoken by them.
      • Finally, in the CoupledDEVS block representing the entire system, you will have to make the right connections (which of course depends on the input/output ports that you have defined in your AtomicDEVS).
      +

      An indication of the complexity: my own solution of the AtomicDEVS blocks is about 300 lines of code (including comments).

      Goal: Performance Analysis

      Once you have implemented the system, we will do performance analysis, comparing combinations of the following parameter values:

      @@ -203,7 +200,7 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi
    7. intTransition
    8. -

      This reflects the order in which the methods are called by the simulator:

      + This reflects the order in which the methods are called by the simulator:
      • extTransition always has highest priority (can interrupt anything)
      • timeAdvance is called before outputFnc
      • @@ -213,21 +210,25 @@ The specification of the semantics of the Atomic DEVS blocks is entirely determi
      • Input/output port attributes start with 'in_' and 'out_'
      +

      Tips

      +
        +
      • Test/debug your integrated solution with only one ship (set num_ships parameter to 1).
      • +
      • To observe the changing State in the PyPDVES debug output, write __repr__-methods for your State-classes.
      • +
      +

      Troubleshooting

      Common mistakes include: -

        -
      • did you forget to return self.state from intTransition or extTransition ?
      • -
      • did you accidentally write to self.x instead of self.state.x ?
      • -
      • did you modify the state in timeAdvance or outputFnc ? (NOT ALLOWED!!)
      • -
      +
        +
      • did you forget to return self.state from intTransition or extTransition ?
      • +
      • did you accidentally write to self.x instead of self.state.x ?
      • +
      • did you modify the state in timeAdvance or outputFnc ? (NOT ALLOWED!!)
      • +
      +

      Extra Material

      - - - \ No newline at end of file From 8d5b9e2c2dbc1fc56dfc50bb3711931411bc7bcf Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Mon, 16 Dec 2024 16:34:53 +0100 Subject: [PATCH 10/10] add frequency plot + fix error in one of the plots + make output bigger --- assignment/plot_template.py | 38 +++++++++++++++++++++++++++++++++---- assignment/runner.py | 23 ++++++++++------------ 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/assignment/plot_template.py b/assignment/plot_template.py index fced26e..bf1b595 100644 --- a/assignment/plot_template.py +++ b/assignment/plot_template.py @@ -2,12 +2,12 @@ def make_plot_ships_script(priority:str, strategy:str, max_waits:list[float], ge return (f""" ### priority={priority}, strategy={strategy} ### -set terminal svg +set terminal svg size 1200 900 # plot 1. x-axis: ships, y-axis: queuing duration of ship set out 'plot_ships_{strategy}_{priority}.svg' -set title "Queueing duration" +set title "Queueing duration (strategy={strategy}, priority={priority})" set xlabel "Ship #" set ylabel "Seconds" #unset xlabel @@ -28,7 +28,7 @@ set xrange [0:{gen_num}] set style fill solid plot 'output_{strategy}_{priority}.csv' \\\n """ + ", \\\n '' ".join([ - f"using 1:{i+1} title '{max_wait}' w boxes ls {i+1}" + f"using 1:{i+2} title '{max_wait}' w boxes ls {i+1}" for i, max_wait in enumerate(max_waits) ])) @@ -38,6 +38,7 @@ def make_plot_box_script(priority:str, strategy:str, max_waits:list[float], gen_ # plot 2. x-axis: max-wait parameter, y-axis: queueing durations of ships set out 'plot_box_{strategy}_{priority}.svg' +set title "Queueing duration (strategy={strategy}, priority={priority})" set style fill solid 0.25 border -1 set style boxplot outliers pointtype 7 set style data boxplot @@ -53,4 +54,33 @@ set xtics (""" + ', '.join([ f"'{max_wait}' {i}" plot 'output_{strategy}_{priority}.csv' \\\n """ + ", \\\n '' ".join([ f"using ({i}):{i+2} title '{max_wait}'" for i, max_wait in enumerate(max_waits) -])) \ No newline at end of file +])) + +def make_plot_frequency_script(priority:str, strategy:str, max_waits:list[float], gen_num:int): + return (f""" + +# plot 3. x-axis: queueing duration interval, y-axis: number of ships + +bin_width = 5*60; + +set out 'plot_freq_{strategy}_{priority}.svg' +set title "Frequency of queueing durations (strategy={strategy}, priority={priority})" +set boxwidth (bin_width) absolute +set style fill solid 1.0 noborder + +set key title "Max Wait" +set key bottom center out +# set key horizontal + +set xtics auto +set xrange [0:] +set xlabel "Queueing duration (interval)" +set ylabel "Number of ships" + +bin_number(x) = floor(x/bin_width) +rounded(x) = bin_width * ( bin_number(x) + 0.5 ) + +plot 'output_{strategy}_{priority}.csv' \\\n """ + ", \\\n '' ".join([ + f"using (rounded(${i+2})):(1) title '{max_wait}' smooth frequency with boxes" + for i, max_wait in list(enumerate(max_waits)) +])) diff --git a/assignment/runner.py b/assignment/runner.py index 0000641..6b840a6 100644 --- a/assignment/runner.py +++ b/assignment/runner.py @@ -1,7 +1,7 @@ import os from pypdevs.simulator import Simulator -from plot_template import make_plot_ships_script, make_plot_box_script +from plot_template import make_plot_ships_script, make_plot_box_script, make_plot_frequency_script # from system_solution import * # Teacher's solution from system import * @@ -42,6 +42,7 @@ outdir = "assignment_output" plots_ships = [] plots_box = [] +plots_freq = [] os.makedirs(outdir, exist_ok=True) @@ -88,18 +89,13 @@ for priority in priorities: raise Exception("There was an IndexError, meaning that fewer ships have made it to the sink than expected.\nYour model is not (yet) correct.") from e # Generate gnuplot code: - plots_ships.append(make_plot_ships_script( - priority=priorities[priority], - strategy=strategies[strategy], - max_waits=max_wait_durations, - gen_num=gen_num, - )) - plots_box.append(make_plot_box_script( - priority=priorities[priority], - strategy=strategies[strategy], - max_waits=max_wait_durations, - gen_num=gen_num, - )) + for f, col in [(make_plot_ships_script, plots_ships), (make_plot_box_script, plots_box), (make_plot_frequency_script, plots_freq)]: + col.append(f( + priority=priorities[priority], + strategy=strategies[strategy], + max_waits=max_wait_durations, + gen_num=gen_num, + )) # Finally, write out a single gnuplot script that plots everything with open(f'{outdir}/plot.gnuplot', 'w') as f: @@ -107,3 +103,4 @@ with open(f'{outdir}/plot.gnuplot', 'w') as f: f.write('\n\n'.join(plots_ships)) # then do the box plots f.write('\n\n'.join(plots_box)) + f.write('\n\n'.join(plots_freq))