From 7b46054b7067ec275d11dad8c35d56d4e2597596 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Fri, 17 Jan 2025 19:39:51 +0100 Subject: [PATCH] End Simulation Criteria --- .../upo/valpre/EndSimulationCriteria.java | 93 +++++++++++++++++++ src/main/java/net/berack/upo/valpre/Main.java | 18 +++- .../net/berack/upo/valpre/NetSimulation.java | 44 +++++---- .../net/berack/upo/valpre/ServerNode.java | 77 +++++++++++---- .../berack/upo/valpre/rand/Distribution.java | 7 +- 5 files changed, 196 insertions(+), 43 deletions(-) create mode 100644 src/main/java/net/berack/upo/valpre/EndSimulationCriteria.java diff --git a/src/main/java/net/berack/upo/valpre/EndSimulationCriteria.java b/src/main/java/net/berack/upo/valpre/EndSimulationCriteria.java new file mode 100644 index 0000000..c1f1b76 --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/EndSimulationCriteria.java @@ -0,0 +1,93 @@ +package net.berack.upo.valpre; + +import java.util.Map; + +import net.berack.upo.valpre.NetSimulation.Statistics; + +/** + * Criteria to determine when to end the simulation. + */ +public interface EndSimulationCriteria { + /** + * Determines if the simulation should end based on the statistics of the nodes. + * + * @param stats The statistics of the nodes in the network. + * @return True if the simulation should end, false otherwise. + */ + public boolean shouldEnd(Map stats); + + /** + * Ends the simulation when the given node has reached the specified number of + * arrivals. + */ + public static class MaxArrivals implements EndSimulationCriteria { + private final String nodeName; + private final int maxArrivals; + + /** + * Creates a new criteria to end the simulation when the given node has reached + * the specified number of arrivals. + * + * @param nodeName The name of the node to check. + * @param maxArrivals The maximum number of arrivals to wait for. + */ + public MaxArrivals(String nodeName, int maxArrivals) { + this.nodeName = nodeName; + this.maxArrivals = maxArrivals; + } + + @Override + public boolean shouldEnd(Map stats) { + return stats.get(nodeName).numArrivals >= this.maxArrivals; + } + } + + /** + * Ends the simulation when the given node has reached the specified number of + * departures. + */ + public static class MaxDepartures implements EndSimulationCriteria { + private final String nodeName; + private final int maxDepartures; + + /** + * Creates a new criteria to end the simulation when the given node has reached + * the specified number of departures. + * + * @param nodeName The name of the node to check. + * @param maxDepartures The maximum number of departures to wait for. + */ + public MaxDepartures(String nodeName, int maxDepartures) { + this.nodeName = nodeName; + this.maxDepartures = maxDepartures; + } + + @Override + public boolean shouldEnd(Map stats) { + return stats.get(nodeName).numDepartures >= this.maxDepartures; + } + } + + /** + * Ends the simulation when the given node has reached the specified number of + * departures. + */ + public static class MaxTime implements EndSimulationCriteria { + private final double maxTime; + + /** + * Creates a new criteria to end the simulation when the given node has reached + * the specified number of departures. + * + * @param maxTime The maximum time to wait for. + */ + public MaxTime(double maxTime) { + this.maxTime = maxTime; + } + + @Override + public boolean shouldEnd(Map stats) { + return stats.values().stream().anyMatch(s -> s.lastEventTime >= this.maxTime); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index e012ad4..5bfe31d 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -4,16 +4,26 @@ import net.berack.upo.valpre.rand.Distribution; public class Main { public static void main(String[] args) { + // Parameters for the simulation + var seed = System.nanoTime(); + var total = 1000; + var lambda = 1.0 / 4.5; + var mu = 3.2; + var sigma = 0.6; + // 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)); + 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(System.nanoTime()); + var sim = new NetSimulation(seed); sim.addNode(node1); sim.addNode(node2); - var results = sim.run(1000, "Queue"); + + var maxDepartures = new EndSimulationCriteria.MaxDepartures("Queue", total); + var maxTime = new EndSimulationCriteria.MaxTime(1000.0); + var results = sim.run(maxDepartures, maxTime); // Display the results for (var entry : results.entrySet()) { diff --git a/src/main/java/net/berack/upo/valpre/NetSimulation.java b/src/main/java/net/berack/upo/valpre/NetSimulation.java index 6df5f3b..2fc86e9 100644 --- a/src/main/java/net/berack/upo/valpre/NetSimulation.java +++ b/src/main/java/net/berack/upo/valpre/NetSimulation.java @@ -12,7 +12,6 @@ import net.berack.upo.valpre.rand.Rng; */ public class NetSimulation { public final long seed; - private final Rng rng; private final Map servers = new HashMap<>(); /** @@ -22,7 +21,6 @@ public class NetSimulation { */ public NetSimulation(long seed) { this.seed = seed; - this.rng = new Rng(seed); } /** @@ -36,34 +34,31 @@ public class NetSimulation { /** * Runs the simulation for the given number of total arrivals, stopping when the - * given node has reached the - * specified number of departures. + * given node has reached the specified number of departures. + * If needed the run method can be called by multiple threads. * - * @param total The total number of arrivals to simulate. - * @param untilDepartureNode The name of the node to stop at. - * @return A map of statistics for each server node in the network. + * @param criteria 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 of the nodes in the network. */ - public Map run(long total, String untilDepartureNode) { + public Map run(EndSimulationCriteria... criteria) { // Initialization var timeNow = 0.0d; - var stats = new HashMap(); + var rng = new Rng(this.seed); // TODO change here for thread variance (use Rngs with ids) var fel = new PriorityQueue(); + var stats = new HashMap(); for (var node : this.servers.values()) { - var s = new Statistics(this.rng); - s.addArrivalIf(node.isSource, node, timeNow, fel); + var s = new Statistics(rng); + s.addArrivalIf(node.shouldSpawnArrival(s.numArrivals), node, timeNow, fel); stats.put(node.name, s); } // Main Simulation Loop - var nodeStop = stats.get(untilDepartureNode); - while (nodeStop.numDepartures < total) { + while (!fel.isEmpty() && !this.shouldEnd(criteria, stats)) { var event = fel.poll(); - if (event == null) { - break; - } - - timeNow = event.time; var statsNode = stats.get(event.node.name); + timeNow = event.time; + switch (event.type) { case ARRIVAL -> statsNode.processArrival(event, timeNow, fel); case DEPARTURE -> statsNode.processDeparture(event, timeNow, fel); @@ -72,6 +67,15 @@ public class NetSimulation { return stats; } + private boolean shouldEnd(EndSimulationCriteria[] criteria, Map stats) { + for (var c : criteria) { + if (c.shouldEnd(stats)) { + return true; + } + } + return false; + } + /** * Represents a statistical summary of the behavior of a server node in the * network. @@ -137,7 +141,7 @@ public class NetSimulation { } this.lastEventTime = timeNow; - this.addArrivalIf(event.node.isSource, event.node, timeNow, fel); + this.addArrivalIf(event.node.shouldSpawnArrival(this.numArrivals), event.node, timeNow, fel); } /** @@ -170,7 +174,7 @@ public class NetSimulation { this.lastEventTime = timeNow; var next = event.node.getChild(rng); - this.addArrivalIf(!event.node.isSink, next, timeNow, fel); + this.addArrivalIf(!event.node.shouldSinkDeparture(this.numDepartures), next, timeNow, fel); } /** diff --git a/src/main/java/net/berack/upo/valpre/ServerNode.java b/src/main/java/net/berack/upo/valpre/ServerNode.java index 24a3b14..ed8f847 100644 --- a/src/main/java/net/berack/upo/valpre/ServerNode.java +++ b/src/main/java/net/berack/upo/valpre/ServerNode.java @@ -12,52 +12,72 @@ import net.berack.upo.valpre.rand.Rng; public class ServerNode { public final String name; public final int maxServers; + public final int sinkDepartures; + public final int spawnArrivals; public final Distribution distribution; - public final boolean isSink; - public final boolean isSource; private final List children = new ArrayList<>(); private double sumProbabilities = 0.0; /** * Creates a source node with the given name and distribution. - * @param name The name of the node. + * It swpawns infinite arrivals (Integer.MAX_VALUE) that are served by infinite + * servers (Integer.MAX_VALUE). + * + * @param name The name of the node. * @param distribution The distribution of the inter-arrival times. * @return The created source node. */ public static ServerNode createSource(String name, Distribution distribution) { - return new ServerNode(name, Integer.MAX_VALUE, distribution, false, true); + return new ServerNode(name, Integer.MAX_VALUE, distribution, Integer.MAX_VALUE, 0); } /** - * Creates a queue node with the given name, maximum number of servers, and distribution. - * @param name The name of the node. - * @param maxServers The maximum number of servers in the queue. + * Creates a source node with the given name, distribution, and number of + * arrivals to spawn that are served by infinite servers (Integer.MAX_VALUE). + * + * @param name The name of the node. + * @param distribution The distribution of the inter-arrival times. + * @param spawnArrivals The number of arrivals to spawn. + * @return The created source node. + */ + public static ServerNode createLimitedSource(String name, Distribution distribution, int spawnArrivals) { + return new ServerNode(name, Integer.MAX_VALUE, distribution, spawnArrivals, 0); + } + + /** + * Creates a queue node with the given name, maximum number of servers, and + * distribution. + * + * @param name The name of the node. + * @param maxServers The maximum number of servers in the queue. * @param distribution The distribution of the service times. * @return The created queue node. */ public static ServerNode createQueue(String name, int maxServers, Distribution distribution) { - return new ServerNode(name, maxServers, distribution, false, false); + return new ServerNode(name, maxServers, distribution, 0, 0); } /** * Creates a generic node with the given name and distribution. - * @param name The name of the node. - * @param maxServers The maximum number of servers in the queue. - * @param distribution The distribution of the service times. - * @param isSink Whether the node is a sink. - * @param isSource Whether the node is a source. + * + * @param name The name of the node. + * @param maxServers The maximum number of servers in the queue. + * @param distribution The distribution of the service times. + * @param spawnArrivals The number of arrivals to spawn. + * @param sinkDepartures The number of departures to sink. */ - public ServerNode(String name, int maxServers, Distribution distribution, boolean isSink, boolean isSource) { + public ServerNode(String name, int maxServers, Distribution distribution, int spawnArrivals, int sinkDepartures) { this.name = name; this.maxServers = maxServers; this.distribution = distribution; - this.isSink = isSink; - this.isSource = isSource; + this.spawnArrivals = spawnArrivals; + this.sinkDepartures = sinkDepartures; } /** * Adds a child node with the given probability to select it. - * @param node The child node to add. + * + * @param node The child node to add. * @param probability The probability of the child node. */ public void addChild(ServerNode node, double probability) { @@ -67,6 +87,7 @@ public class ServerNode { /** * 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. */ @@ -81,6 +102,28 @@ public class ServerNode { return null; } + /** + * Determines if the node should spawn an arrival based on the number of + * arrivals. + * + * @param numArrivals The number of arrivals to check against. + * @return True if the node should spawn an arrival, false otherwise. + */ + public boolean shouldSpawnArrival(int numArrivals) { + return this.spawnArrivals > numArrivals; + } + + /** + * Determines if the node should sink a departure based on the number of + * departures. + * + * @param numDepartures The number of departures to check against. + * @return True if the node should sink a departure, false otherwise. + */ + public boolean shouldSinkDeparture(int numDepartures) { + return this.sinkDepartures > numDepartures; + } + /** * Represents a child node with a probability to select it. */ diff --git a/src/main/java/net/berack/upo/valpre/rand/Distribution.java b/src/main/java/net/berack/upo/valpre/rand/Distribution.java index f5cf58c..e979050 100644 --- a/src/main/java/net/berack/upo/valpre/rand/Distribution.java +++ b/src/main/java/net/berack/upo/valpre/rand/Distribution.java @@ -14,6 +14,7 @@ public interface Distribution { /** * Creates a new exponential distribution with the given rate. + * * @param lambda The rate of the distribution. */ public Exponential(double lambda) { @@ -35,7 +36,8 @@ public interface Distribution { /** * Creates a new normal distribution with the given mean and standard deviation. - * @param mean The mean of the distribution. + * + * @param mean The mean of the distribution. * @param sigma The standard deviation of the distribution. */ public Normal(double mean, double sigma) { @@ -60,7 +62,8 @@ public interface Distribution { /** * Creates a new normal distribution with the given mean and standard deviation. - * @param mean The mean of the distribution. + * + * @param mean The mean of the distribution. * @param sigma The standard deviation of the distribution. */ public NormalBoxMuller(double mean, double sigma) {