EndCriteria&NetBuilder

- Add end criteria parsing and update simulation parameters
- added net builder from console
This commit is contained in:
2025-02-03 17:33:07 +01:00
parent bd61042573
commit 2acb27a6b9
5 changed files with 340 additions and 27 deletions

7
.vscode/launch.json vendored
View File

@@ -46,5 +46,12 @@
"mainClass": "net.berack.upo.valpre.Main", "mainClass": "net.berack.upo.valpre.Main",
"args": "plot -csv example2.csv" "args": "plot -csv example2.csv"
}, },
{
"type": "java",
"name": "Build Net",
"request": "launch",
"mainClass": "net.berack.upo.valpre.Main",
"args": "net"
},
] ]
} }

View File

@@ -5,6 +5,8 @@ import java.net.URISyntaxException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import net.berack.upo.valpre.sim.EndCriteria;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
if (args.length == 0) if (args.length == 0)
@@ -16,20 +18,23 @@ public class Main {
var param = Main.getParameters(program, subArgs); var param = Main.getParameters(program, subArgs);
switch (program) { switch (program) {
case "simulation" -> { case "simulation" -> {
var net = param.get("net"); new Simulation(param.get("net"))
var csv = param.get("csv"); .setCsv(param.get("csv"))
var runs = param.getOrDefault("runs", Integer::parseInt, 100); .setRuns(param.getOrDefault("runs", Integer::parseInt, 100))
var seed = param.getOrDefault("seed", Long::parseLong, 2007539552L); .setSeed(param.getOrDefault("seed", Long::parseLong, 2007539552L))
var p = param.getOrDefault("p", Boolean::parseBoolean, false); .setParallel(param.get("p") != null)
.setEndCriteria(EndCriteria.parse(param.get("end")))
var sim = new Simulation(net, seed, runs, p, csv); .run();
sim.run();
} }
case "plot" -> { case "plot" -> {
var csv = param.get("csv"); var csv = param.get("csv");
var plot = new Plot(csv); var plot = new Plot(csv);
plot.show(); plot.show();
} }
case "net" -> {
var net = new NetBuilderInteractive();
net.run();
}
default -> exit("Invalid program!"); default -> exit("Invalid program!");
} }
} catch (Exception e) { } catch (Exception e) {
@@ -50,12 +55,14 @@ public class Main {
arguments.put("seed", true); arguments.put("seed", true);
arguments.put("runs", true); arguments.put("runs", true);
arguments.put("net", true); arguments.put("net", true);
arguments.put("end", true);
arguments.put("csv", true); arguments.put("csv", true);
var descriptions = new HashMap<String, String>(); var descriptions = new HashMap<String, String>();
descriptions.put("p", "Add this if you want the simulation to use threads (one each run)."); 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("seed", "The seed of the simulation.");
descriptions.put("runs", "How many runs the simulator should run."); 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("net", "The file net to use. Use example1.net or example2.net for the provided ones.");
var csvDesc = switch (program) { var csvDesc = switch (program) {
@@ -65,6 +72,8 @@ public class Main {
}; };
descriptions.put("csv", csvDesc); descriptions.put("csv", csvDesc);
if (program.equals("net"))
return null;
return Parameters.getArgsOrHelper(args, "-", arguments, descriptions); return Parameters.getArgsOrHelper(args, "-", arguments, descriptions);
} }
@@ -76,7 +85,10 @@ public class Main {
var uri = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI(); var uri = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI();
var name = new File(uri).getName(); var name = new File(uri).getName();
System.out.println(message); System.out.println(message);
System.out.println("Usage: java -jar " + name + ".jar [simulation|plot] [args]"); 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("plot args: -csv <csv>");
System.out.println("net args: none");
System.exit(1); System.exit(1);
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
e.printStackTrace(); e.printStackTrace();

View File

@@ -0,0 +1,182 @@
package net.berack.upo.valpre;
import java.util.function.Function;
import net.berack.upo.valpre.rand.Distribution;
import net.berack.upo.valpre.sim.Net;
import net.berack.upo.valpre.sim.ServerNode;
public class NetBuilderInteractive {
private final Net net = new Net();
/**
* Run the interactive net builder.
*
* @param args the arguments
*/
public void run() {
while (true) {
try {
var choice = choose("Choose the next step to do:",
"Add a node", "Add a connection", "Print Nodes", "Save the net", "Exit");
switch (choice) {
case 1 -> {
var node = this.buildNode();
this.net.addNode(node);
}
case 2 -> {
var source = ask("Enter the source node: ");
var target = ask("Enter the target node: ");
var weight = ask("Enter the weight: ", Double::parseDouble, 0.0);
var sourceNode = this.net.getNode(source);
var targetNode = this.net.getNode(target);
this.net.addConnection(sourceNode, targetNode, weight);
}
case 3 -> this.printNodes();
case 4 -> this.net.save(ask("Enter the filename: "));
case 5 -> System.exit(0);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Print the nodes in the net.
*/
private void printNodes() {
var builder = new StringBuilder();
builder.append("Nodes:\n");
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("), ");
builder.delete(builder.length() - 2, builder.length());
builder.append("\n");
}
System.out.print(builder.toString());
}
/**
* Build a node.
*
* @return the node
*/
private ServerNode buildNode() {
var choice = choose("Choose the type of node to create:", "Source", "Queue");
var name = ask("Node name: ");
var distribution = askDistribution("Service distribution");
return switch (choice) {
case 1 -> {
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);
}
case 2 -> {
var servers = ask("Number of servers: ", Integer::parseInt, 1);
var unavailable = askDistribution("Unavailable distribution");
yield ServerNode.createQueue(name, servers, distribution, unavailable);
}
default -> null;
};
}
/**
* Ask the user for a distribution.
*
* @return the distribution
*/
public static Distribution askDistribution(String ask) {
var choice = choose(ask + ":", "Exponential", "Uniform", "Erlang",
"UnavailableTime", "Normal", "NormalBoxMuller", "None");
return switch (choice) {
case 1 -> {
var lambda = ask("Lambda: ", Double::parseDouble, 1.0);
yield new Distribution.Exponential(lambda);
}
case 2 -> {
var min = ask("Min: ", Double::parseDouble, 0.0);
var max = ask("Max: ", Double::parseDouble, 1.0);
yield new Distribution.Uniform(min, max);
}
case 3 -> {
var k = ask("K: ", Integer::parseInt, 1);
var lambda = ask("Lambda: ", Double::parseDouble, 1.0);
yield new Distribution.Erlang(k, lambda);
}
case 4 -> {
var probability = ask("Probability: ", Double::parseDouble, 0.0);
var unavailable = askDistribution("Unavailable distribution");
yield new Distribution.UnavailableTime(probability, unavailable);
}
case 5 -> {
var mean = ask("Mean: ", Double::parseDouble, 0.0);
var stdDev = ask("Standard deviation: ", Double::parseDouble, 1.0);
yield new Distribution.Normal(mean, stdDev);
}
case 6 -> {
var mean = ask("Mean: ", Double::parseDouble, 0.0);
var stdDev = ask("Standard deviation: ", Double::parseDouble, 1.0);
yield new Distribution.NormalBoxMuller(mean, stdDev);
}
default -> null;
};
}
/**
* Ask the user a question.
*
* @param ask the question to ask
* @return the answer
*/
private static String ask(String ask) {
return ask(ask, Function.identity(), "");
}
/**
* Ask the user a question.
*
* @param ask the question to ask
* @param parser the parser to use
* @param defaultValue the default value
* @return the answer
*/
private static <T> T ask(String ask, Function<String, T> parser, T defaultValue) {
System.out.print(ask);
try {
var line = System.console().readLine();
return parser.apply(line);
} catch (Exception e) {
System.out.println("Invalid input: " + e.getMessage());
return defaultValue;
}
}
/**
* Ask the user to choose an option.
*
* @param ask the question to ask
* @param options the options to choose from
* @return the choice
*/
private static int choose(String ask, String... options) {
var builder = new StringBuilder();
builder.append(ask).append("\n");
for (int i = 0; i < options.length; i++) {
builder.append(i + 1).append(". ").append(options[i]).append("\n");
}
builder.append("> ");
var string = builder.toString();
var choice = 0;
while (choice < 1 || choice > options.length)
choice = ask(string, Integer::parseInt, 0);
return choice;
}
}

View File

@@ -5,6 +5,7 @@ import java.io.IOException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import com.esotericsoftware.kryo.KryoException; import com.esotericsoftware.kryo.KryoException;
import net.berack.upo.valpre.sim.EndCriteria;
import net.berack.upo.valpre.sim.Net; import net.berack.upo.valpre.sim.Net;
import net.berack.upo.valpre.sim.SimulationMultiple; import net.berack.upo.valpre.sim.SimulationMultiple;
import net.berack.upo.valpre.sim.stats.CsvResult; import net.berack.upo.valpre.sim.stats.CsvResult;
@@ -14,27 +15,20 @@ import net.berack.upo.valpre.sim.stats.CsvResult;
* and runs the simulation with the given parameters. * and runs the simulation with the given parameters.
*/ */
public class Simulation { public class Simulation {
public final String csv; private String csv;
public final int runs; private int runs;
public final long seed; private long seed;
public final boolean parallel; private boolean parallel;
public final Net net; private Net net;
private EndCriteria[] endCriteria;
/** /**
* Create a new simulation with the given arguments. * Create a new simulation for the given net.
* *
* @param args The arguments for the simulation. * @param netFile the net file to load
* @throws IOException if the file is has a problem * @throws IOException if the file has a problem
*/ */
public Simulation(String netFile, long seed, int runs, boolean parallel, String csv) throws IOException { public Simulation(String netFile) throws IOException {
if (runs <= 0)
throw new IllegalArgumentException("Runs must be greater than 0!");
this.runs = runs;
this.seed = seed;
this.csv = csv;
this.parallel = parallel;
try { try {
var file = Parameters.getFileOrExample(netFile); var file = Parameters.getFileOrExample(netFile);
this.net = Net.load(file); this.net = Net.load(file);
@@ -46,6 +40,83 @@ public class Simulation {
} }
} }
/**
* Create a new simulation for the given net.
*
* @param net the net
* @throws IllegalArgumentException if the net is null
*/
public Simulation(Net net) {
if (net == null)
throw new IllegalArgumentException("Net needed!");
this.net = net;
}
/**
* Set the 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 Simulation setRuns(int runs) {
if (runs <= 0)
throw new IllegalArgumentException("Runs must be greater than 0!");
this.runs = runs;
return this;
}
/**
* Set the seed for the simulation.
*
* @param seed the seed
* @return this simulation
*/
public Simulation setSeed(long seed) {
this.seed = seed;
return this;
}
/**
* Set if the simulation should run in parallel.
* The parallelization is done by running each simulation in a separate thread.
*
* @param parallel if the simulation should run in parallel
* @return this simulation
*/
public Simulation setParallel(boolean parallel) {
this.parallel = parallel;
return this;
}
/**
* Set the CSV file to save the results.
*
* @param csv the CSV file
* @return this simulation
*/
public Simulation setCsv(String csv) {
this.csv = csv;
return this;
}
/**
* Set the end criteria for the simulation.
* Can be an empty array if no criteria are needed.
* Cannot be null.
*
* @param criterias the end criteria
* @return this simulation
* @throws IllegalArgumentException if the criteria are null
*/
public Simulation setEndCriteria(EndCriteria... criterias) {
if (criterias == null)
throw new IllegalArgumentException("End criteria cannot be null!");
this.endCriteria = criterias;
return this;
}
/** /**
* Run the simulation with the given parameters. * Run the simulation with the given parameters.
* At the end it prints the results and saves them to a CSV file if requested. * At the end it prints the results and saves them to a CSV file if requested.
@@ -58,7 +129,9 @@ public class Simulation {
public void run() throws InterruptedException, ExecutionException, KryoException, IOException { public void run() throws InterruptedException, ExecutionException, KryoException, IOException {
var nano = System.nanoTime(); var nano = System.nanoTime();
var sim = new SimulationMultiple(this.net); var sim = new SimulationMultiple(this.net);
var summary = this.parallel ? sim.runParallel(this.seed, this.runs) : sim.run(this.seed, this.runs); var summary = this.parallel
? sim.runParallel(this.seed, this.runs, this.endCriteria)
: sim.run(this.seed, this.runs, this.endCriteria);
nano = System.nanoTime() - nano; nano = System.nanoTime() - nano;
System.out.print(summary); System.out.print(summary);

View File

@@ -12,6 +12,45 @@ public interface EndCriteria {
*/ */
public boolean shouldEnd(Simulation run); 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 * Ends the simulation when the given node has reached the specified number of
* arrivals. * arrivals.