Plot
- added simple plot - refactored summary of runs
This commit is contained in:
33
pom.xml
33
pom.xml
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>net.berack</groupId>
|
<groupId>net.berack</groupId>
|
||||||
@@ -13,6 +13,30 @@
|
|||||||
<maven.compiler.target>23</maven.compiler.target>
|
<maven.compiler.target>23</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- Compilazione Java con Maven -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>23</source>
|
||||||
|
<target>23</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<!-- Maven plugin per eseguire l'applicazione JavaFX -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-maven-plugin</artifactId>
|
||||||
|
<version>0.0.8</version>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>net.berack.upo.valpre.Main</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
@@ -36,5 +60,10 @@
|
|||||||
<artifactId>kryo</artifactId>
|
<artifactId>kryo</artifactId>
|
||||||
<version>5.6.2</version>
|
<version>5.6.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jfree</groupId>
|
||||||
|
<artifactId>jfreechart</artifactId>
|
||||||
|
<version>1.5.5</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,18 +1,35 @@
|
|||||||
package net.berack.upo.valpre;
|
package net.berack.upo.valpre;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.GridLayout;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.swing.JComboBox;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
import org.jfree.chart.ChartFactory;
|
||||||
|
import org.jfree.chart.ChartPanel;
|
||||||
|
import org.jfree.chart.plot.PlotOrientation;
|
||||||
|
import org.jfree.data.category.DefaultCategoryDataset;
|
||||||
|
|
||||||
import net.berack.upo.valpre.sim.stats.CsvResult;
|
import net.berack.upo.valpre.sim.stats.CsvResult;
|
||||||
import net.berack.upo.valpre.sim.stats.ResultMultiple;
|
import net.berack.upo.valpre.sim.stats.ResultSummary;
|
||||||
|
import net.berack.upo.valpre.sim.stats.Statistics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to plot the results of the simulation.
|
* This class is used to plot the results of the simulation.
|
||||||
* The results are saved in a CSV file and then loaded to be plotted.
|
* The results are saved in a CSV file and then loaded to be plotted.
|
||||||
*/
|
*/
|
||||||
public class Plot {
|
public class Plot {
|
||||||
public final ResultMultiple results;
|
public final ResultSummary summary;
|
||||||
|
private final ChartPanel chartPanel;
|
||||||
|
private final JComboBox<String> nodeComboBox;
|
||||||
|
private final JComboBox<String> statComboBox;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new plot object.
|
* Create a new plot object.
|
||||||
@@ -27,14 +44,78 @@ public class Plot {
|
|||||||
throw new IllegalArgumentException("CSV file needed! Use -csv <file>");
|
throw new IllegalArgumentException("CSV file needed! Use -csv <file>");
|
||||||
|
|
||||||
var results = new CsvResult(file).loadResults();
|
var results = new CsvResult(file).loadResults();
|
||||||
this.results = new ResultMultiple(results);
|
this.summary = new ResultSummary(results);
|
||||||
|
|
||||||
|
var nodes = this.summary.getNodes().toArray(new String[0]);
|
||||||
|
this.chartPanel = new ChartPanel(null);
|
||||||
|
this.nodeComboBox = new JComboBox<>(nodes);
|
||||||
|
this.statComboBox = new JComboBox<>(Statistics.getOrderOfApply());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the plot of the results.
|
* Show the plot of the results.
|
||||||
*/
|
*/
|
||||||
public void show() {
|
public void show() {
|
||||||
// TODO: Use JavaFX to show the plot
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
var nodeLabel = new JLabel("Node: ");
|
||||||
|
var statLabel = new JLabel("Stat: ");
|
||||||
|
|
||||||
|
var filterPanel = new JPanel();
|
||||||
|
filterPanel.setLayout(new GridLayout(2, 2));
|
||||||
|
filterPanel.add(nodeLabel);
|
||||||
|
filterPanel.add(nodeComboBox);
|
||||||
|
filterPanel.add(statLabel);
|
||||||
|
filterPanel.add(statComboBox);
|
||||||
|
|
||||||
|
nodeComboBox.addActionListener(_ -> updateChart());
|
||||||
|
statComboBox.addActionListener(_ -> updateChart());
|
||||||
|
|
||||||
|
var rootPane = new JPanel();
|
||||||
|
rootPane.setLayout(new BorderLayout());
|
||||||
|
rootPane.add(filterPanel, BorderLayout.NORTH);
|
||||||
|
rootPane.add(chartPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
chartPanel.setChart(ChartFactory.createBarChart(
|
||||||
|
"Title",
|
||||||
|
"Run",
|
||||||
|
"Value",
|
||||||
|
null,
|
||||||
|
PlotOrientation.VERTICAL,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false));
|
||||||
|
updateChart();
|
||||||
|
|
||||||
|
var frame = new JFrame("Graph of the Simulation");
|
||||||
|
frame.add(rootPane);
|
||||||
|
frame.setSize(800, 600);
|
||||||
|
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
frame.setVisible(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the chart with the selected node and stat.
|
||||||
|
*/
|
||||||
|
private void updateChart() {
|
||||||
|
try {
|
||||||
|
var node = this.nodeComboBox.getSelectedItem().toString();
|
||||||
|
var stat = this.statComboBox.getSelectedItem().toString();
|
||||||
|
|
||||||
|
var summary = this.summary.getSummaryOf(node, stat);
|
||||||
|
var frequency = summary.getFrequency(20);
|
||||||
|
|
||||||
|
var dataset = new DefaultCategoryDataset();
|
||||||
|
for (int i = 0; i < frequency.length; i++) {
|
||||||
|
dataset.addValue(frequency[i], "Frequency", Integer.valueOf(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
var chart = chartPanel.getChart();
|
||||||
|
chart.getCategoryPlot().setDataset(dataset);
|
||||||
|
chart.setTitle(String.format("Avg %.3f", summary.average));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -54,15 +54,14 @@ public class Simulation {
|
|||||||
var net = Net.load(this.file);
|
var net = Net.load(this.file);
|
||||||
var nano = System.nanoTime();
|
var nano = System.nanoTime();
|
||||||
var sim = new SimulationMultiple(net);
|
var sim = new SimulationMultiple(net);
|
||||||
var results = this.parallel ? sim.runParallel(this.seed, this.runs) : sim.run(this.seed, this.runs);
|
var summary = this.parallel ? sim.runParallel(this.seed, this.runs) : sim.run(this.seed, this.runs);
|
||||||
nano = System.nanoTime() - nano;
|
nano = System.nanoTime() - nano;
|
||||||
|
|
||||||
System.out.print(results.average.getHeader());
|
System.out.print(summary);
|
||||||
System.out.print(results.average.getSummary());
|
|
||||||
System.out.println("Final time " + nano / 1e6 + "ms");
|
System.out.println("Final time " + nano / 1e6 + "ms");
|
||||||
|
|
||||||
if (csv != null) {
|
if (csv != null) {
|
||||||
new CsvResult(this.csv).saveResults(results.runs);
|
new CsvResult(this.csv).saveResults(summary.runs);
|
||||||
System.out.println("Data saved to " + this.csv);
|
System.out.println("Data saved to " + this.csv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import net.berack.upo.valpre.rand.Rng;
|
import net.berack.upo.valpre.rand.Rng;
|
||||||
import net.berack.upo.valpre.sim.stats.ResultMultiple;
|
|
||||||
import net.berack.upo.valpre.sim.stats.Result;
|
import net.berack.upo.valpre.sim.stats.Result;
|
||||||
|
import net.berack.upo.valpre.sim.stats.ResultSummary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A network simulation that uses a discrete event simulation to model the
|
* A network simulation that uses a discrete event simulation to model the
|
||||||
@@ -36,7 +36,7 @@ public class SimulationMultiple {
|
|||||||
* events.
|
* events.
|
||||||
* @return The statistics the network.
|
* @return The statistics the network.
|
||||||
*/
|
*/
|
||||||
public ResultMultiple run(long seed, int runs, EndCriteria... criterias) {
|
public ResultSummary run(long seed, int runs, EndCriteria... criterias) {
|
||||||
var rngs = Rng.getMultipleStreams(seed, runs);
|
var rngs = Rng.getMultipleStreams(seed, runs);
|
||||||
var stats = new Result[runs];
|
var stats = new Result[runs];
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ public class SimulationMultiple {
|
|||||||
var sim = new Simulation(this.net, rngs[i], criterias);
|
var sim = new Simulation(this.net, rngs[i], criterias);
|
||||||
stats[i] = sim.run();
|
stats[i] = sim.run();
|
||||||
}
|
}
|
||||||
return new ResultMultiple(stats);
|
return new ResultSummary(stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,7 +62,7 @@ public class SimulationMultiple {
|
|||||||
* @throws InterruptedException If the threads are interrupted.
|
* @throws InterruptedException If the threads are interrupted.
|
||||||
* @throws ExecutionException If the one of the threads has been aborted.
|
* @throws ExecutionException If the one of the threads has been aborted.
|
||||||
*/
|
*/
|
||||||
public ResultMultiple runParallel(long seed, int runs, EndCriteria... criterias)
|
public ResultSummary runParallel(long seed, int runs, EndCriteria... criterias)
|
||||||
throws InterruptedException, ExecutionException {
|
throws InterruptedException, ExecutionException {
|
||||||
var rngs = Rng.getMultipleStreams(seed, runs);
|
var rngs = Rng.getMultipleStreams(seed, runs);
|
||||||
var results = new Result[runs];
|
var results = new Result[runs];
|
||||||
@@ -82,7 +82,7 @@ public class SimulationMultiple {
|
|||||||
futures[i].get();
|
futures[i].get();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ResultMultiple(results);
|
return new ResultSummary(results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ public class Result {
|
|||||||
public final long seed;
|
public final long seed;
|
||||||
public final double simulationTime;
|
public final double simulationTime;
|
||||||
public final double timeElapsedMS;
|
public final double timeElapsedMS;
|
||||||
private int size;
|
|
||||||
private String iFormat;
|
|
||||||
private String fFormat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new result object for the given parameters obtained by the
|
* Creates a new result object for the given parameters obtained by the
|
||||||
@@ -31,53 +28,5 @@ public class Result {
|
|||||||
this.simulationTime = time;
|
this.simulationTime = time;
|
||||||
this.timeElapsedMS = elapsed;
|
this.timeElapsedMS = elapsed;
|
||||||
this.nodes = nodes;
|
this.nodes = nodes;
|
||||||
this.size = (int) Math.ceil(Math.max(Math.log10(this.simulationTime), 1));
|
|
||||||
this.iFormat = "%" + this.size + ".0f";
|
|
||||||
this.fFormat = "%" + (this.size + 4) + ".3f";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the global information of the simulation. In particular this method build
|
|
||||||
* a string that contains the seed and the time elapsed in the simulation and in
|
|
||||||
* real time
|
|
||||||
*
|
|
||||||
* @return a string with the info
|
|
||||||
*/
|
|
||||||
public String getHeader() {
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
builder.append("===== Net Stats =====\n");
|
|
||||||
builder.append(String.format("Seed: \t%d\n", this.seed));
|
|
||||||
builder.append(String.format("Simulation: \t" + fFormat + "\n", this.simulationTime));
|
|
||||||
builder.append(String.format("Elapsed: \t" + fFormat + "ms\n", this.timeElapsedMS / 1e6));
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print a summary of the statistics to the console.
|
|
||||||
* The summary includes all the statistics of nodes and for each it displays the
|
|
||||||
* departures, queue, wait, response, throughput, utilization, unavailability
|
|
||||||
* and the last event time.
|
|
||||||
*
|
|
||||||
* @return a string with all the stats
|
|
||||||
*/
|
|
||||||
public String getSummary() {
|
|
||||||
String[] h = { "Node", "Departures", "Avg Queue", "Avg Wait", "Avg Response", "Throughput", "Utilization %",
|
|
||||||
"Unavailable %", "Last Event" };
|
|
||||||
var table = new ConsoleTable(h);
|
|
||||||
|
|
||||||
for (var entry : this.nodes.entrySet()) {
|
|
||||||
var stats = entry.getValue();
|
|
||||||
table.addRow(
|
|
||||||
entry.getKey(),
|
|
||||||
iFormat.formatted(stats.numDepartures),
|
|
||||||
fFormat.formatted(stats.avgQueueLength),
|
|
||||||
fFormat.formatted(stats.avgWaitTime),
|
|
||||||
fFormat.formatted(stats.avgResponse),
|
|
||||||
fFormat.formatted(stats.troughput),
|
|
||||||
fFormat.formatted(stats.utilization * 100),
|
|
||||||
fFormat.formatted(stats.unavailable * 100),
|
|
||||||
fFormat.formatted(stats.lastEventTime));
|
|
||||||
}
|
|
||||||
return table.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
package net.berack.upo.valpre.sim.stats;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represent the result of multiple runs of simulation.
|
|
||||||
*/
|
|
||||||
public class ResultMultiple {
|
|
||||||
public final Result[] runs;
|
|
||||||
public final Result average;
|
|
||||||
public final Result variance;
|
|
||||||
public final Result error95;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This has all the result and give some statistics about the runs.
|
|
||||||
* The object created has the average, the variance, and the error95.
|
|
||||||
* The runs must be an array of at least 2 run result otherwise an exception is
|
|
||||||
* thrown.
|
|
||||||
*
|
|
||||||
* @param runs an array of run result
|
|
||||||
* @throws IllegalArgumentException if the runs is null or if has a len <= 1
|
|
||||||
*/
|
|
||||||
public ResultMultiple(Result... runs) {
|
|
||||||
if (runs == null || runs.length <= 1)
|
|
||||||
throw new IllegalArgumentException("Sample size must be > 1");
|
|
||||||
|
|
||||||
this.runs = runs;
|
|
||||||
this.average = ResultMultiple.calcAvg(runs);
|
|
||||||
this.variance = ResultMultiple.calcStdDev(this.average, runs);
|
|
||||||
this.error95 = calcError(this.average, this.variance, runs.length, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method calculate the average of the runs result.
|
|
||||||
* The average is calculated for each node.
|
|
||||||
*
|
|
||||||
* @param runs the run to calculate
|
|
||||||
* @return the average of the runs
|
|
||||||
*/
|
|
||||||
public static Result calcAvg(Result... runs) {
|
|
||||||
var avgTime = 0.0d;
|
|
||||||
var avgElapsed = 0L;
|
|
||||||
var nodes = new HashMap<String, Statistics>();
|
|
||||||
|
|
||||||
for (var run : runs) {
|
|
||||||
avgTime += run.simulationTime;
|
|
||||||
avgElapsed += run.timeElapsedMS;
|
|
||||||
|
|
||||||
for (var entry : run.nodes.entrySet()) {
|
|
||||||
var stats = nodes.computeIfAbsent(entry.getKey(), _ -> new Statistics());
|
|
||||||
stats.merge(entry.getValue(), (val1, val2) -> val1 + val2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
avgTime /= runs.length;
|
|
||||||
avgElapsed /= runs.length;
|
|
||||||
for (var stat : nodes.values())
|
|
||||||
stat.apply(val -> val / runs.length);
|
|
||||||
return new Result(runs[0].seed, avgTime, avgElapsed, nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method calculate the standard deviation of the runs result.
|
|
||||||
* The standard deviation is calculated for each node.
|
|
||||||
*
|
|
||||||
* @param avg the average of the runs. {@link #calcAvg(Result...)}
|
|
||||||
* @param runs the run to calculate
|
|
||||||
* @return the standard deviation of the runs
|
|
||||||
*/
|
|
||||||
public static Result calcStdDev(Result avg, Result... runs) {
|
|
||||||
var time = 0.0d;
|
|
||||||
var elapsed = 0.0d;
|
|
||||||
var nodes = new HashMap<String, Statistics>();
|
|
||||||
|
|
||||||
for (var run : runs) {
|
|
||||||
time += Math.pow(run.simulationTime - avg.simulationTime, 2);
|
|
||||||
elapsed += Math.pow(run.timeElapsedMS - avg.simulationTime, 2);
|
|
||||||
|
|
||||||
for (var entry : run.nodes.entrySet()) {
|
|
||||||
var stat = nodes.computeIfAbsent(entry.getKey(), _ -> new Statistics());
|
|
||||||
var average = avg.nodes.get(entry.getKey());
|
|
||||||
var other = entry.getValue();
|
|
||||||
var temp = new Statistics();
|
|
||||||
Statistics.apply(temp, other, average, (o, a) -> Math.pow(o - a, 2));
|
|
||||||
stat.merge(temp, (var1, var2) -> var1 + var2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time = Math.sqrt(time / runs.length - 1);
|
|
||||||
elapsed = Math.sqrt(elapsed / runs.length - 1);
|
|
||||||
for (var stat : nodes.values())
|
|
||||||
stat.apply(val -> Math.sqrt(val / (runs.length - 1)));
|
|
||||||
|
|
||||||
return new Result(runs[0].seed, time, elapsed, nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the error at the selected alpha level.
|
|
||||||
* This method computes the error margin for the provided average and standard
|
|
||||||
* deviation values,
|
|
||||||
* considering the sample size and the confidence level (alpha).
|
|
||||||
* The result is adjusted using a t-distribution to account for the variability
|
|
||||||
* in smaller sample sizes.
|
|
||||||
*
|
|
||||||
* @param avg The average of the results, typically computed using
|
|
||||||
* {@link #calcAvg(Result...)}.
|
|
||||||
* @param stdDev The standard deviation of the results, typically computed
|
|
||||||
* using {@link #calcVar(Result, Result...)}.
|
|
||||||
* @param sampleSize The number of runs or samples used.
|
|
||||||
* @param alpha The significance level (probability) used for the
|
|
||||||
* t-distribution. A value of 0.95 for a 95% confidence level.
|
|
||||||
* @return The calculated error.
|
|
||||||
*/
|
|
||||||
public static Result calcError(Result avg, Result stdDev, int sampleSize, double alpha) {
|
|
||||||
// Getting the correct values for the percentile
|
|
||||||
var distr = new org.apache.commons.math3.distribution.TDistribution(sampleSize - 1);
|
|
||||||
var percentile = distr.inverseCumulativeProbability(alpha);
|
|
||||||
|
|
||||||
// Calculating the error
|
|
||||||
var sqrtSample = Math.sqrt(sampleSize);
|
|
||||||
var error = new Result(avg.seed,
|
|
||||||
percentile * (stdDev.simulationTime / sqrtSample),
|
|
||||||
percentile * (stdDev.timeElapsedMS / sqrtSample),
|
|
||||||
new HashMap<>());
|
|
||||||
for (var entry : stdDev.nodes.entrySet()) {
|
|
||||||
var stat = new Statistics();
|
|
||||||
stat.merge(entry.getValue(), (_, val) -> percentile * (val / sqrtSample));
|
|
||||||
error.nodes.put(entry.getKey(), stat);
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
128
src/main/java/net/berack/upo/valpre/sim/stats/ResultSummary.java
Normal file
128
src/main/java/net/berack/upo/valpre/sim/stats/ResultSummary.java
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package net.berack.upo.valpre.sim.stats;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represent the summary of the result of multiple runs of
|
||||||
|
* simulation. It has the average of the simulation time, the average of the
|
||||||
|
* elapsed time, and the average of the statistics of the nodes.
|
||||||
|
*/
|
||||||
|
public class ResultSummary {
|
||||||
|
|
||||||
|
public final long seed;
|
||||||
|
public final double simulationTime;
|
||||||
|
public final double timeElapsedMS;
|
||||||
|
public final Result[] runs;
|
||||||
|
|
||||||
|
private final Map<String, Map<String, StatisticsSummary>> stats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This has all the result and give some statistics about the runs.
|
||||||
|
* The object created has the average, the variance, and the error95.
|
||||||
|
* The runs must be an array of at least 2 run result otherwise an exception is
|
||||||
|
* thrown.
|
||||||
|
*
|
||||||
|
* @param runs an array of run result
|
||||||
|
* @throws IllegalArgumentException if the runs is null or if has a len <= 1
|
||||||
|
*/
|
||||||
|
public ResultSummary(Result[] runs) {
|
||||||
|
if (runs == null || runs.length <= 1)
|
||||||
|
throw new IllegalArgumentException("Sample size must be > 1");
|
||||||
|
|
||||||
|
// Get the seed, simulation time, and time elapsed
|
||||||
|
var avgTime = 0.0d;
|
||||||
|
var avgElapsed = 0L;
|
||||||
|
for (var run : runs) {
|
||||||
|
avgTime += run.simulationTime;
|
||||||
|
avgElapsed += run.timeElapsedMS;
|
||||||
|
}
|
||||||
|
this.runs = runs;
|
||||||
|
this.seed = runs[0].seed;
|
||||||
|
this.simulationTime = avgTime / runs.length;
|
||||||
|
this.timeElapsedMS = avgElapsed / runs.length;
|
||||||
|
|
||||||
|
// Get the statistics of the nodes
|
||||||
|
var nodeStats = new HashMap<String, Statistics[]>();
|
||||||
|
for (var i = 0; i < runs.length; i++) {
|
||||||
|
for (var entry : runs[i].nodes.entrySet()) {
|
||||||
|
var node = entry.getKey();
|
||||||
|
var stats = nodeStats.computeIfAbsent(node, _ -> new Statistics[runs.length]);
|
||||||
|
stats[i] = entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the summary of the statistics of the nodes
|
||||||
|
this.stats = new HashMap<>();
|
||||||
|
for (var entry : nodeStats.entrySet()) {
|
||||||
|
var node = entry.getKey();
|
||||||
|
var summary = StatisticsSummary.getSummary(entry.getValue());
|
||||||
|
this.stats.put(node, summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the summary of the statistics of a node.
|
||||||
|
*
|
||||||
|
* @param node the node to get the summary
|
||||||
|
* @param stat the statistic to get the summary
|
||||||
|
* @return the summary of the statistics of the node
|
||||||
|
*/
|
||||||
|
public StatisticsSummary getSummaryOf(String node, String stat) {
|
||||||
|
return this.stats.get(node).get(stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the summary of the statistics of a node.
|
||||||
|
*
|
||||||
|
* @param node the node to get the summary
|
||||||
|
* @return the summary of the statistics of the node
|
||||||
|
*/
|
||||||
|
public Map<String, StatisticsSummary> getSummaryOf(String node) {
|
||||||
|
return this.stats.get(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the nodes of the simulation.
|
||||||
|
*
|
||||||
|
* @return the nodes of the simulation
|
||||||
|
*/
|
||||||
|
public Collection<String> getNodes() {
|
||||||
|
return this.stats.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
var size = (int) Math.ceil(Math.max(Math.log10(this.simulationTime), 1));
|
||||||
|
var iFormat = "%" + size + ".0f";
|
||||||
|
var fFormat = "%" + (size + 4) + ".3f";
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.append("===== Net Stats =====\n");
|
||||||
|
builder.append(String.format("Seed: \t%d\n", this.seed));
|
||||||
|
builder.append(String.format("Simulation: \t" + fFormat + "\n", this.simulationTime));
|
||||||
|
builder.append(String.format("Elapsed: \t" + fFormat + "ms\n", this.timeElapsedMS / 1e6));
|
||||||
|
// return builder.toString();
|
||||||
|
|
||||||
|
var table = new ConsoleTable("Node", "Departures", "Avg Queue", "Avg Wait", "Avg Response", "Throughput",
|
||||||
|
"Utilization %", "Unavailable %", "Last Event");
|
||||||
|
|
||||||
|
for (var entry : this.stats.entrySet()) {
|
||||||
|
var stats = entry.getValue();
|
||||||
|
table.addRow(
|
||||||
|
entry.getKey(),
|
||||||
|
iFormat.formatted(stats.get("numDepartures").average),
|
||||||
|
fFormat.formatted(stats.get("avgQueueLength").average),
|
||||||
|
fFormat.formatted(stats.get("avgWaitTime").average),
|
||||||
|
fFormat.formatted(stats.get("avgResponse").average),
|
||||||
|
fFormat.formatted(stats.get("troughput").average),
|
||||||
|
fFormat.formatted(stats.get("utilization").average * 100),
|
||||||
|
fFormat.formatted(stats.get("unavailable").average * 100),
|
||||||
|
fFormat.formatted(stats.get("lastEventTime").average));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(table);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package net.berack.upo.valpre.sim.stats;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A summary of the values.
|
||||||
|
*/
|
||||||
|
public class StatisticsSummary {
|
||||||
|
public final String name;
|
||||||
|
public final double average;
|
||||||
|
public final double median;
|
||||||
|
public final double min;
|
||||||
|
public final double max;
|
||||||
|
public final double stdDev;
|
||||||
|
public final double error95;
|
||||||
|
public final double[] values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a summary of the values.
|
||||||
|
* This method calculates the average, median, minimum, maximum, standard
|
||||||
|
* deviation, and error at the 95% confidence level of the provided values.
|
||||||
|
* The values are sorted before calculating the summary.
|
||||||
|
*
|
||||||
|
* @param values the values to summarize
|
||||||
|
*/
|
||||||
|
public StatisticsSummary(String name, double[] values) {
|
||||||
|
if (values == null || values.length < 2)
|
||||||
|
throw new IllegalArgumentException("The values array must have at least two elements.");
|
||||||
|
|
||||||
|
Arrays.sort(values);
|
||||||
|
var sum = Arrays.stream(values).sum();
|
||||||
|
var avg = sum / values.length;
|
||||||
|
var median = values.length / 2;
|
||||||
|
var varianceSum = Arrays.stream(values).map(value -> Math.pow(value - avg, 2)).sum();
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.values = values;
|
||||||
|
this.average = avg;
|
||||||
|
this.stdDev = Math.sqrt(varianceSum / values.length);
|
||||||
|
this.median = values.length % 2 == 0 ? (values[median - 1] + values[median]) / 2.0 : values[median];
|
||||||
|
this.min = values[0];
|
||||||
|
this.max = values[values.length - 1];
|
||||||
|
this.error95 = this.calcError(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the error at the selected alpha level.
|
||||||
|
* This method computes the error for the average and standard deviation values,
|
||||||
|
* considering the sample size and the confidence level (alpha).
|
||||||
|
* The result is adjusted using a t-distribution to account for the variability
|
||||||
|
* in smaller sample sizes.
|
||||||
|
*
|
||||||
|
* @param alpha the alpha value
|
||||||
|
* @return the error of the values
|
||||||
|
*/
|
||||||
|
public double calcError(double alpha) {
|
||||||
|
var sampleSize = this.values.length;
|
||||||
|
var distr = new org.apache.commons.math3.distribution.TDistribution(sampleSize - 1);
|
||||||
|
var percentile = distr.inverseCumulativeProbability(alpha);
|
||||||
|
|
||||||
|
return percentile * (this.stdDev / Math.sqrt(sampleSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the frequency of the values in the array.
|
||||||
|
*
|
||||||
|
* @param numBins the number of bins to use
|
||||||
|
* @return an array with the frequency of the values
|
||||||
|
*/
|
||||||
|
public int[] getFrequency(int numBins) {
|
||||||
|
var buckets = new int[numBins];
|
||||||
|
var range = this.max - this.min;
|
||||||
|
var step = numBins / range;
|
||||||
|
|
||||||
|
for (var value : this.values) {
|
||||||
|
var index = (int) Math.floor((value - this.min) * step);
|
||||||
|
index = Math.min(index, numBins - 1);
|
||||||
|
buckets[index] += 1;
|
||||||
|
}
|
||||||
|
return buckets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a summary of the statistics.
|
||||||
|
*
|
||||||
|
* @param stats the statistics to summarize
|
||||||
|
* @return a map with the summary of the statistics
|
||||||
|
* @throws IllegalArgumentException if the fields of the statistics cannot be
|
||||||
|
* accessed
|
||||||
|
*/
|
||||||
|
public static Map<String, StatisticsSummary> getSummary(Statistics[] stats) throws IllegalArgumentException {
|
||||||
|
try {
|
||||||
|
var map = new HashMap<String, StatisticsSummary>();
|
||||||
|
|
||||||
|
for (var field : Statistics.class.getFields()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
|
||||||
|
var values = new double[stats.length];
|
||||||
|
for (var i = 0; i < stats.length; i++)
|
||||||
|
values[i] = field.getDouble(stats[i]);
|
||||||
|
|
||||||
|
var name = field.getName();
|
||||||
|
map.put(name, new StatisticsSummary(name, values));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
} catch (IllegalAccessException e) { // This should not happen normally, but it is better to catch it
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new IllegalArgumentException("Cannot access the fields of the statistics.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user