package net.berack.upo.valpre; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; 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; import net.berack.upo.valpre.sim.stats.Result; /** * This class is responsible for running the simulation. It parses the arguments * and runs the simulation with the given parameters. */ public class SimulationBuilder { 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 ConfidenceIndices confidences; /** * Create a new simulation for the given net. * * @param netFile the net file to load * @throws IOException if the file has a problem */ public SimulationBuilder(String netFile) throws IOException { try { this.net = Net.load(netFile); this.confidences = new ConfidenceIndices(this.net); } catch (FileNotFoundException e) { throw new IllegalArgumentException("Net file needed!"); } catch (KryoException e) { throw new IllegalArgumentException("Net file is not valid or corrupted!"); } } /** * Create a new simulation for the given net. * * @param net the net * @throws IllegalArgumentException if the net is null */ public SimulationBuilder(Net net) { if (net == null) throw new IllegalArgumentException("Net needed!"); this.net = net; this.confidences = new ConfidenceIndices(net); } /** * 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 setMaxRuns(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 SimulationBuilder 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 SimulationBuilder setParallel(boolean parallel) { if (parallel && this.confidences.isEmpty()) this.type = Type.Parallel; return this; } /** * Set the CSV file to save the results. * * @param csv the CSV file * @return this simulation */ public SimulationBuilder 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 SimulationBuilder setEndCriteria(EndCriteria... criterias) { if (criterias == null) throw new IllegalArgumentException("End criteria cannot be null!"); this.endCriteria = criterias; 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 has an error. * @throws IOException If the CSV file has a problem. */ public Result.Summary run() throws InterruptedException, ExecutionException, IOException { return this.run(System.out); } /** * Run the simulation with the given parameters. * At the end it prints the results and saves them to a CSV file if requested. * * @param out the output stream to print the results * @throws InterruptedException If the simulation is interrupted. * @throws ExecutionException If the simulation has an error. * @throws IOException If the CSV file has a problem. */ public Result.Summary run(PrintStream out) throws InterruptedException, ExecutionException, IOException { var nano = System.nanoTime(); var sim = new SimulationMultiple(this.net); var summary = switch (this.type) { case Incremental -> sim.runIncremental(this.seed, this.runs, out, 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; out.print(summary); out.println("Final time " + nano / 1e6 + "ms"); if (csv != null) { new CsvResult(this.csv).saveResults(summary.getRuns()); out.println("Data saved to " + this.csv); } return summary; } /** * Inner class to handle the type of simulation. */ private static enum Type { Incremental, Parallel, Normal } }