diff --git a/.vscode/launch.json b/.vscode/launch.json index 1b99fd4..9504289 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,6 +25,13 @@ "mainClass": "net.berack.upo.valpre.Main", "args": "simulation -net example3.net -runs 1000 -p -seed 0" }, + { + "type": "java", + "name": "Run Incremental", + "request": "launch", + "mainClass": "net.berack.upo.valpre.Main", + "args": "simulation -net example3.net -runs 1000 -seed 0 -i \"[Service1:throughput=0.98:0.01],[Service2:utilization=0.98:0.01],[Service2:unavailable=0.98:0.01]\"" + }, { "type": "java", "name": "Run10", diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index 47dd74c..16b33c7 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -24,6 +24,7 @@ public class Main { .setSeed(param.getOrDefault("seed", Long::parseLong, 2007539552L)) .setParallel(param.get("p") != null) .setEndCriteria(EndCriteria.parse(param.get("end"))) + .parseConfidenceIndices(param.get("i")) .run(); } case "plot" -> { @@ -58,6 +59,7 @@ public class Main { arguments.put("net", true); arguments.put("end", true); arguments.put("csv", true); + arguments.put("i", true); var descriptions = new HashMap(); descriptions.put("p", "Add this if you want the simulation to use threads (one each run)."); @@ -65,6 +67,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."; @@ -85,7 +89,8 @@ public class Main { var name = new File(uri).getName(); System.out.println(message); 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("simulation args: -net [-csv ] [-runs ] [-seed ]" + + "[-p] [-end ] [-i ]"); System.out.println("plot args: -csv "); System.out.println("net args: none"); System.exit(1); diff --git a/src/main/java/net/berack/upo/valpre/SimulationBuilder.java b/src/main/java/net/berack/upo/valpre/SimulationBuilder.java index b48bc15..61b85fd 100644 --- a/src/main/java/net/berack/upo/valpre/SimulationBuilder.java +++ b/src/main/java/net/berack/upo/valpre/SimulationBuilder.java @@ -2,7 +2,6 @@ package net.berack.upo.valpre; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.List; import java.util.concurrent.ExecutionException; import com.esotericsoftware.kryo.KryoException; @@ -11,7 +10,6 @@ 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; -import net.berack.upo.valpre.sim.stats.NodeStats; /** * This class is responsible for running the simulation. It parses the arguments @@ -21,10 +19,10 @@ public class SimulationBuilder { private String csv; private int runs; private long seed; - private boolean parallel; private Net net; private EndCriteria[] endCriteria; private ConfidenceIndices confidences; + private Type type = Type.Normal; /** * Create a new simulation for the given net. @@ -92,7 +90,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; } @@ -133,24 +132,51 @@ public class SimulationBuilder { * @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 the node is invalid - * @throws IllegalArgumentException if the stat is invalid - * @throws IllegalArgumentException if the confidence is invalid - * @throws IllegalArgumentException if the relative error is invalid + * @throws IllegalArgumentException if any of the input parameters is invalid */ public SimulationBuilder addConfidenceIndex(String node, String stat, double confidence, double relError) { - if (!List.of(NodeStats.getOrderOfApply()).contains(stat)) - throw new IllegalArgumentException("Invalid statistic: " + stat); - 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"); - 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; } @@ -165,9 +191,11 @@ public class SimulationBuilder { 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); @@ -178,4 +206,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 + } } diff --git a/src/main/java/net/berack/upo/valpre/sim/ConfidenceIndices.java b/src/main/java/net/berack/upo/valpre/sim/ConfidenceIndices.java index e764b59..a856159 100644 --- a/src/main/java/net/berack/upo/valpre/sim/ConfidenceIndices.java +++ b/src/main/java/net/berack/upo/valpre/sim/ConfidenceIndices.java @@ -16,6 +16,7 @@ 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. @@ -36,6 +37,15 @@ public class ConfidenceIndices { } } + /** + * 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. @@ -44,15 +54,24 @@ public class ConfidenceIndices { * @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); } @@ -94,7 +113,7 @@ public class ConfidenceIndices { error.merge(relError, (err, rel) -> err - rel); for (var value : error) - if (value > 0) + if (!value.isInfinite() && !value.isNaN() && value > 0) return false; } @@ -110,20 +129,19 @@ public class ConfidenceIndices { * @param errors the relative errors of the statistics * @return the errors of the statistics */ - public String[] getErrors(NodeStats[] errors) { + public String[] getIndices(NodeStats[] errors) { var statistics = NodeStats.getOrderOfApply(); var retValues = new ArrayList(); 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); - var j = 0; - for (var value : error) { - if (value > 0) - retValues.add("%s:%s=%0.3f".formatted(this.nodes[i], statistics[j], value)); - j += 1; + 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)); } } diff --git a/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java b/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java index 2a7f483..7be0b36 100644 --- a/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java +++ b/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java @@ -101,11 +101,11 @@ public class SimulationMultiple { * @return The statistics the network. * @throws IllegalArgumentException If the confidence is not set. */ - public void runIncremental(long seed, int runs, ConfidenceIndices confidences, EndCriteria... criterias) { + 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); + 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; @@ -121,7 +121,7 @@ public class SimulationMultiple { var errors = confidences.calcRelativeErrors(results); stop = confidences.isOk(errors); - var errString = confidences.getErrors(errors); + var errString = confidences.getIndices(errors); var oneSting = String.join("], [", errString); output.append('[').append(oneSting).append("]"); @@ -129,8 +129,8 @@ public class SimulationMultiple { } } - System.out.println("\nSimulation ended"); - System.out.println(results); + System.out.println(); // remove last printed line + return results; } }