From ffdb3021fd051fd5151fd02624baff4954edf6d7 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Wed, 12 Feb 2025 14:33:32 +0100 Subject: [PATCH] Refactor - simulation result handling to use builder pattern - result to use only arrays instead of maps - updated references to other classes --- .../net/berack/upo/valpre/sim/Simulation.java | 12 +- .../upo/valpre/sim/SimulationMultiple.java | 13 +- .../upo/valpre/sim/stats/CsvResult.java | 18 +- .../berack/upo/valpre/sim/stats/Result.java | 195 ++++++++++++++---- .../upo/valpre/sim/TestSaveExamplesNet.java | 29 ++- .../berack/upo/valpre/sim/TestSimulation.java | 28 +-- 6 files changed, 210 insertions(+), 85 deletions(-) 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 34c317f..85412b9 100644 --- a/src/main/java/net/berack/upo/valpre/sim/Simulation.java +++ b/src/main/java/net/berack/upo/valpre/sim/Simulation.java @@ -1,13 +1,11 @@ package net.berack.upo.valpre.sim; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.PriorityQueue; import net.berack.upo.valpre.rand.Rng; import net.berack.upo.valpre.sim.stats.Result; -import net.berack.upo.valpre.sim.stats.NodeStats; /** * Process an entire run of the simulation. @@ -119,11 +117,13 @@ public final class Simulation { */ public Result endSimulation() { var elapsed = System.nanoTime() - this.timeStartedNano; - var nodes = new HashMap(); - for (var state : this.states) - nodes.put(state.node.name, state.stats); + var builder = new Result.Builder(); + for (var i = 0; i < this.states.length; i++) { + var state = this.states[i]; + builder.addNode(state.node.name, state.stats); + } - return new Result(this.seed, this.time, elapsed * 1e-6, nodes); + return builder.seed(this.seed).times(this.time, elapsed * 1e-6).build(); } /** diff --git a/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java b/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java index 7be0b36..9cc5951 100644 --- a/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java +++ b/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java @@ -1,5 +1,6 @@ package net.berack.upo.valpre.sim; +import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -13,6 +14,7 @@ import net.berack.upo.valpre.sim.stats.Result; */ public class SimulationMultiple { private final Net net; + private final String[] nodes; /** * Create a new object that can simulate the net in input multiple times @@ -20,7 +22,12 @@ public class SimulationMultiple { * @param net the net that should be simulated */ public SimulationMultiple(Net net) { + var nodes = new ArrayList(); + for (var node : net) + nodes.add(node.name); + this.net = net; + this.nodes = nodes.toArray(new String[0]); } /** @@ -37,7 +44,7 @@ public class SimulationMultiple { */ public Result.Summary run(long seed, int runs, EndCriteria... criterias) { var rngs = Rng.getMultipleStreams(seed, runs); - var result = new Result.Summary(rngs[0].getSeed()); + var result = new Result.Summary(rngs[0].getSeed(), nodes); for (int i = 0; i < runs; i++) { var sim = new Simulation(this.net, rngs[i], criterias); @@ -69,7 +76,7 @@ public class SimulationMultiple { var numThreads = Math.min(runs, Runtime.getRuntime().availableProcessors()); try (var threads = Executors.newFixedThreadPool(numThreads)) { - var results = new Result.Summary(rngs[0].getSeed()); + var results = new Result.Summary(rngs[0].getSeed(), nodes); for (int i = 0; i < runs; i++) { final var id = i; @@ -106,7 +113,7 @@ public class SimulationMultiple { throw new IllegalArgumentException("Confidence must be not null"); var rng = new Rng(seed); // Only one RNG for all the simulations - var results = new Result.Summary(rng.getSeed()); + var results = new Result.Summary(rng.getSeed(), nodes); var output = new StringBuilder(); var stop = false; for (int i = 0; !stop; i++) { diff --git a/src/main/java/net/berack/upo/valpre/sim/stats/CsvResult.java b/src/main/java/net/berack/upo/valpre/sim/stats/CsvResult.java index 18e3041..0a5226a 100644 --- a/src/main/java/net/berack/upo/valpre/sim/stats/CsvResult.java +++ b/src/main/java/net/berack/upo/valpre/sim/stats/CsvResult.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Scanner; @@ -75,26 +74,25 @@ public class CsvResult { var results = new ArrayList(); try (var scan = new Scanner(input)) { var headerOrder = CsvResult.extractHeaderPositions(scan.nextLine()); - var nodes = new HashMap(); - var seed = 0L; + var builder = new Result.Builder(); while (scan.hasNextLine()) { var line = scan.nextLine().split(","); var currentSeed = Long.parseLong(line[0]); - var node = line[1]; - if (currentSeed != seed && seed != 0) { - results.add(new Result(seed, 0.0, 0.0, nodes)); - nodes = new HashMap<>(); + if (builder.seed != currentSeed && builder.seed != 0) { + results.add(builder.build()); + builder.reset(); } - seed = currentSeed; + var node = line[1]; var copy = Arrays.copyOfRange(line, 2, line.length); var stats = CsvResult.statsFromCSV(headerOrder, copy); - nodes.put(node, stats); + + builder.seed(currentSeed).addNode(node, stats); } - results.add(new Result(seed, 0.0, 0.0, nodes)); + results.add(builder.build()); } return results; } 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 3790739..c811875 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 @@ -1,8 +1,7 @@ package net.berack.upo.valpre.sim.stats; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -14,7 +13,8 @@ import java.util.Map.Entry; * length, the busy time, and the response time. */ public class Result implements Iterable> { - public final Map nodes; + public final String[] nodes; + public final NodeStats[] stats; public final long seed; public final double simulationTime; public final double timeElapsedMS; @@ -28,24 +28,59 @@ public class Result implements Iterable> { * @param elapsed the real time elapsed while running the simulation in ms * @param nodes all the stats collected by the simulation saved per node */ - public Result(long seed, double time, double elapsed, Map nodes) { + private Result(long seed, double time, double elapsed, String[] nodes, NodeStats[] stats) { this.seed = seed; this.simulationTime = time; this.timeElapsedMS = elapsed; this.nodes = nodes; + this.stats = stats; + } + + public NodeStats getStat(String node) { + for (var i = 0; i < this.nodes.length; i++) + if (this.nodes[i].equals(node)) + return this.stats[i]; + throw new IllegalArgumentException("Node not found"); } @Override public String toString() { - return buildPrintable(this.seed, this.simulationTime, this.timeElapsedMS, this.nodes); + return buildPrintable(this.seed, this.simulationTime, this.timeElapsedMS, this.nodes, this.stats); } @Override - public java.util.Iterator> iterator() { - return this.nodes.entrySet().iterator(); + public Iterator> iterator() { + return new Iterator<>() { + private int index = 0; + + @Override + public boolean hasNext() { + return this.index < Result.this.nodes.length; + } + + @Override + public Entry next() { + var node = Result.this.nodes[this.index]; + var stat = Result.this.stats[this.index]; + this.index++; + return Map.entry(node, stat); + } + }; } - private static String buildPrintable(long seed, double simTime, double timeMS, Map nodes) { + /** + * Create a string representation of the result. It includes the seed, the final + * time of the simulation, the real time elapsed while running the simulation in + * ms, and the stats of each node. + * + * @param seed the initial seed used by the simulation + * @param simTime the final time of the simulation + * @param timeMS the real time elapsed while running the simulation in ms + * @param nodes the names of the nodes + * @param stats the stats of each node + * @return a string representation of the result + */ + private static String buildPrintable(long seed, double simTime, double timeMS, String[] nodes, NodeStats[] stats) { var size = (int) Math.ceil(Math.max(Math.log10(simTime), 1)); var iFormat = "%" + size + ".0f"; var fFormat = "%" + (size + 4) + ".3f"; @@ -59,18 +94,19 @@ public class Result implements Iterable> { var table = new ConsoleTable("Node", "Departures", "Avg Queue", "Avg Wait", "Avg Response", "Throughput", "Utilization %", "Unavailable %", "Last Event"); - for (var entry : nodes.entrySet()) { - var stats = entry.getValue(); + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + var stat = stats[i]; table.addRow( - entry.getKey(), - iFormat.formatted(stats.numDepartures), - fFormat.formatted(stats.avgQueueLength), - fFormat.formatted(stats.avgWaitTime), - fFormat.formatted(stats.avgResponse), - fFormat.formatted(stats.throughput), - fFormat.formatted(stats.utilization * 100), - fFormat.formatted(stats.unavailable * 100), - fFormat.formatted(stats.lastEventTime)); + node, + iFormat.formatted(stat.numDepartures), + fFormat.formatted(stat.avgQueueLength), + fFormat.formatted(stat.avgWaitTime), + fFormat.formatted(stat.avgResponse), + fFormat.formatted(stat.throughput), + fFormat.formatted(stat.utilization * 100), + fFormat.formatted(stat.unavailable * 100), + fFormat.formatted(stat.lastEventTime)); } builder.append(table); @@ -87,7 +123,8 @@ public class Result implements Iterable> { public final long seed; private double avgSimulationTime = 0.0d; private double avgTimeElapsedMS = 0.0d; - private Map stats = new HashMap<>(); + private String[] nodes; + private NodeStats.Summary[] stats; private List runs = new ArrayList<>(); /** @@ -95,8 +132,9 @@ public class Result implements Iterable> { * * @param seed the initial seed used by the simulation */ - public Summary(long seed) { + public Summary(long seed, String[] nodes) { this.seed = seed; + this.setup(nodes); } /** @@ -105,31 +143,47 @@ public class Result implements Iterable> { * @param results the results to summarize */ public Summary(List results) { - this(results.get(0).seed); + var first = results.get(0); + this.seed = first.seed; + this.setup(first.nodes); for (var result : results) this.add(result); } + /** + * Sets up the summary with the nodes. It initializes the statistics of the + * nodes to 0. It is used by the constructors. + */ + private void setup(String[] nodes) { + this.nodes = nodes; + this.stats = new NodeStats.Summary[this.nodes.length]; + for (var i = 0; i < this.nodes.length; i++) + this.stats[i] = new NodeStats.Summary(); + } + /** * Adds the result to the summary. It updates the average simulation time and * the average time elapsed. It also updates the statistics of the nodes. * * @param result the result to add + * @throws IllegalArgumentException if the result is null or the nodes do not + * match */ public void add(Result result) { if (result == null) throw new IllegalArgumentException("Result cannot be null"); + if (result.nodes.length != this.nodes.length) + throw new IllegalArgumentException("Nodes do not match"); var n = this.runs.size() + 1; this.runs.add(result); this.avgSimulationTime += (result.simulationTime - this.avgSimulationTime) / n; this.avgTimeElapsedMS += (result.timeElapsedMS - this.avgTimeElapsedMS) / n; - for (var entry : result.nodes.entrySet()) { - var node = entry.getKey(); - var stats = entry.getValue(); - var summary = this.stats.computeIfAbsent(node, _ -> new NodeStats.Summary()); - summary.update(stats); + for (var i = 0; i < this.nodes.length; i++) { + var stats = this.stats[i]; + var summary = result.stats[i]; + stats.update(summary); } } @@ -156,8 +210,8 @@ public class Result implements Iterable> { * * @return the nodes of the summary */ - public Collection getNodes() { - return this.stats.keySet(); + public List getNodes() { + return List.of(this.nodes); } /** @@ -168,10 +222,10 @@ public class Result implements Iterable> { * @throws IllegalArgumentException if the node is not found */ public NodeStats.Summary getSummaryOf(String node) { - var stat = this.stats.get(node); - if (stat == null) - throw new IllegalArgumentException("Node not found"); - return stat; + for (var i = 0; i < this.nodes.length; i++) + if (this.nodes[i].equals(node)) + return this.stats[i]; + throw new IllegalArgumentException("Node not found"); } /** @@ -185,11 +239,78 @@ public class Result implements Iterable> { @Override public String toString() { - var stats = new HashMap(); - for (var entry : this.stats.entrySet()) - stats.put(entry.getKey(), entry.getValue().average); + var stats = new NodeStats[this.nodes.length]; + for (var i = 0; i < this.nodes.length; i++) + stats[i] = this.stats[i].average; - return buildPrintable(this.seed, this.avgSimulationTime, this.avgTimeElapsedMS, stats); + return buildPrintable(this.seed, this.avgSimulationTime, this.avgTimeElapsedMS, this.nodes, stats); + } + } + + /** + * A builder class to create a new result object. It allows to set the seed, the + * simulation time, the time elapsed, the nodes, and the stats. + */ + public static class Builder { + public long seed; + public double simulationTime; + public double timeElapsedMS; + private List nodes = new ArrayList<>(); + private List stats = new ArrayList<>(); + + /** + * Resets the builder to its initial state. + */ + public Builder reset() { + this.seed = 0; + this.simulationTime = 0.0d; + this.timeElapsedMS = 0.0d; + this.nodes.clear(); + this.stats.clear(); + return this; + } + + /** + * Sets the seed of the result. + * + * @param seed the seed to set + * @return the builder + */ + public Builder seed(long seed) { + this.seed = seed; + return this; + } + + /** + * Sets the simulation time and the time elapsed of the result. + * + * @param simulationTime the simulation time to set + * @param timeElapsedMS the time elapsed to set + * @return the builder + */ + public Builder times(double simulationTime, double timeElapsedMS) { + this.simulationTime = simulationTime; + this.timeElapsedMS = timeElapsedMS; + return this; + } + + /** + * Adds a node and its stats to the result. + * + * @param node the node to add + * @param stat the stats of the node + * @return the builder + */ + public Builder addNode(String node, NodeStats stat) { + this.nodes.add(node); + this.stats.add(stat); + return this; + } + + public Result build() { + var nodes = this.nodes.toArray(String[]::new); + var stats = this.stats.toArray(NodeStats[]::new); + return new Result(this.seed, this.simulationTime, this.timeElapsedMS, nodes, stats); } } } diff --git a/src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java b/src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java index 5075bc7..b354169 100644 --- a/src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java +++ b/src/test/java/net/berack/upo/valpre/sim/TestSaveExamplesNet.java @@ -1,11 +1,10 @@ package net.berack.upo.valpre.sim; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.HashSet; -import java.util.Set; - import org.junit.Test; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -78,8 +77,8 @@ public class TestSaveExamplesNet { assertEquals(Rng.DEFAULT, res.seed); assertEquals(time, res.simulationTime, maxErr); - testNode(res.nodes.get("Source"), 10000, time, 1.0, 4.5, 0.0, 0.0); - testNode(res.nodes.get("Queue"), 10000, time, 2.6, 7.2, 4.0, 0.0); + testNode(res.getStat("Source"), 10000, time, 1.0, 4.5, 0.0, 0.0); + testNode(res.getStat("Queue"), 10000, time, 2.6, 7.2, 4.0, 0.0); } @Test @@ -91,9 +90,9 @@ public class TestSaveExamplesNet { assertEquals(Rng.DEFAULT, res.seed); assertEquals(time, res.simulationTime, maxErr); - testNode(res.nodes.get("Source"), 10000, time, 1.0, 4.5, 0.0, 0.0); - testNode(res.nodes.get("Queue"), 10000, time, 2.6, 7.2, 4.0, 0.0); - testNode(res.nodes.get("Queue Wait"), 10000, time, 5.8, 22.3, 19.1, 8497.7); + testNode(res.getStat("Source"), 10000, time, 1.0, 4.5, 0.0, 0.0); + testNode(res.getStat("Queue"), 10000, time, 2.6, 7.2, 4.0, 0.0); + testNode(res.getStat("Queue Wait"), 10000, time, 5.8, 22.3, 19.1, 8497.7); } @Test @@ -105,9 +104,9 @@ public class TestSaveExamplesNet { assertEquals(Rng.DEFAULT, res.seed); assertEquals(time, res.simulationTime, maxErr); - testNode(res.nodes.get("Source"), 10000, time, 1.0, 0.6, 0.0, 0.0); - testNode(res.nodes.get("Service1"), 10000, time, 3.5, 1.7, 1.2, 0.0); - testNode(res.nodes.get("Service2"), 10000, time, 1.7, 0.5, 0.22, 102.2); + testNode(res.getStat("Source"), 10000, time, 1.0, 0.6, 0.0, 0.0); + testNode(res.getStat("Service1"), 10000, time, 3.5, 1.7, 1.2, 0.0); + testNode(res.getStat("Service2"), 10000, time, 1.7, 0.5, 0.22, 102.2); } private void testNode(NodeStats stat, double numClients, double time, double avgQueue, @@ -140,11 +139,11 @@ public class TestSaveExamplesNet { var list = new CsvResult(csv1).loadResults(); var seeds = new HashSet(); for (var element : list) { - assertEquals(Set.of("Source", "Queue"), element.nodes.keySet()); - assertEquals(10000, element.nodes.get("Source").numArrivals, 0.1); - assertEquals(10000, element.nodes.get("Queue").numArrivals, 0.1); - assertEquals(0.22, element.nodes.get("Source").throughput, 0.1); - assertEquals(0.22, element.nodes.get("Queue").throughput, 0.1); + assertArrayEquals(new String[] { "Source", "Queue" }, element.nodes); + assertEquals(10000, element.getStat("Source").numArrivals, 0.1); + assertEquals(10000, element.getStat("Queue").numArrivals, 0.1); + assertEquals(0.22, element.getStat("Source").throughput, 0.1); + assertEquals(0.22, element.getStat("Queue").throughput, 0.1); seeds.add(element.seed); } 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 560e365..82fb462 100644 --- a/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java +++ b/src/test/java/net/berack/upo/valpre/sim/TestSimulation.java @@ -495,11 +495,11 @@ public class TestSimulation { assertEquals(2.0, result.simulationTime, DELTA); assertEquals(sim.seed, result.seed); assertEquals(elapsed * 1e-6, result.timeElapsedMS, diff); - assertEquals(2, result.nodes.size()); - assertEquals(1, result.nodes.get(node0.name).numArrivals, DELTA); - assertEquals(1, result.nodes.get(node0.name).numDepartures, DELTA); - assertEquals(1, result.nodes.get(node1.name).numArrivals, DELTA); - assertEquals(1, result.nodes.get(node1.name).numDepartures, DELTA); + assertEquals(2, result.stats.length); + assertEquals(1, result.stats[0].numArrivals, DELTA); + assertEquals(1, result.stats[0].numDepartures, DELTA); + assertEquals(1, result.stats[1].numArrivals, DELTA); + assertEquals(1, result.stats[1].numDepartures, DELTA); } @Test @@ -520,10 +520,10 @@ public class TestSimulation { assertTrue(sim.hasEnded()); var res = sim.endSimulation(); - assertEquals(6, res.nodes.get(node0.name).numArrivals, DELTA); - assertEquals(5, res.nodes.get(node0.name).numDepartures, DELTA); - assertEquals(4, res.nodes.get(node1.name).numArrivals, DELTA); - assertEquals(3, res.nodes.get(node1.name).numDepartures, DELTA); + assertEquals(6, res.stats[0].numArrivals, DELTA); + assertEquals(5, res.stats[0].numDepartures, DELTA); + assertEquals(4, res.stats[1].numArrivals, DELTA); + assertEquals(3, res.stats[1].numDepartures, DELTA); } @Test @@ -533,7 +533,7 @@ public class TestSimulation { var sim = new Simulation(net, rigged); var result = sim.run(); - var nodeStat = result.nodes.get("Source"); + var nodeStat = result.getStat("Source"); assertEquals(50, nodeStat.numArrivals, DELTA); assertEquals(50, nodeStat.numDepartures, DELTA); assertEquals(1.0, nodeStat.avgQueueLength, DELTA); @@ -551,7 +551,7 @@ public class TestSimulation { sim = new Simulation(net, rigged); result = sim.run(); - nodeStat = result.nodes.get("Source"); + nodeStat = result.getStat("Source"); assertEquals(50, nodeStat.numArrivals, DELTA); assertEquals(50, nodeStat.numDepartures, DELTA); assertEquals(1.0, nodeStat.avgQueueLength, DELTA); @@ -563,7 +563,7 @@ public class TestSimulation { assertEquals(1.0, nodeStat.throughput, DELTA); assertEquals(1.0, nodeStat.utilization, DELTA); assertEquals(0.0, nodeStat.unavailable, DELTA); - nodeStat = result.nodes.get("Queue"); + nodeStat = result.getStat("Queue"); assertEquals(50, nodeStat.numArrivals, DELTA); assertEquals(50, nodeStat.numDepartures, DELTA); assertEquals(1.0, nodeStat.avgQueueLength, DELTA); @@ -588,7 +588,7 @@ public class TestSimulation { var sim = new Simulation(net, rigged); var result = sim.run(); - var nodeStat = result.nodes.get("Source"); + var nodeStat = result.getStat("Source"); assertEquals(50, nodeStat.numArrivals, DELTA); assertEquals(50, nodeStat.numDepartures, DELTA); assertEquals(1.0, nodeStat.avgQueueLength, DELTA); @@ -601,7 +601,7 @@ public class TestSimulation { assertEquals(1.0, nodeStat.utilization, DELTA); assertEquals(0.0, nodeStat.unavailable, DELTA); - nodeStat = result.nodes.get("Queue"); + nodeStat = result.getStat("Queue"); assertEquals(44, nodeStat.numArrivals, DELTA); assertEquals(44, nodeStat.numDepartures, DELTA); assertEquals(20.0, nodeStat.maxQueueLength, DELTA);