From 2acb27a6b95138bc7a121f5888c272d3fbd56914 Mon Sep 17 00:00:00 2001 From: Berack96 Date: Mon, 3 Feb 2025 17:33:07 +0100 Subject: [PATCH] EndCriteria&NetBuilder - Add end criteria parsing and update simulation parameters - added net builder from console --- .vscode/launch.json | 7 + src/main/java/net/berack/upo/valpre/Main.java | 30 ++- .../upo/valpre/NetBuilderInteractive.java | 182 ++++++++++++++++++ .../net/berack/upo/valpre/Simulation.java | 109 +++++++++-- .../berack/upo/valpre/sim/EndCriteria.java | 39 ++++ 5 files changed, 340 insertions(+), 27 deletions(-) create mode 100644 src/main/java/net/berack/upo/valpre/NetBuilderInteractive.java diff --git a/.vscode/launch.json b/.vscode/launch.json index b515b74..a7c7c9b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -46,5 +46,12 @@ "mainClass": "net.berack.upo.valpre.Main", "args": "plot -csv example2.csv" }, + { + "type": "java", + "name": "Build Net", + "request": "launch", + "mainClass": "net.berack.upo.valpre.Main", + "args": "net" + }, ] } \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index 5144bb7..eb8dc33 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -5,6 +5,8 @@ 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) @@ -16,20 +18,23 @@ public class Main { var param = Main.getParameters(program, subArgs); switch (program) { case "simulation" -> { - var net = param.get("net"); - var csv = param.get("csv"); - var runs = param.getOrDefault("runs", Integer::parseInt, 100); - var seed = param.getOrDefault("seed", Long::parseLong, 2007539552L); - var p = param.getOrDefault("p", Boolean::parseBoolean, false); - - var sim = new Simulation(net, seed, runs, p, csv); - sim.run(); + new Simulation(param.get("net")) + .setCsv(param.get("csv")) + .setRuns(param.getOrDefault("runs", Integer::parseInt, 100)) + .setSeed(param.getOrDefault("seed", Long::parseLong, 2007539552L)) + .setParallel(param.get("p") != null) + .setEndCriteria(EndCriteria.parse(param.get("end"))) + .run(); } case "plot" -> { var csv = param.get("csv"); var plot = new Plot(csv); plot.show(); } + case "net" -> { + var net = new NetBuilderInteractive(); + net.run(); + } default -> exit("Invalid program!"); } } catch (Exception e) { @@ -50,12 +55,14 @@ public class Main { arguments.put("seed", true); arguments.put("runs", true); arguments.put("net", true); + arguments.put("end", true); arguments.put("csv", true); var descriptions = new HashMap(); descriptions.put("p", "Add this if you want the simulation to use threads (one each run)."); descriptions.put("seed", "The seed of the simulation."); descriptions.put("runs", "How many runs the simulator should run."); + descriptions.put("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."); var csvDesc = switch (program) { @@ -65,6 +72,8 @@ public class Main { }; descriptions.put("csv", csvDesc); + if (program.equals("net")) + return null; return Parameters.getArgsOrHelper(args, "-", arguments, descriptions); } @@ -76,7 +85,10 @@ public class Main { var uri = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI(); var name = new File(uri).getName(); 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 -csv [-runs ] [-seed ] [-p]"); + System.out.println("plot args: -csv "); + System.out.println("net args: none"); System.exit(1); } catch (URISyntaxException e) { e.printStackTrace(); diff --git a/src/main/java/net/berack/upo/valpre/NetBuilderInteractive.java b/src/main/java/net/berack/upo/valpre/NetBuilderInteractive.java new file mode 100644 index 0000000..d63e753 --- /dev/null +++ b/src/main/java/net/berack/upo/valpre/NetBuilderInteractive.java @@ -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 ask(String ask, Function 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; + } +} diff --git a/src/main/java/net/berack/upo/valpre/Simulation.java b/src/main/java/net/berack/upo/valpre/Simulation.java index 7afb444..afe06c0 100644 --- a/src/main/java/net/berack/upo/valpre/Simulation.java +++ b/src/main/java/net/berack/upo/valpre/Simulation.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.concurrent.ExecutionException; 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.SimulationMultiple; 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. */ public class Simulation { - public final String csv; - public final int runs; - public final long seed; - public final boolean parallel; - public final Net net; + private String csv; + private int runs; + private long seed; + private boolean parallel; + 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. - * @throws IOException if the file is has a problem + * @param netFile the net file to load + * @throws IOException if the file has a problem */ - public Simulation(String netFile, long seed, int runs, boolean parallel, String csv) 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; - + public Simulation(String netFile) throws IOException { try { var file = Parameters.getFileOrExample(netFile); 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. * 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 { var nano = System.nanoTime(); 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; System.out.print(summary); diff --git a/src/main/java/net/berack/upo/valpre/sim/EndCriteria.java b/src/main/java/net/berack/upo/valpre/sim/EndCriteria.java index 962ae5d..e7c3077 100644 --- a/src/main/java/net/berack/upo/valpre/sim/EndCriteria.java +++ b/src/main/java/net/berack/upo/valpre/sim/EndCriteria.java @@ -12,6 +12,45 @@ 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.