Update simulation result handling and improve result processing
This commit is contained in:
@@ -45,7 +45,7 @@ I percorsi che invece sono direttamente responsabili per la simulazione sono:
|
|||||||
- **EndCriteria** interfaccia che viene implementata dalle classi interne usata per controllare se e quando la simulazione debba finire.
|
- **EndCriteria** interfaccia che viene implementata dalle classi interne usata per controllare se e quando la simulazione debba finire.
|
||||||
- **Simulation** e **SimulationMultiple** che vengono usate per far partire la simulazione; la versione multiple serve ad organizzare molteplici simulazioni su più thread o su un singolo core.
|
- **Simulation** e **SimulationMultiple** che vengono usate per far partire la simulazione; la versione multiple serve ad organizzare molteplici simulazioni su più thread o su un singolo core.
|
||||||
- [net.berack.upo.valpre.sim.stats](https://github.com/Berack96/upo-valpre/tree/main/src/main/java/net/berack/upo/valpre/sim/stats) Package che contiene tutte le classi utili per la raccolta e l'analisi statistica dei vari valori generati dalla simulazione:
|
- [net.berack.upo.valpre.sim.stats](https://github.com/Berack96/upo-valpre/tree/main/src/main/java/net/berack/upo/valpre/sim/stats) Package che contiene tutte le classi utili per la raccolta e l'analisi statistica dei vari valori generati dalla simulazione:
|
||||||
- **Result** il risultato di una run e la sua controparte **ResultSummary** che contiene molteplici risultati di run già analizzati.
|
- **Result** il risultato di una run e la sua classe interna **Result.Summary** che contiene molteplici risultati di run già analizzati.
|
||||||
- **NodeStats** contiene indici statistici di un nodo e la sua controparte **StatisticsSummary** che contiene molteplici indici statistici già analizzati.
|
- **NodeStats** contiene indici statistici di un nodo e la sua classe interna **NodeStats.Summary** che contiene molteplici indici statistici già analizzati.
|
||||||
- **ConsoleTable** utile per mostrare i risultati in console sottoforma di tabella
|
- **ConsoleTable** utile per mostrare i risultati in console sottoforma di tabella
|
||||||
- **CsvResult** utile per la lettura/scrittura dei risultati in formato csv
|
- **CsvResult** utile per la lettura/scrittura dei risultati in formato csv
|
||||||
|
|||||||
@@ -21,15 +21,15 @@ import org.jfree.chart.plot.PlotOrientation;
|
|||||||
import org.jfree.data.category.DefaultCategoryDataset;
|
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.ResultSummary;
|
|
||||||
import net.berack.upo.valpre.sim.stats.NodeStats;
|
import net.berack.upo.valpre.sim.stats.NodeStats;
|
||||||
|
import net.berack.upo.valpre.sim.stats.Result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 ResultSummary summary;
|
public final Result.Summary summary;
|
||||||
private final ChartPanel panelBarChart;
|
private final ChartPanel panelBarChart;
|
||||||
private final JComboBox<String> nodeComboBox;
|
private final JComboBox<String> nodeComboBox;
|
||||||
private final JList<JListEntry> statList;
|
private final JList<JListEntry> statList;
|
||||||
@@ -47,7 +47,7 @@ public class Plot {
|
|||||||
var results = CsvResult.loadResults(stream);
|
var results = CsvResult.loadResults(stream);
|
||||||
stream.close();
|
stream.close();
|
||||||
|
|
||||||
this.summary = new ResultSummary(results);
|
this.summary = new Result.Summary(results);
|
||||||
|
|
||||||
var nodes = this.summary.getNodes().toArray(new String[0]);
|
var nodes = this.summary.getNodes().toArray(new String[0]);
|
||||||
this.panelBarChart = new ChartPanel(null);
|
this.panelBarChart = new ChartPanel(null);
|
||||||
@@ -132,13 +132,14 @@ public class Plot {
|
|||||||
var stat = this.statList.getSelectedValue().name.getText();
|
var stat = this.statList.getSelectedValue().name.getText();
|
||||||
|
|
||||||
var summary = this.summary.getSummaryOf(node);
|
var summary = this.summary.getSummaryOf(node);
|
||||||
var statSummary = summary.get(stat);
|
var frequency = summary.getFrequency(15, n -> n.of(stat));
|
||||||
var frequency = statSummary.getFrequency(15);
|
var min = summary.min.of(stat);
|
||||||
|
var max = summary.max.of(stat);
|
||||||
|
|
||||||
var dataset = new DefaultCategoryDataset();
|
var dataset = new DefaultCategoryDataset();
|
||||||
var bucket = (statSummary.max - statSummary.min) / frequency.length;
|
var bucket = (max - min) / frequency.length;
|
||||||
for (int i = 0; i < frequency.length; i++) {
|
for (int i = 0; i < frequency.length; i++) {
|
||||||
var columnVal = statSummary.min + i * bucket;
|
var columnVal = min + i * bucket;
|
||||||
var columnKey = String.format("%.3f", columnVal);
|
var columnKey = String.format("%.3f", columnVal);
|
||||||
dataset.addValue(frequency[i], "Frequency", columnKey);
|
dataset.addValue(frequency[i], "Frequency", columnKey);
|
||||||
}
|
}
|
||||||
@@ -147,10 +148,14 @@ public class Plot {
|
|||||||
chart.setTitle(stat + " distribution");
|
chart.setTitle(stat + " distribution");
|
||||||
|
|
||||||
var model = this.statList.getModel();
|
var model = this.statList.getModel();
|
||||||
|
var avg = summary.average;
|
||||||
|
var err = summary.calcError(0.95);
|
||||||
|
|
||||||
for (int i = 0; i < model.getSize(); i++) {
|
for (int i = 0; i < model.getSize(); i++) {
|
||||||
var entry = model.getElementAt(i);
|
var entry = model.getElementAt(i);
|
||||||
var value = summary.get(entry.name.getText());
|
var value = entry.name.getText();
|
||||||
entry.value.setText(String.format("%8.3f ±% 9.3f", value.average, value.calcError(0.95)));
|
|
||||||
|
entry.value.setText(String.format("%8.3f ±% 9.3f", avg.of(value), err.of(value)));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public class SimulationBuilder {
|
|||||||
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(summary.runs);
|
new CsvResult(this.csv).saveResults(summary.getRuns());
|
||||||
System.out.println("Data saved to " + this.csv);
|
System.out.println("Data saved to " + this.csv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ public final class Simulation {
|
|||||||
for (var entry : this.states.entrySet())
|
for (var entry : this.states.entrySet())
|
||||||
nodes.put(entry.getKey(), entry.getValue().stats);
|
nodes.put(entry.getKey(), entry.getValue().stats);
|
||||||
|
|
||||||
return new Result(this.seed, this.time, elapsed, nodes);
|
return new Result(this.seed, this.time, elapsed * 1e-6, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ 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.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,15 +35,16 @@ public class SimulationMultiple {
|
|||||||
* events.
|
* events.
|
||||||
* @return The statistics the network.
|
* @return The statistics the network.
|
||||||
*/
|
*/
|
||||||
public ResultSummary run(long seed, int runs, EndCriteria... criterias) {
|
public Result.Summary 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 result = new Result.Summary(rngs[0].getSeed());
|
||||||
|
|
||||||
for (int i = 0; i < runs; i++) {
|
for (int i = 0; i < runs; i++) {
|
||||||
var sim = new Simulation(this.net, rngs[i], criterias);
|
var sim = new Simulation(this.net, rngs[i], criterias);
|
||||||
stats[i] = sim.run();
|
var res = sim.run();
|
||||||
|
result.add(res);
|
||||||
}
|
}
|
||||||
return new ResultSummary(stats);
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,27 +62,29 @@ 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 ResultSummary runParallel(long seed, int runs, EndCriteria... criterias)
|
public Result.Summary 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 futures = new Future[runs];
|
var futures = new Future[runs];
|
||||||
|
|
||||||
var numThreads = Math.min(runs, Runtime.getRuntime().availableProcessors());
|
var numThreads = Math.min(runs, Runtime.getRuntime().availableProcessors());
|
||||||
try (var threads = Executors.newFixedThreadPool(numThreads)) {
|
try (var threads = Executors.newFixedThreadPool(numThreads)) {
|
||||||
|
var results = new Result.Summary(rngs[0].getSeed());
|
||||||
|
|
||||||
for (int i = 0; i < runs; i++) {
|
for (int i = 0; i < runs; i++) {
|
||||||
final var id = i;
|
final var id = i;
|
||||||
futures[i] = threads.submit(() -> {
|
futures[i] = threads.submit(() -> {
|
||||||
var sim = new Simulation(this.net, rngs[id], criterias);
|
var sim = new Simulation(this.net, rngs[id], criterias);
|
||||||
results[id] = sim.run();
|
return sim.run();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < runs; i++) {
|
for (var i = 0; i < runs; i++) {
|
||||||
futures[i].get();
|
var res = (Result) futures[i].get();
|
||||||
|
results.add(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ResultSummary(results);
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.io.InputStream;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ public class CsvResult {
|
|||||||
*
|
*
|
||||||
* @throws IOException if anything happens wile wriiting to the file
|
* @throws IOException if anything happens wile wriiting to the file
|
||||||
*/
|
*/
|
||||||
public void saveResults(Result[] results) throws IOException {
|
public void saveResults(List<Result> results) throws IOException {
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.append("seed,node,");
|
builder.append("seed,node,");
|
||||||
builder.append(String.join(",", NodeStats.getOrderOfApply()));
|
builder.append(String.join(",", NodeStats.getOrderOfApply()));
|
||||||
@@ -59,7 +60,7 @@ public class CsvResult {
|
|||||||
* @return the results loaded from the file
|
* @return the results loaded from the file
|
||||||
* @throws IOException if anything happens while reading the file
|
* @throws IOException if anything happens while reading the file
|
||||||
*/
|
*/
|
||||||
public Result[] loadResults() throws IOException {
|
public List<Result> loadResults() throws IOException {
|
||||||
try (var stream = new FileInputStream(this.file)) {
|
try (var stream = new FileInputStream(this.file)) {
|
||||||
return CsvResult.loadResults(stream);
|
return CsvResult.loadResults(stream);
|
||||||
}
|
}
|
||||||
@@ -71,7 +72,7 @@ public class CsvResult {
|
|||||||
* @param input the input stream to read
|
* @param input the input stream to read
|
||||||
* @return the results loaded from the stream
|
* @return the results loaded from the stream
|
||||||
*/
|
*/
|
||||||
public static Result[] loadResults(InputStream input) {
|
public static List<Result> loadResults(InputStream input) {
|
||||||
var results = new ArrayList<Result>();
|
var results = new ArrayList<Result>();
|
||||||
try (var scan = new Scanner(input)) {
|
try (var scan = new Scanner(input)) {
|
||||||
var _ = scan.nextLine();
|
var _ = scan.nextLine();
|
||||||
@@ -85,7 +86,7 @@ public class CsvResult {
|
|||||||
var node = line[1];
|
var node = line[1];
|
||||||
|
|
||||||
if (currentSeed != seed && seed != 0) {
|
if (currentSeed != seed && seed != 0) {
|
||||||
results.add(new Result(seed, 0.0, 0L, nodes));
|
results.add(new Result(seed, 0.0, 0.0, nodes));
|
||||||
nodes = new HashMap<>();
|
nodes = new HashMap<>();
|
||||||
}
|
}
|
||||||
seed = currentSeed;
|
seed = currentSeed;
|
||||||
@@ -95,9 +96,9 @@ public class CsvResult {
|
|||||||
nodes.put(node, stats);
|
nodes.put(node, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
results.add(new Result(seed, 0.0, 0L, nodes));
|
results.add(new Result(seed, 0.0, 0.0, nodes));
|
||||||
}
|
}
|
||||||
return results.toArray(new Result[0]);
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package net.berack.upo.valpre.sim.stats;
|
package net.berack.upo.valpre.sim.stats;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.apache.commons.math3.distribution.TDistribution;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class keeps track of various statistical metrics related to the net
|
* This class keeps track of various statistical metrics related to the net
|
||||||
* performance, such as the number of arrivals and departures, queue lengths,
|
* performance, such as the number of arrivals and departures, queue lengths,
|
||||||
@@ -10,7 +14,7 @@ import java.util.function.Function;
|
|||||||
* statistics are updated during simulation events, such as arrivals and
|
* statistics are updated during simulation events, such as arrivals and
|
||||||
* departures, and can be used to analyze the net's behavior and performance.
|
* departures, and can be used to analyze the net's behavior and performance.
|
||||||
*/
|
*/
|
||||||
public class NodeStats {
|
public class NodeStats implements Cloneable {
|
||||||
public double numArrivals = 0.0d;
|
public double numArrivals = 0.0d;
|
||||||
public double numDepartures = 0.0d;
|
public double numDepartures = 0.0d;
|
||||||
public double maxQueueLength = 0.0d;
|
public double maxQueueLength = 0.0d;
|
||||||
@@ -97,8 +101,8 @@ public class NodeStats {
|
|||||||
*
|
*
|
||||||
* @param func a function to apply
|
* @param func a function to apply
|
||||||
*/
|
*/
|
||||||
public void apply(Function<Double, Double> func) {
|
public NodeStats apply(Function<Double, Double> func) {
|
||||||
NodeStats.operation(this, this, this, (val1, _) -> func.apply(val1));
|
return NodeStats.operation(this, this, this, (val1, _) -> func.apply(val1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,8 +113,44 @@ public class NodeStats {
|
|||||||
* @param other
|
* @param other
|
||||||
* @param func
|
* @param func
|
||||||
*/
|
*/
|
||||||
public void merge(NodeStats other, BiFunction<Double, Double, Double> func) {
|
public NodeStats merge(NodeStats other, BiFunction<Double, Double, Double> func) {
|
||||||
NodeStats.operation(this, this, other, func);
|
return NodeStats.operation(this, this, other, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodeStats clone() {
|
||||||
|
try {
|
||||||
|
return (NodeStats) super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the stat.
|
||||||
|
*
|
||||||
|
* @param statName the name of the stat
|
||||||
|
* @return the value of the stat
|
||||||
|
*/
|
||||||
|
public double of(String statName) {
|
||||||
|
return switch (statName) {
|
||||||
|
case "numArrivals" -> this.numArrivals;
|
||||||
|
case "numDepartures" -> this.numDepartures;
|
||||||
|
case "maxQueueLength" -> this.maxQueueLength;
|
||||||
|
case "avgQueueLength" -> this.avgQueueLength;
|
||||||
|
case "avgWaitTime" -> this.avgWaitTime;
|
||||||
|
case "avgResponse" -> this.avgResponse;
|
||||||
|
case "busyTime" -> this.busyTime;
|
||||||
|
case "waitTime" -> this.waitTime;
|
||||||
|
case "unavailableTime" -> this.unavailableTime;
|
||||||
|
case "responseTime" -> this.responseTime;
|
||||||
|
case "lastEventTime" -> this.lastEventTime;
|
||||||
|
case "throughput" -> this.throughput;
|
||||||
|
case "utilization" -> this.utilization;
|
||||||
|
case "unavailable" -> this.unavailable;
|
||||||
|
default -> throw new IllegalArgumentException("Invalid stat name");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,27 +164,6 @@ public class NodeStats {
|
|||||||
"unavailable" };
|
"unavailable" };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges two sets of statistics into a new one.
|
|
||||||
* This method combines the statistics from two `Statistics` objects (`val1` and
|
|
||||||
* `val2`) and returns a new `Statistics` object with the merged results. The
|
|
||||||
* provided function is applied to each pair of corresponding statistics from
|
|
||||||
* `val1` and `val2` to compute the merged value. This is useful for merging or
|
|
||||||
* combining statistics from different sources (e.g., different simulation
|
|
||||||
* runs), allowing the creation of aggregated statistics.
|
|
||||||
*
|
|
||||||
* @param val1 The first `Statistics` object to merge.
|
|
||||||
* @param val2 The second `Statistics` object to merge.
|
|
||||||
* @param func The binary function that defines how to merge each pair of values
|
|
||||||
* from `val1` and `val2`. It takes two `Double` values (from `val1`
|
|
||||||
* and `val2`) and returns a new `Double` value.
|
|
||||||
*/
|
|
||||||
public static NodeStats merge(NodeStats val1, NodeStats val2, BiFunction<Double, Double, Double> func) {
|
|
||||||
var save = new NodeStats();
|
|
||||||
NodeStats.operation(save, val1, val2, func);
|
|
||||||
return save;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies a binary function to merge two sets of statistics into a third one.
|
* Applies a binary function to merge two sets of statistics into a third one.
|
||||||
* This method combines the statistics from two `Statistics` objects (`val1` and
|
* This method combines the statistics from two `Statistics` objects (`val1` and
|
||||||
@@ -160,11 +179,13 @@ public class NodeStats {
|
|||||||
* @param func The binary function that defines how to merge each pair of values
|
* @param func The binary function that defines how to merge each pair of values
|
||||||
* from `val1` and `val2`. It takes two `Double` values (from `val1`
|
* from `val1` and `val2`. It takes two `Double` values (from `val1`
|
||||||
* and `val2`) and returns a new `Double` value.
|
* and `val2`) and returns a new `Double` value.
|
||||||
|
* @return The `save` object with the merged statistics.
|
||||||
*/
|
*/
|
||||||
public static void operation(NodeStats save, NodeStats val1, NodeStats val2,
|
public static NodeStats operation(NodeStats save, NodeStats val1, NodeStats val2,
|
||||||
BiFunction<Double, Double, Double> func) {
|
BiFunction<Double, Double, Double> func) {
|
||||||
save.numArrivals = func.apply(val1.numArrivals, val2.numArrivals);
|
save.numArrivals = func.apply(val1.numArrivals, val2.numArrivals);
|
||||||
save.numDepartures = func.apply(val1.numDepartures, val2.numDepartures);
|
save.numDepartures = func.apply(val1.numDepartures, val2.numDepartures);
|
||||||
|
// save.maxQueueLength = func.apply(val1.maxQueueLength, val2.maxQueueLength);
|
||||||
save.avgQueueLength = func.apply(val1.avgQueueLength, val2.avgQueueLength);
|
save.avgQueueLength = func.apply(val1.avgQueueLength, val2.avgQueueLength);
|
||||||
save.avgWaitTime = func.apply(val1.avgWaitTime, val2.avgWaitTime);
|
save.avgWaitTime = func.apply(val1.avgWaitTime, val2.avgWaitTime);
|
||||||
save.avgResponse = func.apply(val1.avgResponse, val2.avgResponse);
|
save.avgResponse = func.apply(val1.avgResponse, val2.avgResponse);
|
||||||
@@ -176,5 +197,102 @@ public class NodeStats {
|
|||||||
save.throughput = func.apply(val1.throughput, val2.throughput);
|
save.throughput = func.apply(val1.throughput, val2.throughput);
|
||||||
save.utilization = func.apply(val1.utilization, val2.utilization);
|
save.utilization = func.apply(val1.utilization, val2.utilization);
|
||||||
save.unavailable = func.apply(val1.unavailable, val2.unavailable);
|
save.unavailable = func.apply(val1.unavailable, val2.unavailable);
|
||||||
|
return save;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to store incremental statistics.
|
||||||
|
* This class is used to store incremental statistics for a confidence index.
|
||||||
|
* It keeps track of the average, variance, minimum, and maximum values of the
|
||||||
|
* statistics over time. The statistics are updated incrementally as new data
|
||||||
|
* points are added, allowing for real-time monitoring of the confidence index.
|
||||||
|
*/
|
||||||
|
public static class Summary {
|
||||||
|
public final NodeStats average = new NodeStats();
|
||||||
|
public final NodeStats variance = new NodeStats();
|
||||||
|
public final NodeStats min = new NodeStats().apply(_ -> Double.MAX_VALUE);
|
||||||
|
public final NodeStats max = new NodeStats().apply(_ -> Double.MIN_VALUE);
|
||||||
|
private List<NodeStats> stats = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the incremental statistics with new data.
|
||||||
|
* This method updates the incremental statistics with new data points. It
|
||||||
|
* calculates the average, variance, minimum, and maximum values of the
|
||||||
|
* statistics over time, based on the new data points. The statistics are
|
||||||
|
* stored in the `average`, `variance`, `min`, and `max` fields of the
|
||||||
|
* `Incremental` object, respectively.
|
||||||
|
*
|
||||||
|
* @param other The `NodeStats` object containing the new data points to add to
|
||||||
|
* the incremental statistics.
|
||||||
|
*/
|
||||||
|
public void update(NodeStats other) {
|
||||||
|
var n = this.stats.size() + 1;
|
||||||
|
this.stats.add(other);
|
||||||
|
|
||||||
|
var delta = this.average.clone().merge(other, (avg, newVal) -> newVal - avg);
|
||||||
|
this.average.merge(delta, (val1, val2) -> val1 + val2 / n);
|
||||||
|
|
||||||
|
var mergedDelta = this.average.clone()
|
||||||
|
.merge(other, (avg, newVal) -> newVal - avg)
|
||||||
|
.merge(delta, (dNew, dOld) -> dNew * dOld);
|
||||||
|
|
||||||
|
var nSampleSize = Math.max(n - 1, 1);
|
||||||
|
this.variance.merge(mergedDelta, (var, deltas) -> var + deltas / nSampleSize);
|
||||||
|
this.min.merge(other, Math::min);
|
||||||
|
this.max.merge(other, Math::max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the standard deviation of the values in the array.
|
||||||
|
*
|
||||||
|
* @return the standard deviation value
|
||||||
|
*/
|
||||||
|
public NodeStats stdDev() {
|
||||||
|
return this.variance.clone().apply(Math::sqrt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 distribution the t-distribution to use
|
||||||
|
* @param stdDev the standard deviation of the values
|
||||||
|
* @param alpha the alpha value
|
||||||
|
* @return the error of the values
|
||||||
|
*/
|
||||||
|
public NodeStats calcError(double alpha) {
|
||||||
|
var n = this.stats.size();
|
||||||
|
var distr = new TDistribution(null, n - 1);
|
||||||
|
var tValue = distr.inverseCumulativeProbability(alpha);
|
||||||
|
|
||||||
|
return this.stdDev().apply(std -> tValue * (std / Math.sqrt(n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the frequency of the values in the array.
|
||||||
|
* In the function passed you can choose which value to use for the frequency.
|
||||||
|
*
|
||||||
|
* @param numBins the number of bins to use
|
||||||
|
* @param getValue the function to get the value from the stats
|
||||||
|
* @return an array with the frequency of the values
|
||||||
|
*/
|
||||||
|
public int[] getFrequency(int numBins, Function<NodeStats, Double> getValue) {
|
||||||
|
var buckets = new int[numBins];
|
||||||
|
var min = getValue.apply(this.min);
|
||||||
|
var max = getValue.apply(this.max);
|
||||||
|
var range = max - min;
|
||||||
|
var step = numBins / range;
|
||||||
|
|
||||||
|
for (var stat : this.stats) {
|
||||||
|
var value = getValue.apply(stat);
|
||||||
|
var index = (int) Math.floor((value - min) * step);
|
||||||
|
index = Math.min(index, numBins - 1);
|
||||||
|
buckets[index] += 1;
|
||||||
|
}
|
||||||
|
return buckets;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
package net.berack.upo.valpre.sim.stats;
|
package net.berack.upo.valpre.sim.stats;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,21 +36,24 @@ public class Result {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
var size = (int) Math.ceil(Math.max(Math.log10(this.simulationTime), 1));
|
return buildPrintable(this.seed, this.simulationTime, this.timeElapsedMS, this.nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildPrintable(long seed, double simTime, double timeMS, Map<String, NodeStats> nodes) {
|
||||||
|
var size = (int) Math.ceil(Math.max(Math.log10(simTime), 1));
|
||||||
var iFormat = "%" + size + ".0f";
|
var iFormat = "%" + size + ".0f";
|
||||||
var fFormat = "%" + (size + 4) + ".3f";
|
var fFormat = "%" + (size + 4) + ".3f";
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.append("===== Net Stats =====\n");
|
builder.append("===== Net Stats =====\n");
|
||||||
builder.append(String.format("Seed: \t%d\n", this.seed));
|
builder.append(String.format("Seed: \t%d\n", seed));
|
||||||
builder.append(String.format("Simulation: \t" + fFormat + "\n", this.simulationTime));
|
builder.append(String.format("Simulation: \t" + fFormat + "\n", simTime));
|
||||||
builder.append(String.format("Elapsed: \t" + fFormat + "ms\n", this.timeElapsedMS / 1e6));
|
builder.append(String.format("Elapsed: \t" + fFormat + "ms\n", timeMS));
|
||||||
// return builder.toString();
|
|
||||||
|
|
||||||
var table = new ConsoleTable("Node", "Departures", "Avg Queue", "Avg Wait", "Avg Response", "Throughput",
|
var table = new ConsoleTable("Node", "Departures", "Avg Queue", "Avg Wait", "Avg Response", "Throughput",
|
||||||
"Utilization %", "Unavailable %", "Last Event");
|
"Utilization %", "Unavailable %", "Last Event");
|
||||||
|
|
||||||
for (var entry : this.nodes.entrySet()) {
|
for (var entry : nodes.entrySet()) {
|
||||||
var stats = entry.getValue();
|
var stats = entry.getValue();
|
||||||
table.addRow(
|
table.addRow(
|
||||||
entry.getKey(),
|
entry.getKey(),
|
||||||
@@ -63,4 +70,120 @@ public class Result {
|
|||||||
builder.append(table);
|
builder.append(table);
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the summary of the statistics of a network simulation.
|
||||||
|
* It is used to save the final results of the network and its nodes, including
|
||||||
|
* the number of arrivals and departures, the maximum queue length, the busy
|
||||||
|
* time, and the response time.
|
||||||
|
*/
|
||||||
|
public static class Summary {
|
||||||
|
public final long seed;
|
||||||
|
private double avgSimulationTime = 0.0d;
|
||||||
|
private double avgTimeElapsedMS = 0.0d;
|
||||||
|
private Map<String, NodeStats.Summary> stats = new HashMap<>();
|
||||||
|
private List<Result> runs = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new summary object for the given seed.
|
||||||
|
*
|
||||||
|
* @param seed the initial seed used by the simulation
|
||||||
|
*/
|
||||||
|
public Summary(long seed) {
|
||||||
|
this.seed = seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new summary object for the given results.
|
||||||
|
*
|
||||||
|
* @param results the results to summarize
|
||||||
|
*/
|
||||||
|
public Summary(List<Result> results) {
|
||||||
|
this(results.get(0).seed);
|
||||||
|
for (var result : results)
|
||||||
|
this.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public void add(Result result) {
|
||||||
|
if (result == null)
|
||||||
|
throw new IllegalArgumentException("Result cannot be null");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the average simulation time of the summary.
|
||||||
|
*
|
||||||
|
* @return the average simulation time
|
||||||
|
*/
|
||||||
|
public double getAvgSimulationTime() {
|
||||||
|
return this.avgSimulationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the average time elapsed of the summary.
|
||||||
|
*
|
||||||
|
* @return the average time elapsed
|
||||||
|
*/
|
||||||
|
public double getAvgTimeElapsedMS() {
|
||||||
|
return this.avgTimeElapsedMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the nodes of the summary.
|
||||||
|
*
|
||||||
|
* @return the nodes of the summary
|
||||||
|
*/
|
||||||
|
public Collection<String> getNodes() {
|
||||||
|
return this.stats.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets 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
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the runs of the summary.
|
||||||
|
*
|
||||||
|
* @return the runs of the summary
|
||||||
|
*/
|
||||||
|
public List<Result> getRuns() {
|
||||||
|
return List.copyOf(this.runs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
var stats = new HashMap<String, NodeStats>();
|
||||||
|
for (var entry : this.stats.entrySet())
|
||||||
|
stats.put(entry.getKey(), entry.getValue().average);
|
||||||
|
|
||||||
|
return buildPrintable(this.seed, this.avgSimulationTime, this.avgTimeElapsedMS, stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
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;
|
|
||||||
this.stats = ResultSummary.getSummary(runs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* @throws IllegalArgumentException if the node or the statistic is not found
|
|
||||||
*/
|
|
||||||
public StatisticsSummary getSummaryOf(String node, String stat) {
|
|
||||||
var stats = this.getSummaryOf(node);
|
|
||||||
var value = stats.get(stat);
|
|
||||||
if (value == null) {
|
|
||||||
var arr = String.join(", ", stats.keySet().toArray(new String[0]));
|
|
||||||
throw new IllegalArgumentException("Statistic [" + stat + "] not found. Available: " + arr);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* @throws IllegalArgumentException if the node is not found
|
|
||||||
*/
|
|
||||||
public Map<String, StatisticsSummary> getSummaryOf(String node) {
|
|
||||||
var stat = this.stats.get(node);
|
|
||||||
if (stat == null)
|
|
||||||
throw new IllegalArgumentException("Node not found");
|
|
||||||
return stat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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("throughput").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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the summary of the statistics of the nodes.
|
|
||||||
* The first map is the node name, the second map is the statistic name.
|
|
||||||
*
|
|
||||||
* @param runs the runs to get the summary
|
|
||||||
* @return the summary of the statistics of the nodes
|
|
||||||
*/
|
|
||||||
public static Map<String, Map<String, StatisticsSummary>> getSummary(Result[] runs) {
|
|
||||||
// Get the statistics of the nodes
|
|
||||||
var nodeStats = new HashMap<String, NodeStats[]>();
|
|
||||||
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 NodeStats[runs.length]);
|
|
||||||
stats[i] = entry.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the summary of the statistics of the nodes
|
|
||||||
var stats = new HashMap<String, Map<String, StatisticsSummary>>();
|
|
||||||
for (var entry : nodeStats.entrySet()) {
|
|
||||||
var node = entry.getKey();
|
|
||||||
var summary = StatisticsSummary.getSummary(entry.getValue());
|
|
||||||
stats.put(node, summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package net.berack.upo.valpre.sim.stats;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.apache.commons.math3.distribution.TDistribution;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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[] values;
|
|
||||||
private final TDistribution distr;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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 - 1));
|
|
||||||
this.median = this.getPercentile(0.50);
|
|
||||||
this.min = values[0];
|
|
||||||
this.max = values[values.length - 1];
|
|
||||||
this.distr = new TDistribution(null, values.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 the percentile of the values in the array.
|
|
||||||
*
|
|
||||||
* @param percentile the percentile to calculate
|
|
||||||
* @return the value at the selected percentile
|
|
||||||
*/
|
|
||||||
public double getPercentile(double percentile) {
|
|
||||||
var index = (int) Math.floor(percentile * (this.values.length - 1));
|
|
||||||
return this.values[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
return StatisticsSummary.calcError(this.distr, this.stdDev, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 distribution the t-distribution to use
|
|
||||||
* @param stdDev the standard deviation of the values
|
|
||||||
* @param alpha the alpha value
|
|
||||||
* @return the error of the values
|
|
||||||
*/
|
|
||||||
public static double calcError(TDistribution distribution, double stdDev, double alpha) {
|
|
||||||
var percentile = distribution.inverseCumulativeProbability(alpha);
|
|
||||||
var n = distribution.getDegreesOfFreedom() + 1;
|
|
||||||
return percentile * (stdDev / Math.sqrt(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(NodeStats[] stats) throws IllegalArgumentException {
|
|
||||||
try {
|
|
||||||
var map = new HashMap<String, StatisticsSummary>();
|
|
||||||
|
|
||||||
for (var field : NodeStats.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