diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index 00df773..f919f20 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -29,21 +29,13 @@ public class Main { csv = arguments.get("csv"); var parallel = arguments.containsKey("p"); - // varius distributions - var distrExp = new Distribution.Exponential(lambda); - var distrNorm = new Distribution.NormalBoxMuller(mu, sigma); - var distrUnav = new Distribution.UnavailableTime(0.2, distrNorm); - // Build the network var net = new Net(); - var node1 = ServerNode.createLimitedSource("Source", distrExp, total); - var node2 = ServerNode.createQueue("Queue", 1, distrNorm); - var node3 = ServerNode.createQueue("Queue Wait", 1, distrNorm, distrUnav); + var node1 = ServerNode.createLimitedSource("Source", new Distribution.Exponential(lambda), total); + var node2 = ServerNode.createQueue("Queue", 1, new Distribution.NormalBoxMuller(mu, sigma)); net.addNode(node1); net.addNode(node2); - net.addNode(node3); net.addConnection(node1, node2, 1.0); - net.addConnection(node2, node3, 1.0); net.normalizeWeights(); /// Run multiple simulations 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 f598477..4d06cda 100644 --- a/src/main/java/net/berack/upo/valpre/rand/Distribution.java +++ b/src/main/java/net/berack/upo/valpre/rand/Distribution.java @@ -4,34 +4,8 @@ package net.berack.upo.valpre.rand; * Represents a probability distribution. */ public interface Distribution { - /** - * Return a sample from the distribution. - * - * @param rng The random number generator to use. - * @return A number given from the distribution. - */ public double sample(Rng rng); - /** - * Gets a positive sample from the distribution. - * This is useful if you need to generate a positive value from a distribution - * that can generate negative values. For example, the normal distribution. - * - * @param distribution The distribution to sample - * @param rng The random number generator to use. - * @return A positive or 0 value from the distribution. - */ - public static double getPositiveSample(Distribution distribution, Rng rng) { - if (distribution == null) - return 0; - - double sample; - do { - sample = distribution.sample(rng); - } while (sample < 0); - return sample; - } - /** * Represents an exponential distribution. */ @@ -198,30 +172,4 @@ public interface Distribution { return -Math.log(rng.random()) / lambdas[i]; } } - - /** - * TODO - */ - public static class UnavailableTime implements Distribution { - public final double probability; - public final Distribution distribution; - - /** - * TODO - * - * @param probability - * @param distribution - */ - public UnavailableTime(double probability, Distribution distribution) { - this.probability = probability; - this.distribution = distribution; - } - - @Override - public double sample(Rng rng) { - if (rng.random() < this.probability) - return Distribution.getPositiveSample(this.distribution, rng); - return 0.0; - } - } } diff --git a/src/main/java/net/berack/upo/valpre/sim/Event.java b/src/main/java/net/berack/upo/valpre/sim/Event.java index f605f3d..68004e3 100644 --- a/src/main/java/net/berack/upo/valpre/sim/Event.java +++ b/src/main/java/net/berack/upo/valpre/sim/Event.java @@ -5,7 +5,6 @@ package net.berack.upo.valpre.sim; */ public class Event implements Comparable { public final double time; - public final double started; public final Type type; public final ServerNode node; @@ -16,11 +15,10 @@ public class Event implements Comparable { * @param node The node that the event is associated with. * @param time The time at which the event occurs. */ - private Event(Type type, ServerNode node, double now, double time) { + private Event(Type type, ServerNode node, double time) { this.type = type; this.time = time; this.node = node; - this.started = now; } @Override @@ -32,40 +30,39 @@ public class Event implements Comparable { return 1; } + /** + * Create a new event. + * + * @param node The node that the event is associated with. + * @param time The time at which the event occurs. + * @param type The type of event. + * + * @return The new event. + */ + public static Event newType(ServerNode node, double time, Type type) { + return new Event(type, node, time); + } + /** * Create a new arrival event. * * @param node The node that the event is associated with. - * @param now The time at which the event has been created. * @param time The time at which the event occurs. * @return The new event. */ - public static Event newArrival(ServerNode node, double now, double time) { - return new Event(Type.ARRIVAL, node, now, time); + public static Event newArrival(ServerNode node, double time) { + return new Event(Type.ARRIVAL, node, time); } /** * Create a new departure event. * * @param node The node that the event is associated with. - * @param now The time at which the event has been created. * @param time The time at which the event occurs. * @return The new event. */ - public static Event newDeparture(ServerNode node, double now, double time) { - return new Event(Type.DEPARTURE, node, now, time); - } - - /** - * Create a new unavailable event. - * - * @param node The node that the event is associated with. - * @param now The time at which the event has been created. - * @param time The time at which the event occurs. - * @return The new event. - */ - public static Event newUnavailable(ServerNode node, double now, double time) { - return new Event(Type.UNAVAILABLE, node, now, time); + public static Event newDeparture(ServerNode node, double time) { + return new Event(Type.DEPARTURE, node, time); } /** @@ -74,6 +71,5 @@ public class Event implements Comparable { public static enum Type { ARRIVAL, DEPARTURE, - UNAVAILABLE, } } diff --git a/src/main/java/net/berack/upo/valpre/sim/ServerNode.java b/src/main/java/net/berack/upo/valpre/sim/ServerNode.java index 3ceff3c..63cb395 100644 --- a/src/main/java/net/berack/upo/valpre/sim/ServerNode.java +++ b/src/main/java/net/berack/upo/valpre/sim/ServerNode.java @@ -11,8 +11,7 @@ public class ServerNode { public final String name; public final int maxServers; public final int spawnArrivals; - public final Distribution service; - public final Distribution unavailable; + public final Distribution distribution; /** * Creates a source node with the given name and distribution. @@ -24,7 +23,7 @@ public class ServerNode { * @return The created source node. */ public static ServerNode createSource(String name, Distribution distribution) { - return new ServerNode(name, Integer.MAX_VALUE, distribution, null, Integer.MAX_VALUE); + return new ServerNode(name, Integer.MAX_VALUE, distribution, Integer.MAX_VALUE); } /** @@ -32,39 +31,25 @@ public class ServerNode { * arrivals to spawn that are served by infinite servers (Integer.MAX_VALUE). * * @param name The name of the node. - * @param service The distribution of the inter-arrival times. + * @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 service, int spawnArrivals) { - return new ServerNode(name, Integer.MAX_VALUE, service, null, spawnArrivals); + public static ServerNode createLimitedSource(String name, Distribution distribution, int spawnArrivals) { + return new ServerNode(name, Integer.MAX_VALUE, distribution, spawnArrivals); } /** * 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 service The distribution of the service times. + * @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 service) { - return new ServerNode(name, maxServers, service, null, 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 service The distribution of the service times. - * @param unavailable The distribution of the unavailable times after service. - * @return The created queue node. - */ - public static ServerNode createQueue(String name, int maxServers, Distribution service, Distribution unavailable) { - return new ServerNode(name, maxServers, service, unavailable, 0); + public static ServerNode createQueue(String name, int maxServers, Distribution distribution) { + return new ServerNode(name, maxServers, distribution, 0); } /** @@ -75,14 +60,13 @@ public class ServerNode { * * @param name The name of the node. * @param maxServers The maximum number of servers in the queue. - * @param service The distribution of the service times. - * @param unavailable The distribution of the unavailable times after service. + * @param distribution The distribution of the service times. * @param spawnArrivals The number of arrivals to spawn. * @throws NullPointerException if the distribution is null */ - private ServerNode(String name, int maxServers, Distribution service, Distribution unavailable, int spawnArrivals) { - if (service == null) - throw new NullPointerException("Service distribution can't be null"); + public ServerNode(String name, int maxServers, Distribution distribution, int spawnArrivals) { + if (distribution == null) + throw new NullPointerException("Distribution can't be null"); if (maxServers <= 0) maxServers = 1; if (spawnArrivals < 0) @@ -90,9 +74,8 @@ public class ServerNode { this.name = name; this.maxServers = maxServers; + this.distribution = distribution; this.spawnArrivals = spawnArrivals; - this.service = service; - this.unavailable = unavailable; } /** @@ -103,18 +86,12 @@ public class ServerNode { * @param rng The random number generator to use. * @return A positive sample from the distribution. */ - public double getServiceTime(Rng rng) { - return Distribution.getPositiveSample(this.service, rng); - } - - /** - * Return the unavailable time after a service - * - * @param rng The random number generator to use. - * @return A positive or 0 value from the distribution. - */ - public double getUnavailableTime(Rng rng) { - return Distribution.getPositiveSample(this.unavailable, rng); + public double getPositiveSample(Rng rng) { + double sample; + do { + sample = this.distribution.sample(rng); + } while (sample < 0); + return sample; } /** @@ -128,14 +105,6 @@ public class ServerNode { return this.spawnArrivals > Math.max(0, numArrivals); } - @Override - public boolean equals(Object obj) { - if (!(obj instanceof ServerNode)) - return false; - var other = (ServerNode) obj; - return obj.hashCode() == other.hashCode(); - } - @Override public int hashCode() { return this.name.hashCode(); diff --git a/src/main/java/net/berack/upo/valpre/sim/Simulation.java b/src/main/java/net/berack/upo/valpre/sim/Simulation.java index 79e7389..31a4c75 100644 --- a/src/main/java/net/berack/upo/valpre/sim/Simulation.java +++ b/src/main/java/net/berack/upo/valpre/sim/Simulation.java @@ -26,7 +26,7 @@ public final class Simulation { * Creates a new run of the simulation with the given nodes and random number * generator. * - * @param states The nodes in the network. + * @param states The nodes in the network. * @param rng The random number generator to use. * @param criterias when the simulation has to end. */ @@ -75,18 +75,21 @@ public final class Simulation { switch (event.type) { case ARRIVAL -> { state.queue.add(this.time); - state.stats.updateArrival(event, state.queue.size()); - this.addDeparture(node, state); + state.stats.updateArrival(this.time, state.queue.size(), state.numServerBusy != 0); + + if (state.numServerBusy < node.maxServers) { + state.numServerBusy++; + this.addDeparture(node); + } } case DEPARTURE -> { - var arrivalTime = state.queue.poll(); - state.stats.updateDeparture(event, arrivalTime); - state.numServerBusy--; + var startService = state.queue.poll(); + state.stats.updateDeparture(this.time, this.time - startService); - if (this.addUnavailable(node)) { - state.numServerUnavailable++; + if (state.numServerBusy > state.queue.size()) { + state.numServerBusy--; } else { - this.addDeparture(node, state); + this.addDeparture(node); } var next = this.net.getChildOf(node, this.rng); @@ -97,11 +100,6 @@ public final class Simulation { this.addArrival(node); } } - case UNAVAILABLE -> { - state.numServerUnavailable--; - state.stats.updateUnavailable(event); - this.addDeparture(node, state); - } } } @@ -145,42 +143,20 @@ public final class Simulation { * @param node The node to create the event for. */ public void addArrival(ServerNode node) { - var event = Event.newArrival(node, this.time, this.time); + 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 service - * distribution. + * 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, NodeState state) { - if (state.canServeRequest(node)) { - state.numServerBusy++; - var delay = node.getServiceTime(this.rng); - var event = Event.newDeparture(node, this.time, this.time + delay); - fel.add(event); - } - } - - /** - * Adds an unavailable event to the future event list if the delay is > 0. - * The event is created based on the given node, and the delay is determined by - * the node's unavailability distribution. - * - * @param node The node to create the event for. - * @return if the event has been added to the FEL. - */ - public boolean addUnavailable(ServerNode node) { - var delay = node.getUnavailableTime(rng); - if (delay > 0) { - var event = Event.newUnavailable(node, this.time, this.time + delay); - fel.add(event); - return true; - } - return false; + public void addDeparture(ServerNode node) { + var delay = node.getPositiveSample(this.rng); + var event = Event.newDeparture(node, this.time + delay); + fel.add(event); } /** @@ -207,21 +183,7 @@ public final class Simulation { */ public static class NodeState { public int numServerBusy = 0; - public int numServerUnavailable = 0; public final Statistics stats = new Statistics(); - public final ArrayDeque queue = new ArrayDeque<>(); - - /** - * TODO - * - * @param node - * @return - */ - public boolean canServeRequest(ServerNode node) { - var totalOccupied = this.numServerBusy + this.numServerUnavailable; - var canServe = node.maxServers > totalOccupied; - var hasRequests = this.numServerBusy < this.queue.size(); - return canServe && hasRequests; - } + private final ArrayDeque queue = new ArrayDeque<>(); } } \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/sim/stats/Result.java b/src/main/java/net/berack/upo/valpre/sim/stats/Result.java index 1ade4a2..711e154 100644 --- a/src/main/java/net/berack/upo/valpre/sim/stats/Result.java +++ b/src/main/java/net/berack/upo/valpre/sim/stats/Result.java @@ -31,7 +31,7 @@ public class Result { this.simulationTime = time; this.timeElapsedMS = elapsed; this.nodes = nodes; - this.size = (int) Math.ceil(Math.max(Math.log10(this.simulationTime), 1)); + this.size = (int) Math.ceil(Math.log10(this.simulationTime)); this.iFormat = "%" + this.size + ".0f"; this.fFormat = "%" + (this.size + 4) + ".3f"; } @@ -56,8 +56,8 @@ public class Result { * the statistics for each node in the network. */ public String getSummary() { - String[] h = { "Node", "Departures", "Avg Queue", "Avg Wait", "Avg Unavailable", "Avg Response", "Throughput", - "Utilization %", "Last Event" }; + String[] h = { "Node", "Departures", "Avg Queue", "Avg Wait", "Avg Response", "Throughput", "Utilization %", + "Last Event" }; var table = new ConsoleTable(h); for (var entry : this.nodes.entrySet()) { @@ -67,7 +67,6 @@ public class Result { iFormat.formatted(stats.numDepartures), fFormat.formatted(stats.avgQueueLength), fFormat.formatted(stats.avgWaitTime), - fFormat.formatted(stats.avgUnavailableTime), fFormat.formatted(stats.avgResponse), fFormat.formatted(stats.troughput), fFormat.formatted(stats.utilization * 100), @@ -87,7 +86,7 @@ public class Result { if (tableHeader) builder.append( - "Seed,Node,Arrivals,Departures,MaxQueue,AvgQueue,AvgWait,AvgUnavailable,AvgResponse,BusyTime,WaitTime,UnavailableTime,ResponseTime,LastEventTime,Throughput,Utilization\n"); + "Seed,Node,Arrivals,Departures,MaxQueue,AvgQueue,AvgWait,AvgResponse,BusyTime,WaitTime,ResponseTime,LastEventTime,Throughput,Utilization\n"); for (var entry : this.nodes.entrySet()) { var stats = entry.getValue(); builder.append(this.seed); @@ -104,16 +103,12 @@ public class Result { builder.append(','); builder.append(stats.avgWaitTime); builder.append(','); - builder.append(stats.avgUnavailableTime); - builder.append(','); builder.append(stats.avgResponse); builder.append(','); builder.append(stats.busyTime); builder.append(','); builder.append(stats.waitTime); builder.append(','); - builder.append(stats.unavailableTime); - builder.append(','); builder.append(stats.responseTime); builder.append(','); builder.append(stats.lastEventTime); diff --git a/src/main/java/net/berack/upo/valpre/sim/stats/Statistics.java b/src/main/java/net/berack/upo/valpre/sim/stats/Statistics.java index f326e46..a84286b 100644 --- a/src/main/java/net/berack/upo/valpre/sim/stats/Statistics.java +++ b/src/main/java/net/berack/upo/valpre/sim/stats/Statistics.java @@ -3,8 +3,6 @@ package net.berack.upo.valpre.sim.stats; import java.util.function.BiFunction; import java.util.function.Function; -import net.berack.upo.valpre.sim.Event; - /** * TODO */ @@ -13,14 +11,12 @@ public class Statistics { public double numDepartures = 0.0d; public double maxQueueLength = 0.0d; public double avgQueueLength = 0.0d; - public double unavailableTime = 0.0d; public double busyTime = 0.0d; public double waitTime = 0.0d; public double responseTime = 0.0d; public double lastEventTime = 0.0d; // derived stats, you can calculate them even at the end - public double avgUnavailableTime = 0.0d; public double avgWaitTime = 0.0d; public double avgResponse = 0.0d; public double troughput = 0.0d; @@ -29,49 +25,39 @@ public class Statistics { /** * TODO * - * @param event + * @param time * @param newQueueSize + * @param updateBusy */ - public void updateArrival(Event event, double newQueueSize) { + public void updateArrival(double time, double newQueueSize, boolean updateBusy) { var total = this.avgQueueLength * this.numArrivals; this.numArrivals++; this.avgQueueLength = (total + newQueueSize) / this.numArrivals; this.maxQueueLength = Math.max(this.maxQueueLength, newQueueSize); + if (updateBusy) + this.busyTime += time - this.lastEventTime; - this.lastEventTime = event.time; + this.lastEventTime = time; } /** * TODO * - * @param event + * @param time * @param response */ - public void updateDeparture(Event event, double arrivalTime) { + public void updateDeparture(double time, double response) { this.numDepartures++; - this.responseTime += event.time - arrivalTime; - this.busyTime += event.time - event.started; + this.responseTime += response; + this.busyTime += time - this.lastEventTime; + this.lastEventTime = time; this.waitTime = this.responseTime - this.busyTime; this.avgWaitTime = this.waitTime / this.numDepartures; this.avgResponse = this.responseTime / this.numDepartures; - this.troughput = this.numDepartures / event.time; - this.utilization = this.busyTime / event.time; - - this.lastEventTime = event.time; - } - - /** - * TODO - * - * @param event - */ - public void updateUnavailable(Event event) { - this.unavailableTime += event.time - event.started; - this.avgUnavailableTime = this.unavailableTime / event.time; - - this.lastEventTime = event.time; + this.troughput = this.numDepartures / this.lastEventTime; + this.utilization = this.busyTime / this.lastEventTime; } /** @@ -120,13 +106,11 @@ public class Statistics { save.avgQueueLength = func.apply(val1.avgQueueLength, val2.avgQueueLength); save.busyTime = func.apply(val1.busyTime, val2.busyTime); save.responseTime = func.apply(val1.responseTime, val2.responseTime); - save.unavailableTime = func.apply(val1.unavailableTime, val2.unavailableTime); save.waitTime = func.apply(val1.waitTime, val2.waitTime); save.lastEventTime = func.apply(val1.lastEventTime, val2.lastEventTime); // derived stats save.avgWaitTime = func.apply(val1.avgWaitTime, val2.avgWaitTime); save.avgResponse = func.apply(val1.avgResponse, val2.avgResponse); - save.avgUnavailableTime = func.apply(val1.avgUnavailableTime, val2.avgUnavailableTime); save.troughput = func.apply(val1.troughput, val2.troughput); save.utilization = func.apply(val1.utilization, val2.utilization); } diff --git a/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java b/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java index f223424..2c55f32 100644 --- a/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java +++ b/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java @@ -14,7 +14,7 @@ public class TestSimulation { private static double DELTA = 0.0000001; private static Rng rigged = new RiggedRng(); private static Distribution const0 = new Constant(0.0); - private static Distribution const1 = new Constant(1.0); + private static Distribution const1 = new Constant(0.0); private final static class RiggedRng extends Rng { @Override @@ -38,7 +38,7 @@ public class TestSimulation { @Test public void serverNode() { - var node = ServerNode.createQueue("Nodo", 0, const1); + var node = new ServerNode("Nodo", 0, const1, 0); assertEquals("Nodo", node.name); assertEquals(1, node.maxServers); assertFalse(node.shouldSpawnArrival(0)); @@ -46,7 +46,7 @@ public class TestSimulation { assertFalse(node.shouldSpawnArrival(1000)); assertFalse(node.shouldSpawnArrival(Integer.MAX_VALUE)); assertFalse(node.shouldSpawnArrival(-1)); - assertEquals(1.0, node.getServiceTime(null), DELTA); + assertEquals(1.0, node.getPositiveSample(null), DELTA); node = ServerNode.createQueue("Queue", 50, const1); assertEquals("Queue", node.name); @@ -56,7 +56,7 @@ public class TestSimulation { assertFalse(node.shouldSpawnArrival(1000)); assertFalse(node.shouldSpawnArrival(Integer.MAX_VALUE)); assertFalse(node.shouldSpawnArrival(-1)); - assertEquals(1.0, node.getServiceTime(null), DELTA); + assertEquals(1.0, node.getPositiveSample(null), DELTA); node = ServerNode.createSource("Source", const1); assertEquals("Source", node.name); @@ -67,7 +67,7 @@ public class TestSimulation { assertTrue(node.shouldSpawnArrival(Integer.MAX_VALUE - 1)); assertFalse(node.shouldSpawnArrival(Integer.MAX_VALUE)); assertTrue(node.shouldSpawnArrival(-1)); - assertEquals(1.0, node.getServiceTime(null), DELTA); + assertEquals(1.0, node.getPositiveSample(null), DELTA); node = ServerNode.createLimitedSource("Source", const1, 50); assertEquals("Source", node.name); @@ -78,28 +78,25 @@ public class TestSimulation { assertFalse(node.shouldSpawnArrival(1000)); assertFalse(node.shouldSpawnArrival(Integer.MAX_VALUE)); assertTrue(node.shouldSpawnArrival(-1)); - assertEquals(1.0, node.getServiceTime(null), DELTA); + assertEquals(1.0, node.getPositiveSample(null), DELTA); } @Test public void event() { var node = ServerNode.createSource("Source", const0); - var event = Event.newUnavailable(node, 0, 1); + var event = Event.newType(node, 0, Event.Type.ARRIVAL); assertEquals(node, event.node); - assertEquals(0.0, event.started, 0.000000000001); - assertEquals(1.0, event.time, 0.000000000001); - assertEquals(Event.Type.UNAVAILABLE, event.type); + assertEquals(0.0, event.time, 0.000000000001); + assertEquals(Event.Type.ARRIVAL, event.type); - var event2 = Event.newArrival(node, 1.0, 5.0); + var event2 = Event.newArrival(node, 1.0); assertEquals(node, event2.node); - assertEquals(1.0, event2.started, 0.000000000001); - assertEquals(5.0, event2.time, 0.000000000001); + assertEquals(1.0, event2.time, 0.000000000001); assertEquals(Event.Type.ARRIVAL, event2.type); - var event3 = Event.newDeparture(node, 7.0, 8.0); + var event3 = Event.newDeparture(node, 5.0); assertEquals(node, event3.node); - assertEquals(7.0, event3.started, 0.000000000001); - assertEquals(8.0, event3.time, 0.000000000001); + assertEquals(5.0, event3.time, 0.000000000001); assertEquals(Event.Type.DEPARTURE, event3.type); assertEquals(0, event2.compareTo(event2));