- simulation result handling to use builder pattern
- result to use only arrays instead of maps
- updated references to other classes
This commit is contained in:
2025-02-12 14:33:32 +01:00
parent e232df7c5a
commit ffdb3021fd
6 changed files with 210 additions and 85 deletions

View File

@@ -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<String, NodeStats>();
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();
}
/**

View File

@@ -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<String>();
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++) {

View File

@@ -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<Result>();
try (var scan = new Scanner(input)) {
var headerOrder = CsvResult.extractHeaderPositions(scan.nextLine());
var nodes = new HashMap<String, NodeStats>();
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;
}

View File

@@ -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<Entry<String, NodeStats>> {
public final Map<String, NodeStats> 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<Entry<String, NodeStats>> {
* @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<String, NodeStats> 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<Entry<String, NodeStats>> iterator() {
return this.nodes.entrySet().iterator();
public Iterator<Entry<String, NodeStats>> iterator() {
return new Iterator<>() {
private int index = 0;
@Override
public boolean hasNext() {
return this.index < Result.this.nodes.length;
}
@Override
public Entry<String, NodeStats> 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<String, NodeStats> 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<Entry<String, NodeStats>> {
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<Entry<String, NodeStats>> {
public final long seed;
private double avgSimulationTime = 0.0d;
private double avgTimeElapsedMS = 0.0d;
private Map<String, NodeStats.Summary> stats = new HashMap<>();
private String[] nodes;
private NodeStats.Summary[] stats;
private List<Result> runs = new ArrayList<>();
/**
@@ -95,8 +132,9 @@ public class Result implements Iterable<Entry<String, NodeStats>> {
*
* @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<Entry<String, NodeStats>> {
* @param results the results to summarize
*/
public Summary(List<Result> 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<Entry<String, NodeStats>> {
*
* @return the nodes of the summary
*/
public Collection<String> getNodes() {
return this.stats.keySet();
public List<String> getNodes() {
return List.of(this.nodes);
}
/**
@@ -168,10 +222,10 @@ public class Result implements Iterable<Entry<String, NodeStats>> {
* @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<Entry<String, NodeStats>> {
@Override
public String toString() {
var stats = new HashMap<String, NodeStats>();
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<String> nodes = new ArrayList<>();
private List<NodeStats> 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);
}
}
}

View File

@@ -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<Long>();
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);
}

View File

@@ -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);