Merge pull request #1 from Berack96/sim-hash-free

Sim hash free
This commit was merged in pull request #1.
This commit is contained in:
Giacomo Bertolazzi
2025-02-11 14:30:15 +01:00
committed by GitHub
23 changed files with 9281 additions and 8424 deletions

15
.vscode/launch.json vendored
View File

@@ -9,28 +9,35 @@
"name": "Run1k Simple",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "simulation -net example1.net -runs 1000 -p -seed 0"
"args": "simulation -net example1.net -runs 1000 -p"
},
{
"type": "java",
"name": "Run1k Complex",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "simulation -net example2.net -runs 1000 -p -seed 0"
"args": "simulation -net example2.net -runs 1000 -p"
},
{
"type": "java",
"name": "Run1k Complex EXP",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "simulation -net example3.net -runs 1000 -p -seed 0"
"args": "simulation -net example3.net -runs 1000 -p"
},
{
"type": "java",
"name": "Run Incremental",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "simulation -net example3.net -runs 1000 -i \"[Service1:throughput=0.98:0.01],[Service2:utilization=0.98:0.01],[Service2:unavailable=0.98:0.01]\""
},
{
"type": "java",
"name": "Run10",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "simulation -net example1.net -runs 10"
"args": "simulation -net example1.net -runs 10 -seed 2007539552"
},
{
"type": "java",

View File

@@ -5,8 +5,6 @@ import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import net.berack.upo.valpre.sim.EndCriteria;
public class Main {
public static void main(String[] args) {
if (args.length == 0)
@@ -20,10 +18,11 @@ public class Main {
var param = Main.getParameters(program, subArgs);
new SimulationBuilder(param.get("net"))
.setCsv(param.get("csv"))
.setRuns(param.getOrDefault("runs", Integer::parseInt, 100))
.setSeed(param.getOrDefault("seed", Long::parseLong, 2007539552L))
.setMaxRuns(param.getOrDefault("runs", Integer::parseInt, 100))
.setSeed(param.getOrDefault("seed", Long::parseLong, 0L))
.setParallel(param.get("p") != null)
.setEndCriteria(EndCriteria.parse(param.get("end")))
.parseEndCriteria(param.get("end"))
.parseConfidenceIndices(param.get("i"))
.run();
}
case "plot" -> {
@@ -58,6 +57,7 @@ public class Main {
arguments.put("net", true);
arguments.put("end", true);
arguments.put("csv", true);
arguments.put("i", true);
var descriptions = new HashMap<String, String>();
descriptions.put("p", "Add this if you want the simulation to use threads (one each run).");
@@ -65,6 +65,8 @@ public class Main {
descriptions.put("runs", "How many runs the simulator should run.");
descriptions.put("end", "When the simulation should end. Format is [ClassName:param1,..,paramN];[..]");
descriptions.put("net", "The file net to use. Use example1.net or example2.net for the provided ones.");
descriptions.put("i", "The confidence indices to use for the simulation. If active then p is ignored."
+ "Format is [node:stat:confidence:relativeError];[..]");
var csvDesc = switch (program) {
case "simulation" -> "The filename for saving every run statistics.";
@@ -83,9 +85,10 @@ public class Main {
try {
var uri = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI();
var name = new File(uri).getName();
System.out.println(message);
System.err.println(message);
System.out.println("Usage: java -jar " + name + ".jar [simulation|plot|net] [args]");
System.out.println("simulation args: -net <net> -csv <csv> [-runs <runs>] [-seed <seed>] [-p]");
System.out.println("simulation args: -net <net> [-csv <csv>] [-runs <runs>] [-seed <seed>]"
+ "[-p] [-end <end>] [-i <indices>]");
System.out.println("plot args: -csv <csv>");
System.out.println("net args: none");
System.exit(1);

View File

@@ -51,8 +51,12 @@ public class NetBuilderInteractive {
for (var i = 0; i < this.net.size(); i++) {
var name = this.net.getNode(i).name;
builder.append(name).append(" -> ");
for (var connection : this.net.getChildren(i))
builder.append(connection.child.name).append("(").append(connection.weight).append("), ");
for (var connection : this.net.getChildren(i)) {
var child = this.net.getNode(connection.index);
builder.append(child.name).append("(").append(connection.weight).append("), ");
}
builder.delete(builder.length() - 2, builder.length());
builder.append("\n");
}
@@ -74,12 +78,12 @@ public class NetBuilderInteractive {
var limit = ask("Arrivals limit (0 for Int.Max): ", Integer::parseInt, 1);
if (limit <= 0)
limit = Integer.MAX_VALUE;
yield ServerNode.createLimitedSource(name, distribution, limit);
yield ServerNode.Builder.sourceLimited(name, limit, distribution);
}
case 2 -> {
var servers = ask("Number of servers: ", Integer::parseInt, 1);
var unavailable = askDistribution("Unavailable distribution");
yield ServerNode.createQueue(name, servers, distribution, unavailable);
yield ServerNode.Builder.queue(name, servers, distribution, unavailable);
}
default -> null;
};

View File

@@ -5,7 +5,11 @@ import java.io.IOException;
import java.util.concurrent.ExecutionException;
import com.esotericsoftware.kryo.KryoException;
import net.berack.upo.valpre.sim.ConfidenceIndices;
import net.berack.upo.valpre.sim.EndCriteria;
import net.berack.upo.valpre.sim.EndCriteria.MaxArrivals;
import net.berack.upo.valpre.sim.EndCriteria.MaxDepartures;
import net.berack.upo.valpre.sim.EndCriteria.MaxTime;
import net.berack.upo.valpre.sim.Net;
import net.berack.upo.valpre.sim.SimulationMultiple;
import net.berack.upo.valpre.sim.stats.CsvResult;
@@ -15,12 +19,13 @@ import net.berack.upo.valpre.sim.stats.CsvResult;
* and runs the simulation with the given parameters.
*/
public class SimulationBuilder {
private String csv;
private int runs;
private long seed;
private boolean parallel;
private String csv = null;
private int runs = 1;
private long seed = 0;
private EndCriteria[] endCriteria = new EndCriteria[0];
private Type type = Type.Normal;
private Net net;
private EndCriteria[] endCriteria;
private ConfidenceIndices confidences;
/**
* Create a new simulation for the given net.
@@ -32,6 +37,7 @@ public class SimulationBuilder {
try {
var file = Parameters.getFileOrExample(netFile);
this.net = Net.load(file);
this.confidences = new ConfidenceIndices(this.net);
file.close();
} catch (FileNotFoundException e) {
throw new IllegalArgumentException("Net file needed!");
@@ -50,16 +56,17 @@ public class SimulationBuilder {
if (net == null)
throw new IllegalArgumentException("Net needed!");
this.net = net;
this.confidences = new ConfidenceIndices(net);
}
/**
* Set the number of runs for the simulation.
* Set the maximum number of runs for the simulation.
*
* @param runs the number of runs
* @throws IllegalArgumentException if the runs are less than 1
* @return this simulation
*/
public SimulationBuilder setRuns(int runs) {
public SimulationBuilder setMaxRuns(int runs) {
if (runs <= 0)
throw new IllegalArgumentException("Runs must be greater than 0!");
@@ -86,7 +93,8 @@ public class SimulationBuilder {
* @return this simulation
*/
public SimulationBuilder setParallel(boolean parallel) {
this.parallel = parallel;
if (parallel && this.confidences.isEmpty())
this.type = Type.Parallel;
return this;
}
@@ -117,21 +125,119 @@ public class SimulationBuilder {
return this;
}
/**
* Set the end criteria for the simulation.
* Parses the given string to create an array of end criteria.
* The string passed must be in the following format:
* [criteria1];[criteria2];...;[criteriaN]
*
* and each criteria must be in the following format:
* ClassName:param1,param2,...,paramN
*
* If the string is empty or null, no criteria are set.
* If one of the criteria is not valid, an exception is thrown.
*
* @param criterias The string to parse.
* @return this builder
* @throws IllegalArgumentException If one of the criteria is not valid.
*/
public SimulationBuilder parseEndCriteria(String criterias) {
if (criterias == null || criterias.isEmpty())
return this;
var criteria = criterias.split(";");
this.endCriteria = new EndCriteria[criteria.length];
for (int i = 0; i < criteria.length; i++) {
var current = criteria[i].substring(1, criteria[i].length() - 1); // Remove the brackets
var parts = current.split(":");
if (parts.length != 2)
throw new IllegalArgumentException("Invalid criteria: " + current);
var className = parts[0];
var params = parts[1].split(",");
this.endCriteria[i] = switch (className) {
case "MaxArrivals" -> new MaxArrivals(params[0], Integer.parseInt(params[1]));
case "MaxDepartures" -> new MaxDepartures(params[0], Integer.parseInt(params[1]));
case "MaxTime" -> new MaxTime(Double.parseDouble(params[0]));
default -> throw new IllegalArgumentException("Invalid criteria: " + current);
};
}
return this;
}
/**
* Add a confidence index for the given node and stat.
* The confidence index is used to determine when the simulation should stop.
*
* @param node the node
* @param stat the stat to calculate the confidence index for
* @param confidence the confidence level expressed as a percentage [0,1]
* @param relError the relative error expressed as a percentage [0,1]
* @return this simulation
* @throws IllegalArgumentException if any of the input parameters is invalid
*/
public SimulationBuilder addConfidenceIndex(String node, String stat, double confidence, double relError) {
var index = this.net.getNodeIndex(node);
if (index < 0)
throw new IllegalArgumentException("Invalid node: " + node);
this.confidences.add(index, stat, confidence, relError);
this.type = Type.Incremental;
return this;
}
/**
* Parse the confidence indices from a string and add them to the simulation.
* If the string is null then nothing is done and the builder is returned.
* The string must be in the following format:
* "[node1:stat1=confidence1:relError1],..,[nodeN:statN=confidenceN:relErrorN]".
*
* @param indices the indices to parse
* @return this simulation
* @throws IllegalArgumentException if indices are not in the correct format
* @throws IllegalArgumentException if the values are invalid
*/
public SimulationBuilder parseConfidenceIndices(String indices) {
if (indices == null)
return this;
for (var index : indices.split(",")) {
var parts = index.split("=");
if (parts.length != 2)
throw new IllegalArgumentException("Invalid confidence index: " + index);
var first = parts[0].split(":");
if (first.length != 2)
throw new IllegalArgumentException("Invalid confidence index: " + index);
var second = parts[1].split(":");
if (second.length != 2)
throw new IllegalArgumentException("Invalid confidence index: " + index);
var node = first[0].substring(1);
var stat = first[1];
var confidence = Double.parseDouble(second[0]);
var relError = Double.parseDouble(second[1].substring(0, second[1].length() - 1));
this.addConfidenceIndex(node, stat, confidence, relError);
}
return this;
}
/**
* Run the simulation with the given parameters.
* At the end it prints the results and saves them to a CSV file if requested.
*
* @throws InterruptedException If the simulation is interrupted.
* @throws ExecutionException If the simulation fails.
* @throws KryoException If the simulation fails.
* @throws IOException If the simulation fails.
* @throws ExecutionException If the simulation has an error.
* @throws IOException If the CSV file has a problem.
*/
public void run() throws InterruptedException, ExecutionException, KryoException, IOException {
public void run() throws InterruptedException, ExecutionException, IOException {
var nano = System.nanoTime();
var sim = new SimulationMultiple(this.net);
var summary = this.parallel
? sim.runParallel(this.seed, this.runs, this.endCriteria)
: sim.run(this.seed, this.runs, this.endCriteria);
var summary = switch (this.type) {
case Incremental -> sim.runIncremental(this.seed, this.runs, this.confidences, this.endCriteria);
case Parallel -> sim.runParallel(this.seed, this.runs, this.endCriteria);
case Normal -> sim.run(this.seed, this.runs, this.endCriteria);
};
nano = System.nanoTime() - nano;
System.out.print(summary);
@@ -142,4 +248,11 @@ public class SimulationBuilder {
System.out.println("Data saved to " + this.csv);
}
}
/**
* Inner class to handle the type of simulation.
*/
private static enum Type {
Incremental, Parallel, Normal
}
}

View File

@@ -0,0 +1,150 @@
package net.berack.upo.valpre.sim;
import java.lang.reflect.Field;
import java.util.ArrayList;
import net.berack.upo.valpre.sim.stats.NodeStats;
import net.berack.upo.valpre.sim.stats.Result;
/**
* Confidence indices for a simulation.
* This class is used to store the confidence indices for a simulation.
* The confidence indices are used to determine when the simulation has
* reached a certain level of confidence.
*/
public class ConfidenceIndices {
private final String[] nodes;
private final NodeStats[] confidences;
private final NodeStats[] relativeErrors;
private boolean isEmpty = true;
/**
* Create a new confidence indices object for the given network.
*
* @param net the network to create the confidence indices for
*/
public ConfidenceIndices(Net net) {
var size = net.size();
this.nodes = new String[size];
this.confidences = new NodeStats[size];
this.relativeErrors = new NodeStats[size];
for (var i = 0; i < size; i++) {
this.nodes[i] = net.getNode(i).name;
this.confidences[i] = new NodeStats();
this.relativeErrors[i] = new NodeStats();
this.relativeErrors[i].apply(_ -> 1.0);
}
}
/**
* Get the number of confidence indices added to the simulation.
*
* @return the number of confidence indices
*/
public boolean isEmpty() {
return this.isEmpty;
}
/**
* Add a confidence index to the simulation. The simulation will stop when the
* relative error of the confidence index is less than the given value.
*
* @param node The node to calculate the confidence index for.
* @param stat The statistic to calculate the confidence index for.
* @param confidence The confidence level of the confidence index.
* @param relError The relative error of the confidence index.
* @throws IllegalArgumentException If the node is invalid, the confidence is
* not between 0 and 1, or the relative error
* is not between 0 and 1.
* @throws IllegalArgumentException If the statistic is invalid.
*/
public void add(int node, String stat, double confidence, double relError) {
if (node < 0 || node >= this.nodes.length)
throw new IllegalArgumentException("Invalid node: " + node);
if (confidence <= 0 || confidence > 1)
throw new IllegalArgumentException("Confidence must be between 0 and 1");
if (relError <= 0 || relError > 1)
throw new IllegalArgumentException("Relative error must be between 0 and 1");
try {
Field field = NodeStats.class.getField(stat);
field.set(this.confidences[node], confidence);
field.set(this.relativeErrors[node], relError);
this.isEmpty = false;
} catch (Exception e) {
throw new IllegalArgumentException("Invalid statistic: " + stat);
}
}
/**
* Calculate the relative errors of the statistics of the network.
*
* @param summary the summary of the network statistics
* @return the relative errors of the statistics
*/
public NodeStats[] calcRelativeErrors(Result.Summary summary) {
var errors = new NodeStats[this.nodes.length];
for (var i = 0; i < this.confidences.length; i++) {
var node = this.nodes[i];
var stat = summary.getSummaryOf(node);
var confidence = this.confidences[i];
var relativeError = stat.calcError(confidence);
relativeError.merge(stat.average, (err, avg) -> err / avg);
errors[i] = relativeError;
}
return errors;
}
/**
* Check if the errors are within the confidence indices.
* The errors within the confidence indices are calculated using the
* {@link #calcRelativeErrors(Result.Summary)} method.
*
* @param errors the relative errors of the statistics
* @return true if the simulation is ok, false otherwise
*/
public boolean isOk(NodeStats[] errors) {
for (var i = 0; i < this.relativeErrors.length; i++) {
var error = errors[i].clone();
var relError = this.relativeErrors[i];
error.merge(relError, (err, rel) -> err - rel);
for (var value : error)
if (!value.isInfinite() && !value.isNaN() && value > 0)
return false;
}
return true;
}
/**
* Get the errors of the statistics of the network.
* The errors are calculated using the
* {@link #calcRelativeErrors(Result.Summary)} method.
* Each error is formatted as a string in the format: "node:stat=value".
*
* @param errors the relative errors of the statistics
* @return the errors of the statistics
*/
public String[] getIndices(NodeStats[] errors) {
var statistics = NodeStats.getOrderOfApply();
var retValues = new ArrayList<String>();
for (var i = 0; i < this.relativeErrors.length; i++) {
var error = errors[i].clone();
for (var stat : statistics) {
var err = error.of(stat);
if (!Double.isFinite(err))
continue;
retValues.add("%s:%s=%.3f".formatted(this.nodes[i], stat, err));
}
}
return retValues.toArray(new String[0]);
}
}

View File

@@ -12,45 +12,6 @@ public interface EndCriteria {
*/
public boolean shouldEnd(Simulation run);
/**
* Parses the given string to create an array of end criteria.
* The string passed must be in the following format:
* [criteria1];[criteria2];...;[criteriaN]
*
* and each criteria must be in the following format:
* ClassName:param1,param2,...,paramN
*
* If the string is empty or null, an empty array is returned.
* If one of the criteria is not valid, an exception is thrown.
*
* @param criterias The string to parse.
* @return An array of end criteria.
* @throws IllegalArgumentException If one of the criteria is not valid.
*/
public static EndCriteria[] parse(String criterias) {
if (criterias == null || criterias.isEmpty())
return new EndCriteria[0];
var criteria = criterias.split(";");
var endCriteria = new EndCriteria[criteria.length];
for (int i = 0; i < criteria.length; i++) {
var current = criteria[i].substring(1, criteria[i].length() - 1); // Remove the brackets
var parts = current.split(":");
if (parts.length != 2)
throw new IllegalArgumentException("Invalid criteria: " + current);
var className = parts[0];
var params = parts[1].split(",");
endCriteria[i] = switch (className) {
case "MaxArrivals" -> new MaxArrivals(params[0], Integer.parseInt(params[1]));
case "MaxDepartures" -> new MaxDepartures(params[0], Integer.parseInt(params[1]));
case "MaxTime" -> new MaxTime(Double.parseDouble(params[0]));
default -> throw new IllegalArgumentException("Invalid criteria: " + current);
};
}
return endCriteria;
}
/**
* Ends the simulation when the given node has reached the specified number of
* arrivals.

View File

@@ -6,7 +6,7 @@ package net.berack.upo.valpre.sim;
public class Event implements Comparable<Event> {
public final double time;
public final Type type;
public final ServerNode node;
public final int nodeIndex;
/**
* Create a new event.
@@ -15,10 +15,10 @@ public class Event implements Comparable<Event> {
* @param node The node that the event is associated with.
* @param time The time at which the event occurs.
*/
private Event(Type type, ServerNode node, double time) {
private Event(Type type, int node, double time) {
this.type = type;
this.time = time;
this.node = node;
this.nodeIndex = node;
}
@Override
@@ -37,7 +37,7 @@ public class Event implements Comparable<Event> {
* @param time The time at which the event occurs.
* @return The new event.
*/
public static Event newArrival(ServerNode node, double time) {
public static Event newArrival(int node, double time) {
return new Event(Type.ARRIVAL, node, time);
}
@@ -48,7 +48,7 @@ public class Event implements Comparable<Event> {
* @param time The time at which the event occurs.
* @return The new event.
*/
public static Event newDeparture(ServerNode node, double time) {
public static Event newDeparture(int node, double time) {
return new Event(Type.DEPARTURE, node, time);
}
@@ -59,7 +59,7 @@ public class Event implements Comparable<Event> {
* @param time The time at which the event occurs.
* @return The new event.
*/
public static Event newAvailable(ServerNode node, double time) {
public static Event newAvailable(int node, double time) {
return new Event(Type.AVAILABLE, node, time);
}

View File

@@ -17,8 +17,6 @@ import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import net.berack.upo.valpre.rand.Rng;
/**
* A class that represents a network of queues, each with its own servers.
* The network in question is created by adding a node and then establishing
@@ -77,6 +75,7 @@ public final class Net implements Iterable<ServerNode> {
* @param weight The probability of the child node.
* @throws IndexOutOfBoundsException if one of the two nodes are not in the net
* @throws IllegalArgumentException if the weight is negative or zero
* @throws IllegalArgumentException if the child is a source node
*/
public void addConnection(int parent, int child, double weight) {
if (weight <= 0)
@@ -86,13 +85,11 @@ public final class Net implements Iterable<ServerNode> {
if (parent < 0 || child < 0 || parent > max || child > max)
throw new IndexOutOfBoundsException("One of the nodes does not exist");
if (this.servers.get(child).spawnArrivals > 0)
throw new IllegalArgumentException("Can't connect to a source node");
var list = this.connections.get(parent);
for (var conn : list) {
if (conn.index == child) {
conn.weight = weight;
return;
}
}
list.removeIf(conn -> conn.index == child);
list.add(new Connection(child, weight));
}
@@ -136,57 +133,12 @@ public final class Net implements Iterable<ServerNode> {
*
* @param index the index of the node
* @return the node
* @throws IndexOutOfBoundsException if the index is not in the range
*/
public ServerNode getNode(int index) {
return this.servers.get(index);
}
/**
* Get one of the child nodes from the parent specified.
* If the node has no child then null is returned.
*
* @param parent the parent node
* @param rng the random number generator used for getting one of the child
* @return the resultig node
*/
public ServerNode getChildOf(ServerNode parent, Rng rng) {
var index = this.indices.get(parent);
return this.getChildOf(index, rng);
}
/**
* Get one of the child nodes from the parent specified. If the index is out of
* bounds then an exception is thrown. If the node has no child then null is
* returned;
*
* @param parent the parent node
* @param rng the random number generator used for getting one of the child
* @throws IndexOutOfBoundsException If the index is not in the range
* @return the resultig node
*/
public ServerNode getChildOf(int parent, Rng rng) {
var random = rng.random();
for (var conn : this.connections.get(parent)) {
random -= conn.weight;
if (random <= 0) {
return this.servers.get(conn.index);
}
}
return null;
}
/**
* Get a list of all the children of the parent.
* In the list there is the node and the weight associated with.
*
* @param parent the parent node
* @return the list of children
*/
public List<NetChild> getChildren(ServerNode parent) {
var index = this.indices.get(parent);
return this.getChildren(index);
}
/**
* Get a list of all the children of the parent.
* In the list there is the node and the weight associated with.
@@ -195,11 +147,10 @@ public final class Net implements Iterable<ServerNode> {
* @throws IndexOutOfBoundsException If the index is not in the range
* @return the resultig node
*/
public List<NetChild> getChildren(int parent) {
var children = new ArrayList<NetChild>();
public List<Connection> getChildren(int parent) {
var children = new ArrayList<Connection>();
for (var conn : this.connections.get(parent)) {
var child = this.servers.get(conn.index);
var listEntry = new NetChild(child, conn.weight);
var listEntry = new Connection(conn.index, conn.weight);
children.add(listEntry);
}
@@ -212,15 +163,38 @@ public final class Net implements Iterable<ServerNode> {
* are not summing to 1 or are unsure.
*/
public void normalizeWeights() {
for (var list : this.connections) {
for (var node = 0; node < this.connections.size(); node++) {
var list = this.connections.get(node);
var sum = 0.0d;
for (var conn : list)
sum += conn.weight;
for (var conn : list)
conn.weight /= sum;
var newOne = new Connection[list.size()];
for (var i = 0; i < list.size(); i++) {
var conn = list.get(i);
var newWeight = conn.weight / sum;
newOne[i] = new Connection(conn.index, newWeight);
}
this.connections.set(node, List.of(newOne));
}
}
/**
* Build the node states for the simulation.
* This method is used to create the state of each node in the network.
* Note that each call to this method will create a new state for each node.
*
* @return the array of node states
*/
public ServerNodeState[] buildNodeStates() {
var states = new ServerNodeState[this.servers.size()];
for (var i = 0; i < states.length; i++)
states[i] = new ServerNodeState(i, this);
return states;
}
/**
* Save the current net to a file.
* The resulting file is saved with Kryo.
@@ -237,6 +211,11 @@ public final class Net implements Iterable<ServerNode> {
}
}
@Override
public Iterator<ServerNode> iterator() {
return this.servers.iterator();
}
/**
* Load the net from the file passed as input.
* The net will be the same as the one saved.
@@ -271,33 +250,15 @@ public final class Net implements Iterable<ServerNode> {
}
/**
* A static inner class used to represent the connection between two nodes
* A Static inner class used to represent the connection of a node
*/
public static class Connection {
public final int index;
public double weight;
public final double weight;
private Connection(int index, double weight) {
this.index = index;
this.weight = weight;
}
}
/**
* A Static inner class used to represent the connection of a node
*/
public static class NetChild {
public final ServerNode child;
public final double weight;
private NetChild(ServerNode child, double weight) {
this.child = child;
this.weight = weight;
}
}
@Override
public Iterator<ServerNode> iterator() {
return this.servers.iterator();
}
}

View File

@@ -9,88 +9,42 @@ import net.berack.upo.valpre.rand.Rng;
*/
public class ServerNode {
public final String name;
public final int maxQueue;
public final int maxServers;
public final int spawnArrivals;
public final Distribution service;
public final Distribution unavailable;
/**
* Creates a source node with the given name and distribution.
* It swpawns infinite arrivals (Integer.MAX_VALUE) that are served by infinite
* servers (Integer.MAX_VALUE).
*
* @param name The name of the node.
* @param distribution The distribution of the inter-arrival times.
* @return The created source node.
*/
public static ServerNode createSource(String name, Distribution distribution) {
return new ServerNode(name, Integer.MAX_VALUE, distribution, null, Integer.MAX_VALUE);
}
/**
* Creates a source node with the given name, distribution, and number of
* arrivals to spawn that are served by infinite servers (Integer.MAX_VALUE).
*
* @param name The name of the node.
* @param service The distribution of the inter-arrival times.
* @param spawnArrivals The number of arrivals to spawn.
* @return The created source node.
*/
public static ServerNode createLimitedSource(String name, Distribution service, int spawnArrivals) {
return new ServerNode(name, Integer.MAX_VALUE, service, null, spawnArrivals);
}
/**
* Creates a queue node with the given name, maximum number of servers, and
* distribution.
*
* @param name The name of the node.
* @param maxServers The maximum number of servers in the queue.
* @param service The distribution of the service times.
* @return The created queue node.
*/
public static ServerNode createQueue(String name, int maxServers, Distribution service) {
return new ServerNode(name, maxServers, service, null, 0);
}
/**
* Creates a queue node with the given name, maximum number of servers, and
* distribution.
*
* @param name The name of the node.
* @param maxServers The maximum number of servers in the queue.
* @param service The distribution of the service times.
* @param unavailable The distribution of the unavailable times after service.
* @return The created queue node.
*/
public static ServerNode createQueue(String name, int maxServers, Distribution service, Distribution unavailable) {
return new ServerNode(name, maxServers, service, unavailable, 0);
}
/**
* Creates a generic node with the given name and distribution.
* The servers number must be 1 or higher; if lower will be put to 1.
* The spawn number must be 0 or higher; if lower will be put to 0.
* The distribution can't be null, otherwise an exception is thrown.
* The queue number must be equal or higher than the servers number; if lower
* will be put to the servers number.
* The service distribution can't be null, otherwise an exception is thrown.
*
* @param name The name of the node.
* @param maxServers The maximum number of servers in the queue.
* @param service The distribution of the service times.
* @param unavailable The distribution of the unavailable times after service.
* @param spawnArrivals The number of arrivals to spawn.
* @param name The name of the node.
* @param servers The maximum number of servers in the queue.
* @param spawn The number of arrivals to spawn.
* @param queue The maximum number of requests in the queue.
* @param service The distribution of the service times.
* @param unavailable The distribution of the unavailable times after service.
* @throws NullPointerException if the distribution is null
*/
private ServerNode(String name, int maxServers, Distribution service, Distribution unavailable, int spawnArrivals) {
private ServerNode(String name, int servers, int spawn, int queue, Distribution service, Distribution unavailable) {
if (service == null)
throw new NullPointerException("Service distribution can't be null");
if (maxServers <= 0)
maxServers = 1;
if (spawnArrivals < 0)
spawnArrivals = 0;
if (servers <= 0)
servers = 1;
if (spawn < 0)
spawn = 0;
if (queue < servers)
queue = servers;
this.name = name;
this.maxServers = maxServers;
this.spawnArrivals = spawnArrivals;
this.maxQueue = queue;
this.maxServers = servers;
this.spawnArrivals = spawn;
this.service = service;
this.unavailable = unavailable;
}
@@ -117,17 +71,6 @@ public class ServerNode {
return Distribution.getPositiveSample(this.unavailable, rng);
}
/**
* Determines if the node should spawn an arrival based on the number of
* arrivals.
*
* @param numArrivals The number of arrivals to check against.
* @return True if the node should spawn an arrival, false otherwise.
*/
public boolean shouldSpawnArrival(double numArrivals) {
return this.spawnArrivals > Math.max(0, numArrivals);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ServerNode))
@@ -140,4 +83,155 @@ public class ServerNode {
public int hashCode() {
return this.name.hashCode();
}
/**
* Creates a new builder for the node.
* It is useful to create a node with a more readable syntax and a in a more
* flexible way.
*/
public static class Builder {
private String name;
private int maxQueue;
private int maxServers;
private int spawnArrivals;
private Distribution service;
private Distribution unavailable;
/**
* Creates a new builder for the node with the given name and distribution.
* The maximum number of servers is set to 1, the maximum number of requests in
* the queue is set to 100, the number of arrivals to spawn is set to 0, and
* the unavailable time is set to null.
*
* @param name The name of the node.
* @param service The distribution of the service times.
* @return The created sink node.
*/
public Builder(String name, Distribution service) {
this.reset(name, service);
}
/**
* Reset the builder with the given name and distribution.
*
* @param name The name of the node.
* @param service The distribution of the service times.
* @return The builder itself.
*/
public Builder reset(String name, Distribution service) {
this.name = name;
this.service = service;
this.maxQueue = 100; // default value
this.maxServers = 1;
this.spawnArrivals = 0;
this.unavailable = null;
return this;
}
/**
* Set the maximum number of clients in the queue.
*
* @param maxQueue The maximum number of clients in the queue.
* @return The builder itself.
*/
public Builder queue(int maxQueue) {
this.maxQueue = maxQueue;
return this;
}
/**
* Set the maximum number of servers.
*
* @param maxServers The maximum number of servers.
* @return The builder itself.
*/
public Builder servers(int maxServers) {
this.maxServers = maxServers;
return this;
}
/**
* Set the number of arrivals to spawn.
*
* @param spawnArrivals The number of arrivals to spawn.
* @return The builder itself.
*/
public Builder spawn(int spawnArrivals) {
this.spawnArrivals = spawnArrivals;
return this;
}
/**
* Set the distribution of the unavailable times after service.
*
* @param unavailable The distribution of the unavailable times after service.
* @return The builder itself.
*/
public Builder unavailable(Distribution unavailable) {
this.unavailable = unavailable;
return this;
}
/**
* Build the node with the given parameters.
*
* @return The created node.
*/
public ServerNode build() {
return new ServerNode(name, maxServers, spawnArrivals, maxQueue, service, unavailable);
}
/**
* Creates a source node with the given name and distribution.
* It swpawns infinite arrivals (Integer.MAX_VALUE).
*
* @param name The name of the node.
* @param distribution The distribution of the inter-arrival times.
* @return The created source node.
*/
public static ServerNode source(String name, Distribution distribution) {
return new Builder(name, distribution).spawn(Integer.MAX_VALUE).build();
}
/**
* Creates a source node with the given name, distribution, and number of
* arrivals to spawn.
*
* @param name The name of the node.
* @param service The distribution of the inter-arrival times.
* @param spawnArrivals The number of arrivals to spawn.
* @return The created source node.
*/
public static ServerNode sourceLimited(String name, int spawnArrivals, Distribution service) {
return new Builder(name, service).spawn(spawnArrivals).build();
}
/**
* Creates a queue node with the given name, maximum number of servers, and
* distribution.
*
* @param name The name of the node.
* @param maxServers The maximum number of servers in the queue.
* @param service The distribution of the service times.
* @return The created queue node.
*/
public static ServerNode queue(String name, int maxServers, Distribution service) {
return new Builder(name, service).servers(maxServers).build();
}
/**
* Creates a queue node with the given name, maximum number of servers, and
* distribution. It also has a distribution for the unavailable times after
* each service.
*
* @param name The name of the node.
* @param maxServers The maximum number of servers in the queue.
* @param service The distribution of the service times.
* @param unavailable The distribution of the unavailable times after service.
* @return The created queue node.
*/
public static ServerNode queue(String name, int maxServers, Distribution service, Distribution unavailable) {
return new Builder(name, service).unavailable(unavailable).servers(maxServers).build();
}
}
}

View File

@@ -0,0 +1,179 @@
package net.berack.upo.valpre.sim;
import java.util.ArrayDeque;
import java.util.List;
import net.berack.upo.valpre.rand.Rng;
import net.berack.upo.valpre.sim.Net.Connection;
import net.berack.upo.valpre.sim.stats.NodeStats;
/**
* Represents a summary of the state of a server node in the network.
* It is used by the simulation to track the number of arrivals and departures,
* the maximum queue length, the busy time, and the response time.
* It also has a connection to the node and the net where it is.
*/
public class ServerNodeState {
public int numServerBusy = 0;
public int numServerUnavailable = 0;
public final ArrayDeque<Double> queue = new ArrayDeque<>();
public final int index;
public final ServerNode node;
public final NodeStats stats = new NodeStats();
public final List<Connection> children;
/**
* Create a new node state based on the index and the net passed as input
*
* @param index the index of the node
* @param net the net where the node is
*/
ServerNodeState(int index, Net net) {
this.index = index;
this.node = net.getNode(index);
this.children = net.getChildren(index);
}
/**
* Check if the queue is full based on the maximum queue length of the node
*
* @return true if the queue is full
*/
public boolean isQueueFull() {
return this.queue.size() >= this.node.maxQueue;
}
/**
* Check if the node can serve a new request based on the number of servers
*
* @return true if the node can serve
*/
public boolean canServe() {
return this.node.maxServers > this.numServerBusy + this.numServerUnavailable;
}
/**
* Check if the node has requests to serve based on the number of busy servers
*
* @return true if the node has requests
*/
public boolean hasRequests() {
return this.queue.size() > this.numServerBusy;
}
/**
* Determines if the node should spawn an arrival based on the number of
* arrivals.
*
* @return True if the node should spawn an arrival, false otherwise.
*/
public boolean shouldSpawnArrival() {
return this.node.spawnArrivals > this.stats.numArrivals;
}
/**
* Update stats and queue when an unavailability event finish. The
* unavailability
* time is the time of the event.
*
* @param time the time of the event
*/
public void updateAvailable(double time) {
this.stats.updateTimes(time, this.numServerBusy, this.numServerUnavailable, this.node.maxServers);
this.numServerUnavailable--;
}
/**
* Update stats and queue when an arrival event occurs. The arrival time is the
* time of the event.
*
* @param time the time of the event
*/
public void updateArrival(double time) {
this.queue.add(time);
this.stats.updateArrival(time, this.queue.size());
this.stats.updateTimes(time, this.numServerBusy, this.numServerUnavailable, node.maxServers);
}
/**
* Update stats and queue when a departure event occurs. The departure time is
* the time of the event.
*
* @param time the time of the event
*/
public void updateDeparture(double time) {
var arrivalTime = this.queue.poll();
this.stats.updateDeparture(time, arrivalTime);
this.stats.updateTimes(time, this.numServerBusy, this.numServerUnavailable, node.maxServers);
this.numServerBusy--;
}
/**
* Create an arrival event based on the node and the time passed as input
*
* @param time the time of the event
* @return the arrival event
*/
public Event spawnArrivalIfPossilbe(double time) {
if (this.shouldSpawnArrival())
return Event.newArrival(this.index, time);
return null;
}
/**
* Create a departure event if the node can serve and has requests. The event is
* created based on the node and the delay is determined by the node's service
* time distribution.
*
* @param time the time of the event
* @param rng the random number generator
* @return the departure event if the node can serve and has requests, null
* otherwise
*/
public Event spawnDepartureIfPossible(double time, Rng rng) {
if (this.canServe() && this.hasRequests()) {
this.numServerBusy++;
var delay = node.getServiceTime(rng);
return Event.newDeparture(this.index, time + delay);
}
return null;
}
/**
* Create an unavailable event if the node is unavailable. The event is created
* based on the given node, and the delay is determined by the node's
* unavailability distribution.
*
* @param time The time of the event
* @param rng The random number generator
* @return The event if the node is unavailable, null otherwise
*/
public Event spawnUnavailableIfPossible(double time, Rng rng) {
var delay = node.getUnavailableTime(rng);
if (delay > 0) {
this.numServerUnavailable++;
return Event.newAvailable(this.index, time + delay);
}
return null;
}
/**
* Create an arrival event to a child node based on the node and the time passed
* as input
*
* @param time the time of the event
* @param rng the random number generator
* @return the arrival event to a child node if the node has children, null
* otherwise
*/
public Event spawnArrivalToChild(double time, Rng rng) {
var random = rng.random();
for (var child : this.children) {
random -= child.weight;
if (random <= 0)
return Event.newArrival(child.index, time);
}
return null;
}
}

View File

@@ -1,10 +1,8 @@
package net.berack.upo.valpre.sim;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import net.berack.upo.valpre.rand.Rng;
@@ -20,39 +18,42 @@ public final class Simulation {
public final EndCriteria[] criterias;
public final long seed;
private final Net net;
private final Map<String, NodeState> states;
private final ServerNodeState[] states;
private final PriorityQueue<Event> fel;
private double time = 0.0d;
private long eventProcessed = 0;
/**
* Creates a new run of the simulation with the given nodes and random number
* generator.
* Creates a new simulation for the given network.
* The random number generator is used to generate random numbers for the
* simulation.
* The simulation will end when the given criteria are met.
* NOTE: the network passed is only used to create the initial states of the
* nodes, so the simulation is not affected by changes to the network after
* the creation of this object.
*
* @param states The nodes in the network.
* @param net The network to simulate.
* @param rng The random number generator to use.
* @param criterias when the simulation has to end.
*/
public Simulation(Net net, Rng rng, EndCriteria... criterias) {
this.timeStartedNano = System.nanoTime();
this.net = net;
this.states = new HashMap<>();
this.states = net.buildNodeStates();
this.fel = new PriorityQueue<>();
this.criterias = criterias;
this.seed = rng.getSeed();
this.rng = rng;
boolean hasLimit = false;
for (var node : net) {
for (var state : this.states) {
var node = state.node;
// check for ending criteria in simulation
if (node.spawnArrivals != Integer.MAX_VALUE)
hasLimit = true;
// Initial arrivals (if spawned)
this.states.put(node.name, new NodeState());
if (node.shouldSpawnArrival(0))
this.addArrival(node);
this.addToFel(state.spawnArrivalIfPossilbe(0.0d));
}
if (!hasLimit && (criterias == null || criterias.length == 0))
@@ -83,38 +84,30 @@ public final class Simulation {
if (event == null)
throw new NullPointerException("No more events to process!");
var node = event.node;
var state = this.states.get(node.name);
var state = this.states[event.nodeIndex];
this.time = event.time;
this.eventProcessed += 1;
switch (event.type) {
case AVAILABLE -> {
state.stats.updateTimes(this.time, state.numServerBusy, state.numServerUnavailable, node.maxServers);
state.numServerUnavailable--;
this.addDepartureIfPossible(node, state);
state.updateAvailable(time);
this.addToFel(state.spawnDepartureIfPossible(time, this.rng));
}
case ARRIVAL -> {
state.queue.add(this.time);
state.stats.updateArrival(this.time, state.queue.size());
state.stats.updateTimes(this.time, state.numServerBusy, state.numServerUnavailable, node.maxServers);
this.addDepartureIfPossible(node, state);
state.updateArrival(time);
this.addToFel(state.spawnDepartureIfPossible(time, this.rng));
}
case DEPARTURE -> {
var arrivalTime = state.queue.poll();
state.stats.updateDeparture(this.time, arrivalTime);
state.stats.updateTimes(this.time, state.numServerBusy, state.numServerUnavailable, node.maxServers);
state.numServerBusy--;
state.updateDeparture(time);
this.addUnavailableIfPossible(node, state);
this.addDepartureIfPossible(node, state);
this.addToFel(state.spawnUnavailableIfPossible(time, this.rng));
this.addToFel(state.spawnDepartureIfPossible(time, this.rng));
this.addToFel(state.spawnArrivalIfPossilbe(time));
var next = this.net.getChildOf(node, this.rng);
if (next != null)
this.addArrival(next);
if (node.shouldSpawnArrival(state.stats.numArrivals))
this.addArrival(node);
// Spawn arrival to child node if queue is not full otherwise drop
var ev = state.spawnArrivalToChild(time, this.rng);
if (ev != null && !this.states[ev.nodeIndex].isQueueFull())
this.addToFel(ev);
}
}
}
@@ -127,8 +120,8 @@ public final class Simulation {
public Result endSimulation() {
var elapsed = System.nanoTime() - this.timeStartedNano;
var nodes = new HashMap<String, NodeStats>();
for (var entry : this.states.entrySet())
nodes.put(entry.getKey(), entry.getValue().stats);
for (var state : this.states)
nodes.put(state.node.name, state.stats);
return new Result(this.seed, this.time, elapsed * 1e-6, nodes);
}
@@ -166,9 +159,10 @@ public final class Simulation {
*
* @param node the name of the node
* @return the node
* @throws NullPointerException if the node does not exist.
*/
public ServerNode getNode(String node) {
return this.net.getNode(node);
return this.getNodeState(node).node;
}
/**
@@ -176,55 +170,26 @@ public final class Simulation {
*
* @param node the name of the node
* @return the current state of the node
* @throws NullPointerException if the node does not exist.
*/
public NodeState getNodeState(String node) {
return this.states.get(node);
}
/**
* Adds an arrival event to the future event list. The event is created based
* on the given node, and no delay is added.
*
* @param node The node to create the event for.
*/
public void addArrival(ServerNode node) {
var event = Event.newArrival(node, this.time);
fel.add(event);
}
/**
* Adds a departure event to the future event list. The event is created based
* on the given node, and the delay is determined by the node's service
* distribution.
*
* @param node The node to create the event for.
* @param state The current state of the node
*/
public void addDepartureIfPossible(ServerNode node, NodeState state) {
var canServe = node.maxServers > state.numServerBusy + state.numServerUnavailable;
var hasRequests = state.queue.size() > state.numServerBusy;
if (canServe && hasRequests) {
state.numServerBusy++;
var delay = node.getServiceTime(this.rng);
var event = Event.newDeparture(node, this.time + delay);
fel.add(event);
public ServerNodeState getNodeState(String node) {
for (var state : this.states) {
if (state.node.name.equals(node))
return state;
}
throw new NullPointerException("Node not found: " + node);
}
/**
* Add an AVAILABLE event in the case that the node has an unavailability time.
* Add an arrival event to the future event list if the event is not null,
* otherwise do nothing.
*
* @param node The node to create the event for
* @param state The current state of the node
* @param e the event to add
*/
public void addUnavailableIfPossible(ServerNode node, NodeState state) {
var delay = node.getUnavailableTime(rng);
if (delay > 0) {
state.numServerUnavailable++;
var event = Event.newAvailable(node, time + delay);
this.fel.add(event);
}
public void addToFel(Event e) {
if (e != null)
this.fel.add(e);
}
/**
@@ -243,16 +208,4 @@ public final class Simulation {
}
return false;
}
/**
* Represents a summary of the state of a server node in the network.
* It is used by the simulation to track the number of arrivals and departures,
* the maximum queue length, the busy time, and the response time.
*/
public static class NodeState {
public int numServerBusy = 0;
public int numServerUnavailable = 0;
public final NodeStats stats = new NodeStats();
public final ArrayDeque<Double> queue = new ArrayDeque<>();
}
}

View File

@@ -87,4 +87,50 @@ public class SimulationMultiple {
return results;
}
}
/**
* Run the simulation multiple times with the given seed and end criteria. The
* simulation runs will stop when the relative error of the confidence index is
* less than the given value.
* The results are printed on the console.
*
* @param seed The seed to use for the random number generator.
* @param criterias The criteria to determine when to end the simulation. If
* null then the simulation will run until there are no more
* events.
* @return The statistics the network.
* @throws IllegalArgumentException If the confidence is not set.
*/
public Result.Summary runIncremental(long seed, int runs, ConfidenceIndices confidences, EndCriteria... criterias) {
if (confidences == null)
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 output = new StringBuilder();
var stop = false;
for (int i = 0; !stop; i++) {
var sim = new Simulation(this.net, rng, criterias);
var result = sim.run();
results.add(result);
if (i > 0) {
output.setLength(0);
output.append(String.format("\rSimulation [%6d]: ", i + 1));
var errors = confidences.calcRelativeErrors(results);
stop = confidences.isOk(errors);
var errString = confidences.getIndices(errors);
var oneSting = String.join("], [", errString);
output.append('[').append(oneSting).append("]");
System.out.print(output);
}
}
System.out.println(); // remove last printed line
return results;
}
}

View File

@@ -9,7 +9,6 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This class is used to save the results of the simulation to a CSV file.
@@ -44,7 +43,7 @@ public class CsvResult {
try (var writer = new FileWriter(this.file)) {
for (var result : results) {
for (var entry : result.nodes.entrySet()) {
for (var entry : result) {
builder.append(result.seed).append(",");
builder.append(entry.getKey()).append(",");
builder.append(CsvResult.statsToCSV(entry.getValue())).append('\n');
@@ -75,8 +74,7 @@ public class CsvResult {
public static List<Result> loadResults(InputStream input) {
var results = new ArrayList<Result>();
try (var scan = new Scanner(input)) {
var _ = scan.nextLine();
var headerOrder = CsvResult.extractHeaderPositions(scan.nextLine());
var nodes = new HashMap<String, NodeStats>();
var seed = 0L;
@@ -92,7 +90,7 @@ public class CsvResult {
seed = currentSeed;
var copy = Arrays.copyOfRange(line, 2, line.length);
var stats = CsvResult.statsFromCSV(copy);
var stats = CsvResult.statsFromCSV(headerOrder, copy);
nodes.put(node, stats);
}
@@ -124,11 +122,50 @@ public class CsvResult {
* @param values the values to convert
* @return the statistics object
*/
public static NodeStats statsFromCSV(String[] values) {
var i = new AtomicInteger(0);
var stats = new NodeStats();
stats.apply(_ -> Double.parseDouble(values[i.getAndIncrement()]));
return stats;
public static NodeStats statsFromCSV(String[] header, String[] values) {
try {
var stats = new NodeStats();
var clazz = NodeStats.class;
for (var i = 0; i < values.length; i++) {
var value = Double.parseDouble(values[i]);
var field = header[i];
clazz.getField(field).setDouble(stats, value);
}
return stats;
} catch (Exception e) {
throw new IllegalArgumentException("Error while parsing the CSV file " + e.getMessage());
}
}
/**
* Extract the header order from the CSV file.
*
* @param header the header to extract
* @return the positions of the header
* @throws IllegalArgumentException if the header is not correct
*/
public static String[] extractHeaderPositions(String header) {
var splittedHeader = header.split(",");
var headerSeed = splittedHeader[0].equals("seed");
var headerNode = splittedHeader[1].equals("node");
if (!headerSeed || !headerNode)
throw new IllegalArgumentException("CSV file doesn't have the node or seed header");
var allStats = NodeStats.getOrderOfApply();
splittedHeader = Arrays.copyOfRange(splittedHeader, 2, splittedHeader.length);
if (splittedHeader.length > allStats.length)
throw new IllegalArgumentException("CSV file doesn't have the correct header [" + allStats + "]");
var order = new String[allStats.length];
var stats = Arrays.asList(allStats);
for (var i = 0; i < splittedHeader.length; i++) {
var stat = splittedHeader[i];
var index = stats.indexOf(stat);
order[i] = allStats[index];
}
return order;
}
}

View File

@@ -1,6 +1,7 @@
package net.berack.upo.valpre.sim.stats;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -14,18 +15,22 @@ import org.apache.commons.math3.distribution.TDistribution;
* statistics are updated during simulation events, such as arrivals and
* departures, and can be used to analyze the net's behavior and performance.
*/
public class NodeStats implements Cloneable {
public class NodeStats implements Cloneable, Iterable<Double> {
private static final String[] ORDER_OF_APPLY = { "numArrivals", "numDepartures", "maxQueueLength", "avgQueueLength",
"avgWaitTime", "avgResponse", "busyTime", "waitTime", "unavailableTime", "responseTime", "lastEventTime",
"throughput", "utilization", "unavailable" };
public double numArrivals = 0.0d;
public double numDepartures = 0.0d;
public double maxQueueLength = 0.0d;
public double avgQueueLength = 0.0d;
public double unavailableTime = 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 waitTime = 0.0d;
public double avgWaitTime = 0.0d;
public double avgResponse = 0.0d;
public double throughput = 0.0d;
@@ -95,8 +100,6 @@ public class NodeStats implements Cloneable {
/**
* Apply a function to ALL the stats in this class.
* The only stats that are not updated with this function are the one that
* starts with max, min (since they are special)
* The input of the function is the current value of the stat.
*
* @param func a function to apply
@@ -106,19 +109,17 @@ public class NodeStats implements Cloneable {
}
/**
* A function used to merge tree stats.
* The only stats that are not updated with this function are the one that
* starts with max, min (since they are special)
* A function used to merge two sets of statistics.
*
* @param other
* @param func
* @param other the other stats to merge
* @param func the function to merge the stats
*/
public NodeStats merge(NodeStats other, BiFunction<Double, Double, Double> func) {
return NodeStats.operation(this, this, other, func);
}
@Override
protected NodeStats clone() {
public NodeStats clone() {
try {
return (NodeStats) super.clone();
} catch (CloneNotSupportedException e) {
@@ -127,6 +128,23 @@ public class NodeStats implements Cloneable {
}
}
@Override
public Iterator<Double> iterator() {
return new Iterator<>() {
private int index = 0;
@Override
public boolean hasNext() {
return this.index < ORDER_OF_APPLY.length;
}
@Override
public Double next() {
return NodeStats.this.of(ORDER_OF_APPLY[this.index++]);
}
};
}
/**
* Get the value of the stat.
*
@@ -159,9 +177,7 @@ public class NodeStats implements Cloneable {
* @return the order of the stats
*/
public static String[] getOrderOfApply() {
return new String[] { "numArrivals", "numDepartures", "avgQueueLength", "avgWaitTime", "avgResponse",
"busyTime", "waitTime", "unavailableTime", "responseTime", "lastEventTime", "throughput", "utilization",
"unavailable" };
return ORDER_OF_APPLY;
}
/**
@@ -185,7 +201,7 @@ public class NodeStats implements Cloneable {
BiFunction<Double, Double, Double> func) {
save.numArrivals = func.apply(val1.numArrivals, val2.numArrivals);
save.numDepartures = func.apply(val1.numDepartures, val2.numDepartures);
// save.maxQueueLength = func.apply(val1.maxQueueLength, val2.maxQueueLength);
save.maxQueueLength = func.apply(val1.maxQueueLength, val2.maxQueueLength);
save.avgQueueLength = func.apply(val1.avgQueueLength, val2.avgQueueLength);
save.avgWaitTime = func.apply(val1.avgWaitTime, val2.avgWaitTime);
save.avgResponse = func.apply(val1.avgResponse, val2.avgResponse);
@@ -264,11 +280,27 @@ public class NodeStats implements Cloneable {
* @return the error of the values
*/
public NodeStats calcError(double alpha) {
return this.calcError(new NodeStats().apply(_ -> alpha));
}
/**
* Calculates the error at the selected alpha level for each NodeStats.
* 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 values for each statistics
* @return the error of the values
*/
public NodeStats calcError(NodeStats alpha) {
var n = this.stats.size();
var distr = new TDistribution(null, n - 1);
var tValue = distr.inverseCumulativeProbability(alpha);
var tValue = alpha.clone().apply(a -> distr.inverseCumulativeProbability(a));
return this.stdDev().apply(std -> tValue * (std / Math.sqrt(n)));
return this.stdDev().merge(tValue, (std, t) -> t * (std / Math.sqrt(n)));
}
/**

View File

@@ -5,6 +5,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Represents the statistics of a network simulation.
@@ -12,7 +13,7 @@ import java.util.Map;
* nodes, including the number of arrivals and departures, the maximum queue
* length, the busy time, and the response time.
*/
public class Result {
public class Result implements Iterable<Entry<String, NodeStats>> {
public final Map<String, NodeStats> nodes;
public final long seed;
public final double simulationTime;
@@ -39,6 +40,11 @@ public class Result {
return buildPrintable(this.seed, this.simulationTime, this.timeElapsedMS, this.nodes);
}
@Override
public java.util.Iterator<Entry<String, NodeStats>> iterator() {
return this.nodes.entrySet().iterator();
}
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";

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,186 @@
package net.berack.upo.valpre.sim;
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;
import com.esotericsoftware.kryo.KryoException;
import net.berack.upo.valpre.SimulationBuilder;
import net.berack.upo.valpre.rand.Distribution;
import net.berack.upo.valpre.rand.Rng;
import net.berack.upo.valpre.sim.stats.CsvResult;
import net.berack.upo.valpre.sim.stats.NodeStats;
public class TestSaveExamplesNet {
private static final Distribution exp0_22 = new Distribution.Exponential(1.0 / 4.5);
private static final Distribution exp2 = new Distribution.Exponential(2.0);
private static final Distribution exp1_5 = new Distribution.Exponential(1.5);
private static final Distribution exp3_5 = new Distribution.Exponential(3.5);
private static final Distribution exp10 = new Distribution.Exponential(10.0);
private static final Distribution norm3_2 = new Distribution.NormalBoxMuller(3.2, 0.6);
private static final Distribution norm4_2 = new Distribution.NormalBoxMuller(4.2, 0.6);
private static final Distribution unNorm = new Distribution.UnavailableTime(0.2, norm4_2);
private static final Distribution unExp = new Distribution.UnavailableTime(0.1, exp10);
private static final int spawn = 10000;
private static final String path = "src/main/resources/example%d.%s";
private static final String netFile1 = path.formatted(1, "net");
private static final String netFile2 = path.formatted(2, "net");
private static final String netFile3 = path.formatted(3, "net");
private static final String csv1 = path.formatted(1, "csv");
private static final String csv2 = path.formatted(2, "csv");
private static final String csv3 = path.formatted(3, "csv");
private static final Net net1 = new Net();
private static final Net net2 = new Net();
private static final Net net3 = new Net();
static {
net1.addNode(ServerNode.Builder.sourceLimited("Source", spawn, exp0_22));
net1.addNode(ServerNode.Builder.queue("Queue", 1, norm3_2));
net1.addConnection(0, 1, 1.0);
net2.addNode(ServerNode.Builder.sourceLimited("Source", spawn, exp0_22));
net2.addNode(ServerNode.Builder.queue("Queue", 1, norm3_2));
net2.addNode(ServerNode.Builder.queue("Queue Wait", 1, norm3_2, unNorm));
net2.addConnection(0, 1, 1.0);
net2.addConnection(1, 2, 1.0);
net3.addNode(ServerNode.Builder.sourceLimited("Source", spawn, exp1_5));
net3.addNode(ServerNode.Builder.queue("Service1", 1, exp2));
net3.addNode(ServerNode.Builder.queue("Service2", 1, exp3_5, unExp));
net3.addConnection(0, 1, 1.0);
net3.addConnection(1, 2, 1.0);
}
@BeforeAll
public void saveAll() throws IOException {
net1.save(netFile1);
net2.save(netFile2);
net3.save(netFile3);
}
@Test
public void loadExample1() throws KryoException, IOException {
var sim = new Simulation(Net.load(netFile1), new Rng());
var res = sim.run();
var time = 44782.0;
var maxErr = time / 1000.0;
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);
}
@Test
public void loadExample2() throws KryoException, IOException {
var sim = new Simulation(Net.load(netFile2), new Rng());
var res = sim.run();
var time = 45417.0;
var maxErr = time / 1000.0;
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);
}
@Test
public void loadExample3() throws KryoException, IOException {
var sim = new Simulation(Net.load(netFile3), new Rng());
var res = sim.run();
var time = 6736.0;
var maxErr = time / 1000.0;
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);
}
private void testNode(NodeStats stat, double numClients, double time, double avgQueue,
double avgResponse, double avgWait, double totalUnavailable) {
assertEquals("Num Arrivals", numClients, stat.numArrivals, 0.1);
assertEquals("Num Departures", numClients, stat.numDepartures, 0.1);
var maxErr = time / 1000.0;
assertEquals(time, stat.lastEventTime, maxErr);
assertEquals("Avg Queue", avgQueue, stat.avgQueueLength, 0.1);
assertEquals("Avg Wait", avgWait, stat.avgWaitTime, 0.1);
assertEquals("Avg Response", avgResponse, stat.avgResponse, 0.1);
var totalWait = numClients * stat.avgWaitTime;
var totalResponse = numClients * stat.avgResponse;
var totalBusy = totalResponse - totalWait;
assertEquals("Tot Wait", totalWait, stat.waitTime, maxErr);
assertEquals("Tot Response", totalResponse, stat.responseTime, maxErr);
assertEquals("Tot Busy", totalBusy, stat.busyTime, maxErr);
assertEquals("Tot Unavailable", totalUnavailable, stat.unavailableTime, maxErr);
assertEquals("Throughput", stat.numDepartures / stat.lastEventTime, stat.throughput, 0.001);
assertEquals("% Busy", stat.busyTime / stat.lastEventTime, stat.utilization, 0.001);
assertEquals("% Unavailable", stat.unavailableTime / stat.lastEventTime, stat.unavailable, 0.001);
}
@Test
public void loadCsv() throws IOException {
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);
seeds.add(element.seed);
}
assertEquals(list.size(), seeds.size());
}
@Test
@AfterAll
public void multiSimulation1() throws Exception {
new SimulationBuilder(net1)
.setCsv(csv1)
.setMaxRuns(1000)
.setSeed(2007539552L)
.setParallel(true)
.run();
}
@Test
@AfterAll
public void multiSimulation2() throws Exception {
new SimulationBuilder(net2)
.setCsv(csv2)
.setMaxRuns(1000)
.setSeed(2007539552L)
.setParallel(true)
.run();
}
@Test
@AfterAll
public void multiSimulation3() throws Exception {
new SimulationBuilder(net3)
.setCsv(csv3)
.setMaxRuns(1000)
.setSeed(2007539552L)
.setParallel(true)
.run();
}
}

View File

@@ -2,6 +2,8 @@ package net.berack.upo.valpre.sim;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.HashSet;
@@ -21,8 +23,8 @@ public class TestSimulation {
private static final ServerNode node0;
private static final ServerNode node1;
static {
node0 = ServerNode.createLimitedSource("First", const1, 0);
node1 = ServerNode.createQueue("Second", 1, const1);
node0 = ServerNode.Builder.sourceLimited("First", 0, const1);
node1 = ServerNode.Builder.queue("Second", 1, const1);
simpleNet = new Net();
simpleNet.addNode(node0);
@@ -39,64 +41,45 @@ public class TestSimulation {
@Test
public void serverNode() {
var node = ServerNode.createQueue("Nodo", 0, const1);
var node = ServerNode.Builder.queue("Nodo", 0, const1);
assertEquals("Nodo", node.name);
assertEquals(1, node.maxServers);
assertFalse(node.shouldSpawnArrival(0));
assertFalse(node.shouldSpawnArrival(50));
assertFalse(node.shouldSpawnArrival(1000));
assertFalse(node.shouldSpawnArrival(Integer.MAX_VALUE));
assertFalse(node.shouldSpawnArrival(-1));
assertEquals(0, node.spawnArrivals);
assertEquals(1.0, node.getServiceTime(null), DELTA);
node = ServerNode.createQueue("Queue", 50, const1);
node = ServerNode.Builder.queue("Queue", 50, const1);
assertEquals("Queue", node.name);
assertEquals(50, node.maxServers);
assertFalse(node.shouldSpawnArrival(0));
assertFalse(node.shouldSpawnArrival(50));
assertFalse(node.shouldSpawnArrival(1000));
assertFalse(node.shouldSpawnArrival(Integer.MAX_VALUE));
assertFalse(node.shouldSpawnArrival(-1));
assertEquals(0, node.spawnArrivals);
assertEquals(1.0, node.getServiceTime(null), DELTA);
node = ServerNode.createSource("Source", const1);
node = ServerNode.Builder.source("Source", const1);
assertEquals("Source", node.name);
assertEquals(Integer.MAX_VALUE, node.maxServers);
assertTrue(node.shouldSpawnArrival(0));
assertTrue(node.shouldSpawnArrival(50));
assertTrue(node.shouldSpawnArrival(1000));
assertTrue(node.shouldSpawnArrival(Integer.MAX_VALUE - 1));
assertFalse(node.shouldSpawnArrival(Integer.MAX_VALUE));
assertTrue(node.shouldSpawnArrival(-1));
assertEquals(1, node.maxServers);
assertEquals(Integer.MAX_VALUE, node.spawnArrivals);
assertEquals(1.0, node.getServiceTime(null), DELTA);
node = ServerNode.createLimitedSource("Source", const1, 50);
node = ServerNode.Builder.sourceLimited("Source", 50, const1);
assertEquals("Source", node.name);
assertEquals(Integer.MAX_VALUE, node.maxServers);
assertTrue(node.shouldSpawnArrival(0));
assertTrue(node.shouldSpawnArrival(49));
assertFalse(node.shouldSpawnArrival(50));
assertFalse(node.shouldSpawnArrival(1000));
assertFalse(node.shouldSpawnArrival(Integer.MAX_VALUE));
assertTrue(node.shouldSpawnArrival(-1));
assertEquals(1, node.maxServers);
assertEquals(50, node.spawnArrivals);
assertEquals(1.0, node.getServiceTime(null), DELTA);
}
@Test
public void event() {
var node = ServerNode.createSource("Source", const0);
var event = Event.newAvailable(node, 1.0);
assertEquals(node, event.node);
var event = Event.newAvailable(0, 1.0);
assertEquals(0, event.nodeIndex);
assertEquals(1.0, event.time, 0.000000000001);
assertEquals(Event.Type.AVAILABLE, event.type);
var event2 = Event.newArrival(node, 5.0);
assertEquals(node, event2.node);
var event2 = Event.newArrival(0, 5.0);
assertEquals(0, event2.nodeIndex);
assertEquals(5.0, event2.time, 0.000000000001);
assertEquals(Event.Type.ARRIVAL, event2.type);
var event3 = Event.newDeparture(node, 8.0);
assertEquals(node, event3.node);
var event3 = Event.newDeparture(1, 8.0);
assertEquals(1, event3.nodeIndex);
assertEquals(8.0, event3.time, 0.000000000001);
assertEquals(Event.Type.DEPARTURE, event3.type);
@@ -110,7 +93,7 @@ public class TestSimulation {
var net = new Net();
assertEquals(0, net.size());
var node = ServerNode.createSource("First", const0);
var node = ServerNode.Builder.source("First", const0);
var index = net.addNode(node);
assertEquals(1, net.size());
assertEquals(0, index);
@@ -118,7 +101,7 @@ public class TestSimulation {
assertEquals(node, net.getNode("First"));
assertEquals(index, net.getNodeIndex("First"));
var node1 = ServerNode.createQueue("Second", 1, const0);
var node1 = ServerNode.Builder.queue("Second", 1, const0);
var index1 = net.addNode(node1);
assertEquals(2, net.size());
assertEquals(0, index);
@@ -138,18 +121,18 @@ public class TestSimulation {
net.addConnection(0, 1, 1.0);
var conn = net.getChildren(0);
assertEquals(1, conn.size());
assertEquals(node1, conn.get(0).child);
assertEquals(1, conn.get(0).index);
assertEquals(1.0, conn.get(0).weight, DELTA);
conn = net.getChildren(1);
assertEquals(0, conn.size());
var node2 = ServerNode.createQueue("Third", 1, const0);
var node2 = ServerNode.Builder.queue("Third", 1, const0);
net.addNode(node2);
net.addConnection(0, 2, 1.0);
conn = net.getChildren(0);
assertEquals(2, conn.size());
assertEquals(node1, conn.get(0).child);
assertEquals(node2, conn.get(1).child);
assertEquals(1, conn.get(0).index);
assertEquals(2, conn.get(1).index);
assertEquals(1.0, conn.get(0).weight, DELTA);
assertEquals(1.0, conn.get(1).weight, DELTA);
conn = net.getChildren(1);
@@ -160,19 +143,128 @@ public class TestSimulation {
net.normalizeWeights();
conn = net.getChildren(0);
assertEquals(2, conn.size());
assertEquals(node1, conn.get(0).child);
assertEquals(node2, conn.get(1).child);
assertEquals(1, conn.get(0).index);
assertEquals(2, conn.get(1).index);
assertEquals(0.5, conn.get(0).weight, DELTA);
assertEquals(0.5, conn.get(1).weight, DELTA);
conn = net.getChildren(1);
assertEquals(0, conn.size());
conn = net.getChildren(2);
assertEquals(0, conn.size());
}
var sample = net.getChildOf(0, rigged);
assertEquals(node1, sample);
sample = net.getChildOf(node, rigged);
assertEquals(node1, sample);
@Test
public void nodeState() {
var state = new ServerNodeState(1, simpleNet);
assertEquals(1, state.index);
assertEquals(node1, state.node);
assertEquals(0, state.numServerBusy);
assertEquals(0, state.numServerUnavailable);
assertEquals(0, state.queue.size());
assertFalse(state.isQueueFull());
assertTrue(state.canServe());
assertFalse(state.hasRequests());
assertFalse(state.shouldSpawnArrival());
state.numServerBusy = 1;
assertEquals(1, state.numServerBusy);
assertFalse(state.canServe());
assertFalse(state.hasRequests());
state.numServerBusy = 0;
state.numServerUnavailable = 1;
assertEquals(1, state.numServerUnavailable);
assertFalse(state.canServe());
assertFalse(state.hasRequests());
state.queue.add(1.0);
assertEquals(1, state.queue.size());
assertTrue(state.hasRequests());
assertFalse(state.isQueueFull());
state.numServerUnavailable = 0;
state.numServerBusy = 0;
assertTrue(state.canServe());
assertTrue(state.hasRequests());
state.numServerBusy = 1;
state.queue.poll();
assertEquals(0, state.queue.size());
assertFalse(state.hasRequests());
}
@Test
public void nodeStatsUpdates() {
var net = new Net();
net.addNode(ServerNode.Builder.sourceLimited("Source", 50, const1));
net.addNode(node1);
net.addConnection(0, 1, 1.0);
var state = new ServerNodeState(0, net);
var event = state.spawnArrivalIfPossilbe(0);
assertNotNull(event);
assertEquals(0, state.stats.numArrivals, DELTA);
assertEquals(0, state.stats.numDepartures, DELTA);
assertEquals(0, state.numServerBusy);
assertEquals(0, state.numServerUnavailable);
assertEquals(Event.Type.ARRIVAL, event.type);
assertEquals(0, event.nodeIndex);
state.updateArrival(event.time);
assertEquals(1, state.stats.numArrivals, DELTA);
assertEquals(0, state.numServerBusy);
event = state.spawnDepartureIfPossible(event.time, rigged);
assertNotNull(event);
assertEquals(1, state.stats.numArrivals, DELTA);
assertEquals(0, state.stats.numDepartures, DELTA);
assertEquals(1, state.numServerBusy);
assertEquals(0, state.numServerUnavailable);
assertEquals(Event.Type.DEPARTURE, event.type);
assertEquals(0, event.nodeIndex);
state.updateDeparture(event.time);
assertEquals(1, state.stats.numArrivals, DELTA);
assertEquals(1, state.stats.numDepartures, DELTA);
assertEquals(0, state.numServerBusy);
assertEquals(0, state.numServerUnavailable);
state = new ServerNodeState(1, net);
event = state.spawnArrivalIfPossilbe(0);
assertNull(event);
assertEquals(0, state.stats.numArrivals, DELTA);
assertEquals(0, state.stats.numDepartures, DELTA);
assertEquals(0, state.numServerBusy);
assertEquals(0, state.numServerUnavailable);
state.updateArrival(0);
assertEquals(1, state.stats.numArrivals, DELTA);
assertEquals(0, state.numServerBusy);
event = state.spawnDepartureIfPossible(0, rigged);
assertNotNull(event);
assertEquals(1, state.stats.numArrivals, DELTA);
assertEquals(0, state.stats.numDepartures, DELTA);
assertEquals(1, state.numServerBusy);
assertEquals(0, state.numServerUnavailable);
assertEquals(Event.Type.DEPARTURE, event.type);
assertEquals(1, event.nodeIndex);
state.updateDeparture(event.time);
assertEquals(1, state.stats.numArrivals, DELTA);
assertEquals(1, state.stats.numDepartures, DELTA);
assertEquals(0, state.numServerBusy);
assertEquals(0, state.numServerUnavailable);
event = state.spawnUnavailableIfPossible(0, rigged);
assertNull(event);
state = new ServerNodeState(0, net);
event = state.spawnArrivalToChild(0, rigged);
assertNotNull(event);
assertEquals(0, state.stats.numArrivals, DELTA);
assertEquals(0, state.stats.numDepartures, DELTA);
assertEquals(0, state.numServerBusy);
assertEquals(0, state.numServerUnavailable);
assertEquals(Event.Type.ARRIVAL, event.type);
assertEquals(1, event.nodeIndex);
}
@Test
@@ -183,7 +275,7 @@ public class TestSimulation {
assertTrue(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.addArrival(node0);
sim.addToFel(Event.newArrival(0, sim.getTime()));
assertFalse(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.processNextEvent(); // Arrival
@@ -204,7 +296,7 @@ public class TestSimulation {
assertTrue(sim.hasEnded()); // No more events
assertFalse(criteria.shouldEnd(sim));
sim.addArrival(node0);
sim.addToFel(Event.newArrival(0, sim.getTime()));
assertFalse(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.processNextEvent(); // Arrival
@@ -225,7 +317,7 @@ public class TestSimulation {
assertTrue(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.addArrival(node0);
sim.addToFel(Event.newArrival(0, sim.getTime()));
assertFalse(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
@@ -247,7 +339,7 @@ public class TestSimulation {
assertTrue(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.addArrival(node0);
sim.addToFel(Event.newArrival(0, sim.getTime()));
assertFalse(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.processNextEvent(); // Arrival
@@ -264,7 +356,7 @@ public class TestSimulation {
assertTrue(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.addArrival(node0);
sim.addToFel(Event.newArrival(0, sim.getTime()));
assertFalse(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
@@ -286,7 +378,7 @@ public class TestSimulation {
assertTrue(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.addArrival(node0);
sim.addToFel(Event.newArrival(0, sim.getTime()));
assertFalse(sim.hasEnded());
assertFalse(criteria.shouldEnd(sim));
sim.processNextEvent(); // Arrival
@@ -321,7 +413,7 @@ public class TestSimulation {
var fel = sim.getFutureEventList();
assertEquals(0, fel.size());
sim.addArrival(node0);
sim.addToFel(Event.newArrival(0, sim.getTime()));
assertFalse(sim.hasEnded());
assertEquals(0, sim.getEventsProcessed());
assertEquals(0.0, sim.getTime(), DELTA);
@@ -334,7 +426,7 @@ public class TestSimulation {
fel = sim.getFutureEventList();
assertEquals(1, fel.size());
assertEquals(Event.Type.ARRIVAL, fel.get(0).type);
assertEquals(node0, fel.get(0).node);
assertEquals(0, fel.get(0).nodeIndex);
assertEquals(0.0, fel.get(0).time, DELTA);
sim.processNextEvent(); // Arrival
@@ -350,7 +442,7 @@ public class TestSimulation {
fel = sim.getFutureEventList();
assertEquals(1, fel.size());
assertEquals(Event.Type.DEPARTURE, fel.get(0).type);
assertEquals(node0, fel.get(0).node);
assertEquals(0, fel.get(0).nodeIndex);
assertEquals(1.0, fel.get(0).time, DELTA);
sim.processNextEvent(); // Departure Source
@@ -366,7 +458,7 @@ public class TestSimulation {
fel = sim.getFutureEventList();
assertEquals(1, fel.size());
assertEquals(Event.Type.ARRIVAL, fel.get(0).type);
assertEquals(node1, fel.get(0).node);
assertEquals(1, fel.get(0).nodeIndex);
assertEquals(1.0, fel.get(0).time, DELTA);
sim.processNextEvent(); // Arrival Queue
@@ -382,7 +474,7 @@ public class TestSimulation {
fel = sim.getFutureEventList();
assertEquals(1, fel.size());
assertEquals(Event.Type.DEPARTURE, fel.get(0).type);
assertEquals(node1, fel.get(0).node);
assertEquals(1, fel.get(0).nodeIndex);
assertEquals(2.0, fel.get(0).time, DELTA);
sim.processNextEvent(); // Departure Queue
@@ -402,7 +494,7 @@ public class TestSimulation {
var result = sim.endSimulation();
assertEquals(2.0, result.simulationTime, DELTA);
assertEquals(sim.seed, result.seed);
assertEquals(elapsed * 1e-6, result.timeElapsedMS * 1e-6, diff);
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);
@@ -414,12 +506,12 @@ public class TestSimulation {
public void endSim() {
var criteria = new EndCriteria.MaxDepartures(node0.name, 5);
var sim = new Simulation(simpleNet, rigged, criteria);
sim.addArrival(node0);
sim.addArrival(node0);
sim.addArrival(node0);
sim.addArrival(node0);
sim.addArrival(node0);
sim.addArrival(node0);
sim.addToFel(Event.newArrival(0, sim.getTime()));
sim.addToFel(Event.newArrival(0, sim.getTime()));
sim.addToFel(Event.newArrival(0, sim.getTime()));
sim.addToFel(Event.newArrival(0, sim.getTime()));
sim.addToFel(Event.newArrival(0, sim.getTime()));
sim.addToFel(Event.newArrival(0, sim.getTime()));
while (!criteria.shouldEnd(sim)) {
sim.processNextEvent();
@@ -431,13 +523,13 @@ public class TestSimulation {
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(0, res.nodes.get(node1.name).numDepartures, DELTA);
assertEquals(3, res.nodes.get(node1.name).numDepartures, DELTA);
}
@Test
public void simulationStats() {
var net = new Net();
net.addNode(ServerNode.createLimitedSource("Source", const1, 50));
net.addNode(ServerNode.Builder.sourceLimited("Source", 50, const1));
var sim = new Simulation(net, rigged);
var result = sim.run();
@@ -454,7 +546,7 @@ public class TestSimulation {
assertEquals(1.0, nodeStat.utilization, DELTA);
assertEquals(0.0, nodeStat.unavailable, DELTA);
net.addNode(ServerNode.createQueue("Queue", 1, const1));
net.addNode(ServerNode.Builder.queue("Queue", 1, const1));
net.addConnection(0, 1, 1.0);
sim = new Simulation(net, rigged);
@@ -485,4 +577,37 @@ public class TestSimulation {
assertEquals(nodeStat.numDepartures / nodeStat.lastEventTime, nodeStat.throughput, DELTA);
assertEquals(0.0, nodeStat.unavailable, DELTA);
}
@Test
public void simulationDrop() {
var net = new Net();
net.addNode(ServerNode.Builder.sourceLimited("Source", 50, const1));
net.addNode(new ServerNode.Builder("Queue", _ -> 2.0).queue(20).build());
net.addConnection(0, 1, 1.0);
var sim = new Simulation(net, rigged);
var result = sim.run();
var nodeStat = result.nodes.get("Source");
assertEquals(50, nodeStat.numArrivals, DELTA);
assertEquals(50, nodeStat.numDepartures, DELTA);
assertEquals(1.0, nodeStat.avgQueueLength, DELTA);
assertEquals(1.0, nodeStat.avgResponse, DELTA);
assertEquals(0.0, nodeStat.avgWaitTime, DELTA);
assertEquals(1.0, nodeStat.maxQueueLength, DELTA);
assertEquals(50.0, nodeStat.busyTime, DELTA);
assertEquals(50.0, nodeStat.lastEventTime, DELTA);
assertEquals(1.0, nodeStat.throughput, DELTA);
assertEquals(1.0, nodeStat.utilization, DELTA);
assertEquals(0.0, nodeStat.unavailable, DELTA);
nodeStat = result.nodes.get("Queue");
assertEquals(44, nodeStat.numArrivals, DELTA);
assertEquals(44, nodeStat.numDepartures, DELTA);
assertEquals(20.0, nodeStat.maxQueueLength, DELTA);
assertEquals(23.0227272, nodeStat.avgResponse, DELTA);
assertEquals(21.0227272, nodeStat.avgWaitTime, DELTA);
assertEquals(0.0, nodeStat.unavailable, DELTA);
assertEquals(result.simulationTime, nodeStat.lastEventTime, DELTA);
}
}