From 0d541f7737eb0cac3b7076afd6e673ce8b4cb5bc Mon Sep 17 00:00:00 2001 From: Berack96 Date: Fri, 17 Jan 2025 12:29:47 +0100 Subject: [PATCH] Rewrite - rewritten all the sim code in a way to make easier to read and collect stats --- .../java/net/berack/upo/valpre/Event.java | 34 +++++ src/main/java/net/berack/upo/valpre/Main.java | 29 ++++- .../net/berack/upo/valpre/NetSimulation.java | 123 ++++++++++++++++++ .../net/berack/upo/valpre/ServerNode.java | 58 +++++++++ .../berack/upo/valpre/rand/Distribution.java | 59 +++++++++ 5 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/berack/upo/valpre/Event.java create mode 100644 src/main/java/net/berack/upo/valpre/NetSimulation.java create mode 100644 src/main/java/net/berack/upo/valpre/ServerNode.java create mode 100644 src/main/java/net/berack/upo/valpre/rand/Distribution.java diff --git a/src/main/java/net/berack/upo/valpre/Event.java b/src/main/java/net/berack/upo/valpre/Event.java new file mode 100644 index 0000000..316983a --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/Event.java @@ -0,0 +1,34 @@ +package net.berack.upo.valpre; + +public class Event implements Comparable { + public final double time; + public final Type type; + public final ServerNode node; + + private Event(Type type, ServerNode node, double time) { + this.type = type; + this.time = time; + this.node = node; + } + + public int compareTo(Event other) { + if (this.time < other.time) + return -1; + if (this.time == other.time) + return 0; + return 1; + } + + public static Event newArrival(ServerNode node, double time) { + return new Event(Type.ARRIVAL, node, time); + } + + public static Event newDeparture(ServerNode node, double time) { + return new Event(Type.DEPARTURE, node, time); + } + + public static enum Type { + ARRIVAL, + DEPARTURE, + } +} diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index aca9461..e012ad4 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -1,7 +1,34 @@ package net.berack.upo.valpre; +import net.berack.upo.valpre.rand.Distribution; + public class Main { public static void main(String[] args) { - System.out.println("Hello world!"); + // Build the network + var node1 = ServerNode.createSource("Source", new Distribution.Exponential(1.0 / 4.5)); + var node2 = ServerNode.createQueue("Queue", 1, new Distribution.NormalBoxMuller(3.2, 0.6)); + node1.addChild(node2, 1.0); + + /// Run the simulation + var sim = new NetSimulation(System.nanoTime()); + sim.addNode(node1); + sim.addNode(node2); + var results = sim.run(1000, "Queue"); + + // Display the results + for (var entry : results.entrySet()) { + var stats = entry.getValue(); + var size = (int) Math.ceil(Math.max(Math.log10(stats.numArrivals), Math.log10(stats.lastEventTime))); + var iFormat = "%" + size + "d"; + var fFormat = "%" + (size + 4) + ".3f"; + + System.out.println("===== " + entry.getKey() + " ====="); + System.out.printf(" Arrivals: \t" + iFormat + "\n", stats.numArrivals); + System.out.printf(" Departures:\t" + iFormat + "\n", stats.numDepartures); + System.out.printf(" Max Queue: \t" + iFormat + "\n", stats.maxQueueLength); + System.out.printf(" Response: \t" + fFormat + "\n", stats.responseTime / stats.numDepartures); + System.out.printf(" Busy %%: \t" + fFormat + "\n", stats.busyTime * 100 / stats.lastEventTime); + System.out.printf(" Last Event:\t" + fFormat + "\n", stats.lastEventTime); + } } } \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/NetSimulation.java b/src/main/java/net/berack/upo/valpre/NetSimulation.java new file mode 100644 index 0000000..8c4b348 --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/NetSimulation.java @@ -0,0 +1,123 @@ +package net.berack.upo.valpre; + +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; +import net.berack.upo.valpre.rand.Rng; + +public class NetSimulation { + public final long seed; + private final Rng rng; + private final Map servers = new HashMap<>(); + + public NetSimulation(long seed) { + this.seed = seed; + this.rng = new Rng(seed); + } + + public void addNode(ServerNode node) { + this.servers.put(node.name, node); + } + + public Map run(long total, String untilDepartureNode) { + // Initialization + var timeNow = 0.0d; + var stats = new HashMap(); + var fel = new PriorityQueue(); + for (var node : this.servers.values()) { + var s = new Statistics(this.rng); + s.addArrivalIf(node.isSource, node, timeNow, fel); + stats.put(node.name, s); + } + + // Main Simulation Loop + var nodeStop = stats.get(untilDepartureNode); + while (nodeStop.numDepartures < total) { + var event = fel.poll(); + if (event == null) { + break; + } + + timeNow = event.time; + var statsNode = stats.get(event.node.name); + switch (event.type) { + case ARRIVAL -> statsNode.processArrival(event, timeNow, fel); + case DEPARTURE -> statsNode.processDeparture(event, timeNow, fel); + } + } + return stats; + } + + public static class Statistics { + public int numArrivals = 0; + public int numDepartures = 0; + public int maxQueueLength = 0; + public double busyTime = 0.0; + public double responseTime = 0.0; + public double lastEventTime = 0.0; + + private int numServerBusy = 0; + private ArrayDeque queue = new ArrayDeque<>(); + private final Rng rng; + + public Statistics(Rng rng) { + this.rng = rng; + } + + public void reset() { + this.numArrivals = 0; + this.numDepartures = 0; + this.numServerBusy = 0; + this.busyTime = 0.0; + this.responseTime = 0.0; + this.queue.clear(); + } + + private void processArrival(Event event, double timeNow, PriorityQueue fel) { + this.numArrivals++; + this.queue.add(event.time); + this.maxQueueLength = Math.max(this.maxQueueLength, this.queue.size()); + + if (event.node.maxServers > this.numServerBusy) { + this.numServerBusy++; + var time = event.node.distribution.sample(this.rng); + var departure = Event.newDeparture(event.node, timeNow + time); + fel.add(departure); + } else { + this.busyTime += timeNow - this.lastEventTime; + } + this.lastEventTime = timeNow; + + this.addArrivalIf(event.node.isSource, event.node, timeNow, fel); + } + + private void processDeparture(Event event, double timeNow, PriorityQueue fel) { + var startService = this.queue.poll(); + var response = timeNow - startService; + + if (this.queue.size() < this.numServerBusy) { + this.numServerBusy--; + } else { + var time = event.node.distribution.sample(this.rng); + var departure = Event.newDeparture(event.node, timeNow + time); + fel.add(departure); + } + + this.numDepartures++; + this.responseTime += response; + this.busyTime += timeNow - this.lastEventTime; + this.lastEventTime = timeNow; + + var next = event.node.getChild(rng); + this.addArrivalIf(!event.node.isSink, next, timeNow, fel); + } + + private void addArrivalIf(boolean condition, ServerNode node, double timeNow, PriorityQueue fel) { + if (condition && node != null) { + var delay = node.distribution.sample(this.rng); + fel.add(Event.newArrival(node, timeNow + delay)); + } + } + } +} diff --git a/src/main/java/net/berack/upo/valpre/ServerNode.java b/src/main/java/net/berack/upo/valpre/ServerNode.java new file mode 100644 index 0000000..241ee7c --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/ServerNode.java @@ -0,0 +1,58 @@ +package net.berack.upo.valpre; + +import java.util.ArrayList; +import java.util.List; +import net.berack.upo.valpre.rand.Distribution; +import net.berack.upo.valpre.rand.Rng; + +public class ServerNode { + public final String name; + public final int maxServers; + public final Distribution distribution; + public final boolean isSink; + public final boolean isSource; + private final List children = new ArrayList<>(); + private double sumProbabilities = 0.0; + + public static ServerNode createSource(String name, Distribution distribution) { + return new ServerNode(name, Integer.MAX_VALUE, distribution, false, true); + } + + public static ServerNode createQueue(String name, int maxServers, Distribution distribution) { + return new ServerNode(name, maxServers, distribution, false, false); + } + + public ServerNode(String name, int maxServers, Distribution distribution, boolean isSink, boolean isSource) { + this.name = name; + this.maxServers = maxServers; + this.distribution = distribution; + this.isSink = isSink; + this.isSource = isSource; + } + + public void addChild(ServerNode node, double probability) { + this.children.add(new NodeChild(node, probability)); + this.sumProbabilities += probability; + } + + public ServerNode getChild(Rng rng) { + var random = rng.random(); + for (var child : this.children) { + random -= child.probability / this.sumProbabilities; + if (random <= 0) { + return child.node; + } + } + return null; + } + + public static class NodeChild { + public final ServerNode node; + public final double probability; + + public NodeChild(ServerNode node, double probability) { + this.node = node; + this.probability = probability; + } + } +} diff --git a/src/main/java/net/berack/upo/valpre/rand/Distribution.java b/src/main/java/net/berack/upo/valpre/rand/Distribution.java new file mode 100644 index 0000000..9f6801a --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/rand/Distribution.java @@ -0,0 +1,59 @@ +package net.berack.upo.valpre.rand; + +public interface Distribution { + public double sample(Rng rng); + + public static class Exponential implements Distribution { + private final double lambda; + + public Exponential(double lambda) { + this.lambda = lambda; + } + + @Override + public double sample(Rng rng) { + return -Math.log(rng.random()) / lambda; + } + } + + public static class Normal implements Distribution { + private final double mean; + private final double sigma; + + public Normal(double mean, double sigma) { + this.mean = mean; + this.sigma = sigma; + } + + @Override + public double sample(Rng rng) { + var sample = rng.random(); + return mean + sigma * Math.sqrt(-2 * Math.log(sample)) * Math.cos(2 * Math.PI * sample); + } + } + + public static class NormalBoxMuller implements Distribution { + private final double mean; + private final double sigma; + private double next = Double.NaN; + + public NormalBoxMuller(double mean, double sigma) { + this.mean = mean; + this.sigma = sigma; + } + + @Override + public double sample(Rng rng) { + if (!Double.isNaN(next)) { + var sample = next; + next = Double.NaN; + return sample; + } + + var sample1 = rng.random(); + var sample2 = rng.random(); + next = mean + sigma * Math.sqrt(-2 * Math.log(sample1)) * Math.sin(2 * Math.PI * sample2); + return mean + sigma * Math.sqrt(-2 * Math.log(sample1)) * Math.cos(2 * Math.PI * sample2); + } + } +}