- added parameters to main
- print and/or save
- added more data to the stats
- removed lower and upper bounds for simplier error
This commit is contained in:
2025-01-27 13:45:54 +01:00
parent 71dac2c6d1
commit a729dfb123
7 changed files with 294 additions and 54 deletions

3
.gitignore vendored
View File

@@ -25,4 +25,5 @@ replay_pid*
# mine
pdf/*
pdf/*
*.csv

36
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Run1k Rand",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "-runs 1000 -p -seed 0"
},
{
"type": "java",
"name": "Run1k save",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "-runs 1000 -p -csv result.csv"
},
{
"type": "java",
"name": "Run1k",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "-runs 1000 -p"
},
{
"type": "java",
"name": "Run10",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "-runs 10"
},
]
}

View File

@@ -1,5 +1,8 @@
package net.berack.upo.valpre;
import java.util.HashMap;
import java.util.Map;
import net.berack.upo.valpre.rand.Distribution;
import net.berack.upo.valpre.sim.SimulationMultiple;
import net.berack.upo.valpre.sim.Net;
@@ -8,12 +11,24 @@ import net.berack.upo.valpre.sim.ServerNode;
public class Main {
public static void main(String[] args) throws Exception {
// Parameters for the simulation
var seed = 2007539552;
String csv = null;
var runs = 100;
var seed = 2007539552L;
var total = 10000;
var lambda = 1.0 / 4.5;
var mu = 3.2;
var sigma = 0.6;
// Evantually change the parameters
var arguments = parseParameters(args);
if (arguments.containsKey("seed"))
seed = Long.parseLong(arguments.get("seed"));
if (arguments.containsKey("runs"))
runs = Integer.parseInt(arguments.get("runs"));
if (arguments.containsKey("csv"))
csv = arguments.get("csv");
var parallel = arguments.containsKey("p");
// Build the network
var net = new Net();
var node1 = ServerNode.createLimitedSource("Source", new Distribution.Exponential(lambda), total);
@@ -28,11 +43,39 @@ public class Main {
// var maxTime = new EndSimulationCriteria.MaxTime(1000.0);
var nano = System.nanoTime();
var sim = new SimulationMultiple(net);
var results = sim.runParallel(seed, 1000);
var results = parallel ? sim.runParallel(seed, runs) : sim.run(seed, runs);
nano = System.nanoTime() - nano;
System.out.print(results.average.getHeader());
System.out.print(results.average.getSummary());
System.out.println("Final time " + nano / 1e6 + "ms");
if (csv != null) {
results.saveCSV(csv);
System.out.println("Data saved to " + csv);
}
}
public static Map<String, String> parseParameters(String[] args) {
var arguments = new HashMap<String, Boolean>();
arguments.put("p", false);
arguments.put("seed", true);
arguments.put("runs", true);
arguments.put("csv", true);
var param = new Parameters("-", arguments);
try {
return param.parse(args);
} catch (IllegalArgumentException e) {
var descriptions = new HashMap<String, String>();
descriptions.put("p", "Add this if you want the simulation to use threads (one each run)");
descriptions.put("seed", "The seed of the simulation");
descriptions.put("runs", "How many runs the simulator should run");
descriptions.put("csv", "The filename for saving every run statistics");
System.out.println(e.getMessage());
System.out.println(param.helper(descriptions));
return null;
}
}
}

View File

@@ -0,0 +1,115 @@
package net.berack.upo.valpre;
import java.util.HashMap;
import java.util.Map;
/**
* TODO
*/
public class Parameters {
private final Map<String, Boolean> arguments;
private final String prefix;
/**
* TODO
*
* @param arguments
*/
public Parameters(String prefix, Map<String, Boolean> arguments) {
if (arguments == null || arguments.size() == 0)
throw new IllegalArgumentException();
this.arguments = arguments;
this.prefix = prefix;
}
/**
* TODO
*
* @param eventualDescription
* @return
*/
public String helper(Map<String, String> eventualDescription) {
var size = 0;
var parameters = new HashMap<String, String>();
for (var param : this.arguments.entrySet()) {
var string = this.prefix + param.getKey();
if (param.getValue())
string += " <value>";
parameters.put(param.getKey(), string);
size = Math.max(size, string.length());
}
size += 2; // spacing
var builder = new StringBuilder();
for (var param : parameters.entrySet()) {
var key = param.getKey();
var args = param.getValue();
builder.append(" ");
builder.append(args);
var desc = eventualDescription.get(key);
if (desc != null) {
builder.append(" ".repeat(size - args.length()));
builder.append(desc);
}
builder.append("\n");
}
return builder.toString();
}
/**
* TODO
*
* @param args
* @return
*/
public Map<String, String> parse(String[] args) {
var result = new HashMap<String, String>();
if (args == null || args.length == 0)
return result;
for (var i = 0; i < args.length; i += 1) {
var current = args[i];
var next = i + 1 < args.length ? args[i + 1] : null;
var updateI = this.parseSingle(current, next, result);
if (updateI)
i += 1;
}
return result;
}
/**
* TODO
*
* @param current
* @param next
* @param result
* @return
*/
private boolean parseSingle(String current, String next, Map<String, String> result) {
if (!current.startsWith(this.prefix))
throw new IllegalArgumentException("Missing prefix [" + current + "]");
current = current.substring(this.prefix.length());
var value = this.arguments.get(current);
if (value != null) {
result.put(current, value ? next : "");
return value;
}
var finalSize = result.size() + current.length();
for (var letter : current.split(""))
if (this.arguments.get(letter) != null)
result.put(current, "");
if (finalSize != result.size())
throw new IllegalArgumentException("Argument unknown");
return false;
}
}

View File

@@ -13,6 +13,9 @@ public class Result {
public final long seed;
public final double simulationTime;
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
@@ -28,6 +31,9 @@ public class Result {
this.simulationTime = time;
this.timeElapsedMS = elapsed;
this.nodes = nodes;
this.size = (int) Math.ceil(Math.log10(this.simulationTime));
this.iFormat = "%" + this.size + ".0f";
this.fFormat = "%" + (this.size + 4) + ".3f";
}
/**
@@ -36,13 +42,11 @@ public class Result {
* real time
*/
public String getHeader() {
var size = (int) Math.ceil(Math.log10(this.simulationTime));
var format = "%" + (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" + format + "\n", this.simulationTime));
builder.append(String.format("Elapsed: \t" + format + "ms\n", this.timeElapsedMS / 1e6));
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();
}
@@ -52,24 +56,68 @@ public class Result {
* the statistics for each node in the network.
*/
public String getSummary() {
var size = (int) Math.ceil(Math.log10(this.simulationTime));
var iFormat = "%" + size + ".0f";
var fFormat = "%" + (size + 4) + ".3f";
String[] h = { "Node", "Departures", "Avg Queue", "Avg Response", "Throughput", "Utilization %", "Last Event" };
String[] h = { "Node", "Departures", "Avg Queue", "Avg Wait", "Avg Response", "Throughput", "Utilization %",
"Last Event" };
var table = new ConsoleTable(h);
for (var entry : this.nodes.entrySet()) {
var stats = entry.getValue();
table.addRow(
entry.getKey(),
String.format(iFormat, stats.numDepartures),
String.format(fFormat, stats.avgQueueLength),
String.format(fFormat, stats.avgResponse),
String.format(fFormat, stats.troughput),
String.format(fFormat, stats.utilization * 100),
String.format(fFormat, stats.lastEventTime));
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.lastEventTime));
}
return table.toString();
}
/**
* TODO
*
* @param tableHeader
* @return
*/
public String getSummaryCSV(boolean tableHeader) {
var builder = new StringBuilder();
if (tableHeader)
builder.append(
"Seed,Node,Arrivals,Departures,MaxQueue,AvgQueue,AvgWait,AvgResponse,BusyTime,WaitTime,ResponseTime,LastEventTime,Throughput,Utilization\n");
for (var entry : this.nodes.entrySet()) {
var stats = entry.getValue();
builder.append(this.seed);
builder.append(',');
builder.append(entry.getKey().replace(',', ';').replace('"', '\''));
builder.append(',');
builder.append(stats.numArrivals);
builder.append(',');
builder.append(stats.numDepartures);
builder.append(',');
builder.append(stats.maxQueueLength);
builder.append(',');
builder.append(stats.avgQueueLength);
builder.append(',');
builder.append(stats.avgWaitTime);
builder.append(',');
builder.append(stats.avgResponse);
builder.append(',');
builder.append(stats.busyTime);
builder.append(',');
builder.append(stats.waitTime);
builder.append(',');
builder.append(stats.responseTime);
builder.append(',');
builder.append(stats.lastEventTime);
builder.append(',');
builder.append(stats.troughput);
builder.append(',');
builder.append(stats.utilization);
builder.append('\n');
}
return builder.toString();
}
}

View File

@@ -1,5 +1,7 @@
package net.berack.upo.valpre.sim.stats;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
/**
@@ -9,8 +11,7 @@ public class ResultMultiple {
public final Result[] runs;
public final Result average;
public final Result variance;
public final Result lowerBound;
public final Result upperBound;
public final Result error95;
/**
* TODO
@@ -18,13 +19,31 @@ public class ResultMultiple {
* @param runs
*/
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.calcVar(this.average, runs);
this.error95 = calcError(this.average, this.variance, runs.length, 0.95);
}
var temp = calcInterval(this.average, this.variance, runs.length, 0.95);
this.lowerBound = temp[0];
this.upperBound = temp[1];
/**
* TODO
*
* @param filename
* @throws IOException
*/
public void saveCSV(String filename) throws IOException {
try (var file = new FileWriter(filename)) {
var first = true;
var builder = new StringBuilder();
for (var run : this.runs) {
builder.append(run.getSummaryCSV(first));
first = false;
}
file.write(builder.toString());
}
}
/**
@@ -98,10 +117,7 @@ public class ResultMultiple {
* @param alpha
* @return
*/
public static Result[] calcInterval(Result avg, Result stdDev, int sampleSize, double alpha) {
if (sampleSize <= 1)
throw new IllegalArgumentException("Il numero di campioni deve essere maggiore di 1.");
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);
@@ -117,31 +133,6 @@ public class ResultMultiple {
stat.merge(entry.getValue(), (_, val) -> percentile * (val / sqrtSample));
error.nodes.put(entry.getKey(), stat);
}
// Calculating the lower and the upper bound
var lowerBound = new Result(avg.seed,
avg.simulationTime - error.simulationTime,
avg.timeElapsedMS - error.timeElapsedMS,
new HashMap<>());
var upperBound = new Result(avg.seed,
avg.simulationTime + error.simulationTime,
avg.timeElapsedMS + error.timeElapsedMS,
new HashMap<>());
error.nodes.entrySet().forEach(entry -> {
var key = entry.getKey();
var errStat = entry.getValue();
var avgStat = avg.nodes.get(key);
var lower = new Statistics();
var upper = new Statistics();
Statistics.apply(lower, avgStat, errStat, (a, e) -> a - e);
Statistics.apply(upper, avgStat, errStat, (a, e) -> a + e);
lowerBound.nodes.put(key, lower);
upperBound.nodes.put(key, lower);
});
return new Result[] { lowerBound, upperBound };
return error;
}
}

View File

@@ -12,10 +12,12 @@ public class Statistics {
public double maxQueueLength = 0.0d;
public double avgQueueLength = 0.0d;
public double busyTime = 0.0d;
public double waitTime = 0.0d;
public double responseTime = 0.0d;
public double lastEventTime = 0.0d;
// derived stats, you can calculate them even at the end
public double avgWaitTime = 0.0d;
public double avgResponse = 0.0d;
public double troughput = 0.0d;
public double utilization = 0.0d;
@@ -50,7 +52,9 @@ public class Statistics {
this.responseTime += response;
this.busyTime += time - this.lastEventTime;
this.lastEventTime = time;
this.waitTime = this.responseTime - this.busyTime;
this.avgWaitTime = this.waitTime / this.numDepartures;
this.avgResponse = this.responseTime / this.numDepartures;
this.troughput = this.numDepartures / this.lastEventTime;
this.utilization = this.busyTime / this.lastEventTime;
@@ -102,10 +106,12 @@ public class Statistics {
save.avgQueueLength = func.apply(val1.avgQueueLength, val2.avgQueueLength);
save.busyTime = func.apply(val1.busyTime, val2.busyTime);
save.responseTime = func.apply(val1.responseTime, val2.responseTime);
save.waitTime = func.apply(val1.waitTime, val2.waitTime);
save.lastEventTime = func.apply(val1.lastEventTime, val2.lastEventTime);
// derived stats
save.avgWaitTime = func.apply(val1.avgWaitTime, val2.avgWaitTime);
save.avgResponse = func.apply(val1.avgResponse, val2.avgResponse);
save.troughput = func.apply(val1.troughput, val2.troughput);
save.utilization = func.apply(val1.utilization, val2.utilization);
save.avgResponse = func.apply(val1.avgResponse, val2.avgResponse);
}
}