From 69b8e5710be8c541bfd2c8525108a6df55294bcf Mon Sep 17 00:00:00 2001 From: Berack96 Date: Wed, 22 Jan 2025 17:04:53 +0100 Subject: [PATCH] Refatoring - separated net topology from the simulation nodes - moved files in a better hierarchy --- src/main/java/net/berack/upo/valpre/Main.java | 16 +- .../net/berack/upo/valpre/NetSimulation.java | 310 ------------------ .../upo/valpre/{ => sim}/EndCriteria.java | 10 +- .../berack/upo/valpre/{ => sim}/Event.java | 2 +- .../java/net/berack/upo/valpre/sim/Net.java | 131 ++++++++ .../upo/valpre/{ => sim}/ServerNode.java | 47 +-- .../net/berack/upo/valpre/sim/Simulation.java | 226 +++++++++++++ .../upo/valpre/sim/SimulationMultiple.java | 85 +++++ .../upo/valpre/sim/stats/ConsoleTable.java | 50 +++ .../valpre/{ => sim/stats}/NetStatistics.java | 42 +-- 10 files changed, 511 insertions(+), 408 deletions(-) delete mode 100644 src/main/java/net/berack/upo/valpre/NetSimulation.java rename src/main/java/net/berack/upo/valpre/{ => sim}/EndCriteria.java (89%) rename src/main/java/net/berack/upo/valpre/{ => sim}/Event.java (98%) create mode 100644 src/main/java/net/berack/upo/valpre/sim/Net.java rename src/main/java/net/berack/upo/valpre/{ => sim}/ServerNode.java (71%) create mode 100644 src/main/java/net/berack/upo/valpre/sim/Simulation.java create mode 100644 src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java create mode 100644 src/main/java/net/berack/upo/valpre/sim/stats/ConsoleTable.java rename src/main/java/net/berack/upo/valpre/{ => sim/stats}/NetStatistics.java (87%) diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index 3e766a8..f996bbe 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -1,6 +1,9 @@ package net.berack.upo.valpre; import net.berack.upo.valpre.rand.Distribution; +import net.berack.upo.valpre.sim.SimulationMultiple; +import net.berack.upo.valpre.sim.Net; +import net.berack.upo.valpre.sim.ServerNode; public class Main { public static void main(String[] args) throws Exception { @@ -12,18 +15,19 @@ public class Main { var sigma = 0.6; // Build the network + var net = new Net(); var node1 = ServerNode.createLimitedSource("Source", new Distribution.Exponential(lambda), total); var node2 = ServerNode.createQueue("Queue", 1, new Distribution.NormalBoxMuller(mu, sigma)); - node1.addChild(node2, 1.0); - - /// Run the simulation - var sim = new NetSimulation(); - sim.addNode(node1); - sim.addNode(node2); + net.addNode(node1); + net.addNode(node2); + net.addConnection(node1.name, node2.name, 1.0); + net.normalizeWeights(); + /// Run multiple simulations // var maxDepartures = new EndSimulationCriteria.MaxDepartures("Queue", total); // var maxTime = new EndSimulationCriteria.MaxTime(1000.0); var nano = System.nanoTime(); + var sim = new SimulationMultiple(net); var results = sim.runParallel(seed, 1000); nano = System.nanoTime() - nano; diff --git a/src/main/java/net/berack/upo/valpre/NetSimulation.java b/src/main/java/net/berack/upo/valpre/NetSimulation.java deleted file mode 100644 index 753ccad..0000000 --- a/src/main/java/net/berack/upo/valpre/NetSimulation.java +++ /dev/null @@ -1,310 +0,0 @@ -package net.berack.upo.valpre; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.PriorityQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import net.berack.upo.valpre.NetStatistics.RunResult; -import net.berack.upo.valpre.NetStatistics.Statistics; -import net.berack.upo.valpre.rand.Rng; -import net.berack.upo.valpre.rand.Rngs; - -/** - * A network simulation that uses a discrete event simulation to model the - * behavior of a network of servers. - */ -public class NetSimulation { - private final Collection servers = new ArrayList<>(); - - /** - * Adds a new server node to the network. - * - * @param node The server node to add. - */ - public void addNode(ServerNode node) { - this.servers.add(node); - } - - /** - * Run the simualtion multiple times with the given seed and number of runs. - * The runs are calculated one after the other. For a parallel run see - * {@link #runParallel(long, int, EndCriteria...)}. - * - * @param seed The seed to use for the random number generator. - * @param runs The number of runs to perform. - * @param criterias The criteria to determine when to end the simulation. If - * null then the simulation will run until there are no more - * events. - * @return The statistics the network. - */ - public NetStatistics run(long seed, int runs, EndCriteria... criterias) { - var rng = new Rng(seed); - var stats = new RunResult[runs]; - - for (int i = 0; i < runs; i++) { - stats[i] = this.run(rng, criterias); - } - return new NetStatistics(stats); - } - - /** - * Runs the simulation multiple times with the given seed and number of runs. - * The runs are calculated in parallel using the given number of threads. - * The maximum number of threads are determined by the available processors - * and the number of runs. - * - * @param seed The seed to use for the random number generator. - * @param runs The number of runs to perform. - * @param criterias The criteria to determine when to end the simulation. If - * null then the simulation will run until there are no more - * events. - * @return The statistics the network. - * @throws InterruptedException If the threads are interrupted. - * @throws ExecutionException If the one of the threads has been aborted. - */ - public NetStatistics runParallel(long seed, int runs, EndCriteria... criterias) - throws InterruptedException, ExecutionException { - var rngs = new Rngs(seed); - var results = new NetStatistics.RunResult[runs]; - var futures = new Future[runs]; - - var numThreads = Math.min(runs, Runtime.getRuntime().availableProcessors()); - try (var threads = Executors.newFixedThreadPool(numThreads)) { - for (int i = 0; i < runs; i++) { - final var id = i; - futures[i] = threads.submit(() -> { - results[id] = this.run(rngs.getRng(id), criterias); - }); - } - - for (var i = 0; i < runs; i++) { - futures[i].get(); - } - - return new NetStatistics(results); - } - } - - /** - * Runs the simulation until a given criteria is met. - * - * @param rng The random number generator to use. - * @param criterias The criteria to determine when to end the simulation. If - * null then the simulation will run until there are no more - * events. - * @return The statistics the network. - */ - public NetStatistics.RunResult run(Rng rng, EndCriteria... criterias) { - var run = new SimulationRun(this.servers, rng, criterias); - while (!run.hasEnded()) - run.processNextEvent(); - return run.endSimulation(); - } - - /** - * Process an entire run of the simulation. - */ - public static class SimulationRun { - private final Map nodes; - private final PriorityQueue fel; - private final EndCriteria[] criterias; - private final long timeStartedNano; - private final long seed; - private final Rng rng; - private double time; - - /** - * Creates a new run of the simulation with the given nodes and random number - * generator. - * - * @param nodes The nodes in the network. - * @param rng The random number generator to use. - * @param criterias when the simulation has to end. - */ - private SimulationRun(Collection nodes, Rng rng, EndCriteria... criterias) { - this.nodes = new HashMap<>(); - this.fel = new PriorityQueue<>(); - this.criterias = criterias; - this.timeStartedNano = System.nanoTime(); - this.seed = rng.getSeed(); - this.rng = rng; - this.time = 0.0d; - - // Initial arrivals (if spawned) - for (var node : nodes) { - this.nodes.put(node.name, new NodeBehavior()); - if (node.shouldSpawnArrival(0)) - this.addArrival(node); - } - } - - /** - * Processes the next event in the future event list. - * This method will throw NullPointerException if there are no more events. - * You should check if the simulation has ended before calling this method. - * - * @see #hasEnded() - */ - public void processNextEvent() { - var event = fel.poll(); - var node = this.nodes.get(event.node.name); - this.time = event.time; - - switch (event.type) { - case ARRIVAL -> { - if (node.updateArrival(event.time, event.node.maxServers)) - this.addDeparture(event.node); - } - case DEPARTURE -> { - if (node.updateDeparture(event.time)) - this.addDeparture(event.node); - - var next = event.node.getChild(this.rng); - if (next != null) { - this.addArrival(next); - } - if (event.node.shouldSpawnArrival(node.stats.numArrivals)) { - this.addArrival(event.node); - } - } - } - } - - /** - * Ends the simulation and returns the statistics of the network. - * - * @return The statistics of the network. - */ - private NetStatistics.RunResult endSimulation() { - var elapsed = System.nanoTime() - this.timeStartedNano; - var nodes = new HashMap(); - for (var entry : this.nodes.entrySet()) - nodes.put(entry.getKey(), entry.getValue().stats); - - return new RunResult(this.seed, this.time, elapsed, nodes); - } - - /** - * Get the current time. - * - * @return a double representing the current time of the simulation. - */ - public double getTime() { - return this.time; - } - - /** - * Get the node requested by the name passed as a string. - * - * @param node the name of the node - * @return the node - */ - public NodeBehavior getNode(String node) { - return this.getNode(node); - } - - /** - * Adds an arrival event to the future event list. The event is created based - * on the given node, and no delay is added. - * - * @param node The node to create the event for. - */ - public void addArrival(ServerNode node) { - var event = Event.newArrival(node, this.time); - fel.add(event); - } - - /** - * Adds a departure event to the future event list. The event is created based - * on the given node, and the delay is determined by the node's distribution. - * - * @param node The node to create the event for. - */ - public void addDeparture(ServerNode node) { - var delay = node.getPositiveSample(this.rng); - var event = Event.newDeparture(node, this.time + delay); - fel.add(event); - } - - /** - * Determines if the simulation has finshed based on the given criteria. - * - * @return True if the simulation should end, false otherwise. - */ - public boolean hasEnded() { - if (fel.isEmpty()) { - return true; - } - for (var c : this.criterias) { - if (c.shouldEnd(this)) { - return true; - } - } - return false; - } - } - - /** - * Represents a summary of the behavior of a server node in the network. - * It is used by the simulation to track the number of arrivals and departures, - * the maximum queue length, the busy time, and the response time. - */ - public static class NodeBehavior { - public int numServerBusy = 0; - public final Statistics stats = new Statistics(); - private final ArrayDeque queue = new ArrayDeque<>(); - - /** - * TODO - * - * @param time - * @param maxServers - * @return - */ - public boolean updateArrival(double time, int maxServers) { - var total = this.stats.averageQueueLength * this.stats.numArrivals; - - this.queue.add(time); - this.stats.numArrivals++; - this.stats.averageQueueLength = (total + this.queue.size()) / this.stats.numArrivals; - this.stats.maxQueueLength = Math.max(this.stats.maxQueueLength, this.queue.size()); - - var startDeparture = maxServers > this.numServerBusy; - if (startDeparture) { - this.numServerBusy++; - } else { - this.stats.busyTime += time - this.stats.lastEventTime; - } - - this.stats.lastEventTime = time; - return startDeparture; - } - - /** - * TODO - * - * @param time - * @return - */ - public boolean updateDeparture(double time) { - var startService = this.queue.poll(); - var response = time - startService; - - var startDeparture = this.queue.size() >= this.numServerBusy; - if (!startDeparture) { - this.numServerBusy--; - } - - this.stats.numDepartures++; - this.stats.responseTime += response; - this.stats.busyTime += time - this.stats.lastEventTime; - this.stats.lastEventTime = time; - return startDeparture; - } - } -} diff --git a/src/main/java/net/berack/upo/valpre/EndCriteria.java b/src/main/java/net/berack/upo/valpre/sim/EndCriteria.java similarity index 89% rename from src/main/java/net/berack/upo/valpre/EndCriteria.java rename to src/main/java/net/berack/upo/valpre/sim/EndCriteria.java index c1be9c9..962ae5d 100644 --- a/src/main/java/net/berack/upo/valpre/EndCriteria.java +++ b/src/main/java/net/berack/upo/valpre/sim/EndCriteria.java @@ -1,4 +1,4 @@ -package net.berack.upo.valpre; +package net.berack.upo.valpre.sim; /** * Criteria to determine when to end the simulation. @@ -10,7 +10,7 @@ public interface EndCriteria { * @param run The current run of the network. * @return True if the simulation should end, false otherwise. */ - public boolean shouldEnd(NetSimulation.SimulationRun run); + public boolean shouldEnd(Simulation run); /** * Ends the simulation when the given node has reached the specified number of @@ -33,7 +33,7 @@ public interface EndCriteria { } @Override - public boolean shouldEnd(NetSimulation.SimulationRun run) { + public boolean shouldEnd(Simulation run) { return run.getNode(nodeName).stats.numArrivals >= this.maxArrivals; } } @@ -59,7 +59,7 @@ public interface EndCriteria { } @Override - public boolean shouldEnd(NetSimulation.SimulationRun run) { + public boolean shouldEnd(Simulation run) { return run.getNode(nodeName).stats.numDepartures >= this.maxDepartures; } } @@ -82,7 +82,7 @@ public interface EndCriteria { } @Override - public boolean shouldEnd(NetSimulation.SimulationRun run) { + public boolean shouldEnd(Simulation run) { return run.getTime() >= this.maxTime; } } diff --git a/src/main/java/net/berack/upo/valpre/Event.java b/src/main/java/net/berack/upo/valpre/sim/Event.java similarity index 98% rename from src/main/java/net/berack/upo/valpre/Event.java rename to src/main/java/net/berack/upo/valpre/sim/Event.java index dd8e22e..68004e3 100644 --- a/src/main/java/net/berack/upo/valpre/Event.java +++ b/src/main/java/net/berack/upo/valpre/sim/Event.java @@ -1,4 +1,4 @@ -package net.berack.upo.valpre; +package net.berack.upo.valpre.sim; /** * Represents an event in the simulation. diff --git a/src/main/java/net/berack/upo/valpre/sim/Net.java b/src/main/java/net/berack/upo/valpre/sim/Net.java new file mode 100644 index 0000000..6fa8414 --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/sim/Net.java @@ -0,0 +1,131 @@ +package net.berack.upo.valpre.sim; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; + +import net.berack.upo.valpre.rand.Rng; + +/** + * TODO + */ +public final class Net { + private final HashMap indices = new HashMap<>(); + private final List servers = new ArrayList<>(); + private final List> connections = new ArrayList<>(); + private final List sum = new ArrayList<>(); + + /** + * Adds a new server node to the network. + * The unique identifier for the nodes is the name and, if you try to add a node + * that has the same name of another, then the method will return an exception + * + * @param node The server node to add. + * @throws IllegalArgumentException if the node already exist + */ + public void addNode(ServerNode node) { + if (this.indices.containsKey(node.name)) + throw new IllegalArgumentException("Node already exist"); + + this.servers.add(node); + this.indices.put(node.name, this.servers.size() - 1); + this.connections.add(new ArrayList<>()); + this.sum.add(0.0); + } + + /** + * Adds a connection between the nodes with the given weight to select it. + * The weight must be > 0 and the nodes must be already added to the net. + * If the connection is already present then the new weight is used. + * + * @param parent The parent node. + * @param child The child node to add. + * @param weight The probability of the child node. + * @throws NullPointerException if one of the two nodes are not in the net + * @throws IllegalArgumentException if the weight is negative or zero + */ + public void addConnection(String parent, String child, double weight) { + var nodeP = this.indices.get(parent); + var nodeC = this.indices.get(child); + + if (weight <= 0) + throw new IllegalArgumentException("Weight must be > 0"); + if (nodeP == nodeC && nodeP == null) + throw new NullPointerException("One of the nodes does not exist"); + + var list = this.connections.get(nodeP); + for (var conn : list) { + if (conn.index == nodeC) { + conn.weight = weight; + return; + } + } + list.add(new Connection(nodeC, weight)); + } + + /** + * Get the total number of the nodes in the net + * + * @return the size of the net + */ + public int size() { + return this.servers.size(); + } + + /** + * Get one of the child nodes from the parent specified. If the index is out of + * bounds then an + * exception is thrown. If the node has no child then null is returned; + * + * @param parent the parent node + * @param rng the random number generator used for getting one of the child + * @return the resultig node + */ + public ServerNode getChildOf(ServerNode parent, Rng rng) { + var index = this.indices.get(parent.name); + var random = rng.random(); + for (var conn : this.connections.get(index)) { + random -= conn.weight / 1.0; + if (random <= 0) { + return this.servers.get(conn.index); + } + } + return null; + } + + /** + * Normalizes the weights in each connections so that their sum equals 1. + * This method should be called by the user if they have inserted weights that + * are not summing to 1 or are unsure. + */ + public void normalizeWeights() { + for (var list : this.connections) { + var sum = 0.0d; + for (var conn : list) + sum += conn.weight; + for (var conn : list) + conn.weight /= sum; + } + } + + /** + * Apply a consumer to all the nodes. The implementation uses a stream and for + * this reason you should consider to make thread safe the consumer. + * + * @param consumer a function that takes in input a ServerNode + */ + public void forEachNode(Consumer consumer) { + this.servers.stream().forEach(consumer); + } + + public static class Connection { + public final int index; + public double weight; + + private Connection(int index, double weight) { + this.index = index; + this.weight = weight; + } + } +} diff --git a/src/main/java/net/berack/upo/valpre/ServerNode.java b/src/main/java/net/berack/upo/valpre/sim/ServerNode.java similarity index 71% rename from src/main/java/net/berack/upo/valpre/ServerNode.java rename to src/main/java/net/berack/upo/valpre/sim/ServerNode.java index 4e6c5f0..1d2eba7 100644 --- a/src/main/java/net/berack/upo/valpre/ServerNode.java +++ b/src/main/java/net/berack/upo/valpre/sim/ServerNode.java @@ -1,7 +1,5 @@ -package net.berack.upo.valpre; +package net.berack.upo.valpre.sim; -import java.util.ArrayList; -import java.util.List; import net.berack.upo.valpre.rand.Distribution; import net.berack.upo.valpre.rand.Rng; @@ -14,8 +12,6 @@ public class ServerNode { public final int maxServers; public final int spawnArrivals; public final Distribution distribution; - private final List children = new ArrayList<>(); - private double sumProbabilities = 0.0; /** * Creates a source node with the given name and distribution. @@ -71,34 +67,6 @@ public class ServerNode { this.spawnArrivals = spawnArrivals; } - /** - * Adds a child node with the given probability to select it. - * - * @param node The child node to add. - * @param probability The probability of the child node. - */ - public void addChild(ServerNode node, double probability) { - this.children.add(new NodeChild(node, probability)); - this.sumProbabilities += probability; - } - - /** - * Gets a child node based on the given random number generator. - * - * @param rng The random number generator to use. - * @return The child node selected based on the probabilities. - */ - 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; - } - /** * Gets a positive sample from the distribution. * This is useful if you need to generate a positive value from a distribution @@ -125,17 +93,4 @@ public class ServerNode { public boolean shouldSpawnArrival(double numArrivals) { return this.spawnArrivals > numArrivals; } - - /** - * Represents a child node with a probability to select it. - */ - 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/sim/Simulation.java b/src/main/java/net/berack/upo/valpre/sim/Simulation.java new file mode 100644 index 0000000..9f5ba5a --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/sim/Simulation.java @@ -0,0 +1,226 @@ +package net.berack.upo.valpre.sim; + +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +import net.berack.upo.valpre.rand.Rng; +import net.berack.upo.valpre.sim.stats.NetStatistics; +import net.berack.upo.valpre.sim.stats.NetStatistics.Statistics; + +/** + * Process an entire run of the simulation. + */ +public final class Simulation { + private final Net net; + private final Map nodes; + private final PriorityQueue fel; + private final EndCriteria[] criterias; + private final long timeStartedNano; + private final long seed; + private final Rng rng; + private double time; + + /** + * Creates a new run of the simulation with the given nodes and random number + * generator. + * + * @param nodes The nodes in the network. + * @param rng The random number generator to use. + * @param criterias when the simulation has to end. + */ + public Simulation(Net net, Rng rng, EndCriteria... criterias) { + this.net = net; + this.nodes = new HashMap<>(); + this.fel = new PriorityQueue<>(); + this.criterias = criterias; + this.timeStartedNano = System.nanoTime(); + this.seed = rng.getSeed(); + this.rng = rng; + this.time = 0.0d; + + // Initial arrivals (if spawned) + net.forEachNode(node -> { + this.nodes.put(node.name, new NodeBehavior()); + if (node.shouldSpawnArrival(0)) + this.addArrival(node); + }); + } + + /** + * Runs the simulation until a given criteria is met. + * + * @return The final statistics the network. + */ + public NetStatistics.RunResult run() { + while (!this.hasEnded()) + this.processNextEvent(); + return this.endSimulation(); + } + + /** + * Processes the next event in the future event list. + * This method will throw NullPointerException if there are no more events. + * You should check if the simulation has ended before calling this method. + * + * @see #hasEnded() + */ + public void processNextEvent() { + var event = fel.poll(); + var node = event.node; + var behaviour = this.nodes.get(node.name); + this.time = event.time; + + switch (event.type) { + case ARRIVAL -> { + if (behaviour.updateArrival(event.time, node.maxServers)) + this.addDeparture(node); + } + case DEPARTURE -> { + if (behaviour.updateDeparture(event.time)) + this.addDeparture(node); + + var next = this.net.getChildOf(node, this.rng); + if (next != null) { + this.addArrival(next); + } + if (node.shouldSpawnArrival(behaviour.stats.numArrivals)) { + this.addArrival(node); + } + } + } + } + + /** + * Ends the simulation and returns the statistics of the network. + * + * @return The statistics of the network. + */ + public NetStatistics.RunResult endSimulation() { + var elapsed = System.nanoTime() - this.timeStartedNano; + var nodes = new HashMap(); + for (var entry : this.nodes.entrySet()) + nodes.put(entry.getKey(), entry.getValue().stats); + + return new NetStatistics.RunResult(this.seed, this.time, elapsed, nodes); + } + + /** + * Get the current time. + * + * @return a double representing the current time of the simulation. + */ + public double getTime() { + return this.time; + } + + /** + * Get the node requested by the name passed as a string. + * + * @param node the name of the node + * @return the node + */ + public NodeBehavior getNode(String node) { + return this.getNode(node); + } + + /** + * Adds an arrival event to the future event list. The event is created based + * on the given node, and no delay is added. + * + * @param node The node to create the event for. + */ + public void addArrival(ServerNode node) { + var event = Event.newArrival(node, this.time); + fel.add(event); + } + + /** + * Adds a departure event to the future event list. The event is created based + * on the given node, and the delay is determined by the node's distribution. + * + * @param node The node to create the event for. + */ + public void addDeparture(ServerNode node) { + var delay = node.getPositiveSample(this.rng); + var event = Event.newDeparture(node, this.time + delay); + fel.add(event); + } + + /** + * Determines if the simulation has finshed based on the given criteria. + * + * @return True if the simulation should end, false otherwise. + */ + public boolean hasEnded() { + if (fel.isEmpty()) { + return true; + } + for (var c : this.criterias) { + if (c.shouldEnd(this)) { + return true; + } + } + return false; + } + + /** + * Represents a summary of the behavior of a server node in the network. + * It is used by the simulation to track the number of arrivals and departures, + * the maximum queue length, the busy time, and the response time. + */ + public static class NodeBehavior { + public int numServerBusy = 0; + public final Statistics stats = new Statistics(); + private final ArrayDeque queue = new ArrayDeque<>(); + + /** + * TODO + * + * @param time + * @param maxServers + * @return + */ + public boolean updateArrival(double time, int maxServers) { + var total = this.stats.averageQueueLength * this.stats.numArrivals; + + this.queue.add(time); + this.stats.numArrivals++; + this.stats.averageQueueLength = (total + this.queue.size()) / this.stats.numArrivals; + this.stats.maxQueueLength = Math.max(this.stats.maxQueueLength, this.queue.size()); + + var startDeparture = maxServers > this.numServerBusy; + if (startDeparture) { + this.numServerBusy++; + } else { + this.stats.busyTime += time - this.stats.lastEventTime; + } + + this.stats.lastEventTime = time; + return startDeparture; + } + + /** + * TODO + * + * @param time + * @return + */ + public boolean updateDeparture(double time) { + var startService = this.queue.poll(); + var response = time - startService; + + var startDeparture = this.queue.size() >= this.numServerBusy; + if (!startDeparture) { + this.numServerBusy--; + } + + this.stats.numDepartures++; + this.stats.responseTime += response; + this.stats.busyTime += time - this.stats.lastEventTime; + this.stats.lastEventTime = time; + return startDeparture; + } + } +} \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java b/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java new file mode 100644 index 0000000..ceae9c2 --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java @@ -0,0 +1,85 @@ +package net.berack.upo.valpre.sim; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import net.berack.upo.valpre.rand.Rng; +import net.berack.upo.valpre.rand.Rngs; +import net.berack.upo.valpre.sim.stats.NetStatistics; +import net.berack.upo.valpre.sim.stats.NetStatistics.RunResult; + +/** + * A network simulation that uses a discrete event simulation to model the + * behavior of a network of servers. + */ +public class SimulationMultiple { + private final Net net; + + public SimulationMultiple(Net net) { + this.net = net; + } + + /** + * Run the simualtion multiple times with the given seed and number of runs. + * The runs are calculated one after the other. For a parallel run see + * {@link #runParallel(long, int, EndCriteria...)}. + * + * @param seed The seed to use for the random number generator. + * @param runs The number of runs to perform. + * @param criterias The criteria to determine when to end the simulation. If + * null then the simulation will run until there are no more + * events. + * @return The statistics the network. + */ + public NetStatistics run(long seed, int runs, EndCriteria... criterias) { + var rng = new Rng(seed); + var stats = new RunResult[runs]; + + for (int i = 0; i < runs; i++) { + var sim = new Simulation(this.net, rng, criterias); + stats[i] = sim.run(); + } + return new NetStatistics(stats); + } + + /** + * Runs the simulation multiple times with the given seed and number of runs. + * The runs are calculated in parallel using the given number of threads. + * The maximum number of threads are determined by the available processors + * and the number of runs. + * + * @param seed The seed to use for the random number generator. + * @param runs The number of runs to perform. + * @param criterias The criteria to determine when to end the simulation. If + * null then the simulation will run until there are no more + * events. + * @return The statistics the network. + * @throws InterruptedException If the threads are interrupted. + * @throws ExecutionException If the one of the threads has been aborted. + */ + public NetStatistics runParallel(long seed, int runs, EndCriteria... criterias) + throws InterruptedException, ExecutionException { + var rngs = new Rngs(seed); + var results = new NetStatistics.RunResult[runs]; + var futures = new Future[runs]; + + var numThreads = Math.min(runs, Runtime.getRuntime().availableProcessors()); + try (var threads = Executors.newFixedThreadPool(numThreads)) { + for (int i = 0; i < runs; i++) { + final var id = i; + futures[i] = threads.submit(() -> { + var sim = new Simulation(this.net, rngs.getRng(id), criterias); + results[id] = sim.run(); + }); + } + + for (var i = 0; i < runs; i++) { + futures[i].get(); + } + + return new NetStatistics(results); + } + } + +} diff --git a/src/main/java/net/berack/upo/valpre/sim/stats/ConsoleTable.java b/src/main/java/net/berack/upo/valpre/sim/stats/ConsoleTable.java new file mode 100644 index 0000000..0532716 --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/sim/stats/ConsoleTable.java @@ -0,0 +1,50 @@ +package net.berack.upo.valpre.sim.stats; + +/** + * TODO + */ +public class ConsoleTable { + + private StringBuilder builder = new StringBuilder(); + private final int maxLen; + private final String border; + + /** + * TODO + * + * @param header + */ + public ConsoleTable(String... header) { + var max = 0; + for (var name : header) + max = Math.max(max, name.length()); + this.maxLen = max + 2; + this.border = ("+" + "═".repeat(maxLen)).repeat(header.length) + "+\n"; + this.builder.append(border); + this.addRow(header); + } + + /** + * TODO + * + * @param values + */ + public void addRow(String... values) { + for (var val : values) { + var diff = maxLen - val.length(); + var first = (int) Math.ceil(diff / 2.0); + builder.append('║'); + builder.append(" ".repeat(first)); + builder.append(val); + builder.append(" ".repeat(diff - first)); + } + + builder.append("║\n"); + builder.append(border); + } + + @Override + public String toString() { + return builder.toString(); + } +} diff --git a/src/main/java/net/berack/upo/valpre/NetStatistics.java b/src/main/java/net/berack/upo/valpre/sim/stats/NetStatistics.java similarity index 87% rename from src/main/java/net/berack/upo/valpre/NetStatistics.java rename to src/main/java/net/berack/upo/valpre/sim/stats/NetStatistics.java index 0a81189..03910d4 100644 --- a/src/main/java/net/berack/upo/valpre/NetStatistics.java +++ b/src/main/java/net/berack/upo/valpre/sim/stats/NetStatistics.java @@ -1,4 +1,4 @@ -package net.berack.upo.valpre; +package net.berack.upo.valpre.sim.stats; import java.util.HashMap; import java.util.Map; @@ -125,7 +125,7 @@ public class NetStatistics { * @param nodes The collection of server nodes to track. * @param rng The random number generator to use. */ - public RunResult(long seed, double time, long elapsed, HashMap nodes) { + public RunResult(long seed, double time, long elapsed, Map nodes) { this.seed = seed; this.simulationTime = time; this.timeElapsedNano = elapsed; @@ -227,42 +227,4 @@ public class NetStatistics { this.lastEventTime = 0.0d; } } - - /** - * TODO - */ - private static class ConsoleTable { - private StringBuilder builder = new StringBuilder(); - private final int maxLen; - private final String border; - - public ConsoleTable(String... header) { - var max = 0; - for (var name : header) - max = Math.max(max, name.length()); - this.maxLen = max + 2; - this.border = ("+" + "═".repeat(maxLen)).repeat(header.length) + "+\n"; - this.builder.append(border); - this.addRow(header); - } - - public void addRow(String... values) { - for (var val : values) { - var diff = maxLen - val.length(); - var first = (int) Math.ceil(diff / 2.0); - builder.append('║'); - builder.append(" ".repeat(first)); - builder.append(val); - builder.append(" ".repeat(diff - first)); - } - - builder.append("║\n"); - builder.append(border); - } - - @Override - public String toString() { - return builder.toString(); - } - } } \ No newline at end of file