diff --git a/parsers/src/main/java/org/chocosolver/parser/RegParser.java b/parsers/src/main/java/org/chocosolver/parser/RegParser.java index 48da217274..a3cb5f926e 100644 --- a/parsers/src/main/java/org/chocosolver/parser/RegParser.java +++ b/parsers/src/main/java/org/chocosolver/parser/RegParser.java @@ -22,6 +22,7 @@ import org.chocosolver.solver.search.strategy.BlackBoxConfigurator; import org.chocosolver.solver.search.strategy.Search; import org.chocosolver.solver.search.strategy.SearchParams; +import org.chocosolver.solver.trace.CPProfiler; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.Variable; import org.chocosolver.util.tools.VariableUtils; @@ -30,6 +31,7 @@ import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; +import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; @@ -173,6 +175,9 @@ public abstract class RegParser implements IParser { @Option(name = "-dfx", usage = "Force default explanation algorithm.") public boolean dftexp = false; + @Option(name = "--cp-profiler", usage = "Connect to CP-Profiler. Two comma-separated values are expected: the execution id and the port.") + public String cpProfiler = null; + /** * Default settings to apply */ @@ -339,7 +344,19 @@ public final void solve() { getModel().getSolver().log().white().printf("Problem solving starts at %s\n", dtf.format(now)); } if (portfolio.getModels().size() == 1) { + CPProfiler profiler = null; + if (cpProfiler != null) { + String[] params = cpProfiler.split(","); + profiler = new CPProfiler(getModel().getSolver(), Integer.parseInt(params[0]), Integer.parseInt(params[1]), false); + } singleThread(); + if (cpProfiler != null) { + try { + profiler.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } else { manyThread(); } diff --git a/parsers/src/main/minizinc/choco.msc b/parsers/src/main/minizinc/choco.msc index dd81565b0f..4ba28fb39e 100644 --- a/parsers/src/main/minizinc/choco.msc +++ b/parsers/src/main/minizinc/choco.msc @@ -2,11 +2,11 @@ "id": "org.choco.choco", "name": "Choco-solver", "description": "Choco FlatZinc executable", - "version" : "4.10.14", + "version" : "4.10.15", "mznlib" : "/Users/kyzrsoze/Sources/CHOCO/continuous-branch/parsers/src/main/minizinc/mzn_lib/", "executable" : "/Users/kyzrsoze/Sources/CHOCO/continuous-branch/parsers/src/main/minizinc/fzn-choco", "tags": ["cp","int"], - "stdFlags": ["-a","-f","-n","-p","-r","-s","-t"], + "stdFlags": ["-a","-f","-n","-p","-r","-s","-t","--cp-profiler"], "supportsMzn": false, "supportsFzn": true, "needsSolns2Out": true, diff --git a/parsers/src/main/minizinc/fzn-choco b/parsers/src/main/minizinc/fzn-choco index afc2799144..dcdce326d7 100755 --- a/parsers/src/main/minizinc/fzn-choco +++ b/parsers/src/main/minizinc/fzn-choco @@ -41,8 +41,11 @@ OPTIONS: -jar Override the jar file. (The default is $CHOCO_JAR.) + --cp-profiler id,port + Enable the cp-profiler with the given id and port. + --jargs - Override default java argument (The default is $JAVA_ARGS.) + Override default java argument (The default is $JAVA_ARGS.) EXAMPLES: @@ -112,10 +115,15 @@ do shift ;; - --jargs) - JAVA_ARGS="$2" - shift - ;; + --jargs) + JAVA_ARGS="$2" + shift + ;; + + --cp-profiler) + ARGS="$ARGS --cp-profiler $2" + shift + ;; -*) echo "$0: unknown option \`$1'" 1>&2 diff --git a/parsers/src/main/minizinc/fzn-choco.exe b/parsers/src/main/minizinc/fzn-choco.bat similarity index 91% rename from parsers/src/main/minizinc/fzn-choco.exe rename to parsers/src/main/minizinc/fzn-choco.bat index 01a8f6ee1a..a74d60dda7 100755 --- a/parsers/src/main/minizinc/fzn-choco.exe +++ b/parsers/src/main/minizinc/fzn-choco.bat @@ -52,7 +52,10 @@ OPTIONS: Override the jar file. (The default is %CHOCO_JAR%.) --jargs - Override default java argument (The default is %JAVA_ARGS%.) + Override default java argument (The default is %JAVA_ARGS%.) + + --cp-profiler , + Enable the cp-profiler with the given id and port. EXAMPLES: @@ -102,6 +105,10 @@ if /i "%~1"=="-jar" ( set "CHOCO_JAR=%~2" shift )else +if /i "%~1"=="--cp-profiler" ( + set ARGS=%ARGS%" --cp-profiler %~2" + shift +)else if /i "%~1"=="--jargs" ( set "JAVA_ARGS=%~2" shift diff --git a/solver/src/main/java/org/chocosolver/solver/trace/CPProfiler.java b/solver/src/main/java/org/chocosolver/solver/trace/CPProfiler.java new file mode 100644 index 0000000000..1b53ba144e --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/trace/CPProfiler.java @@ -0,0 +1,130 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2024, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.trace; + +import cpp.Connector; +import cpp.Message; +import org.chocosolver.solver.Solver; + +import java.io.Closeable; +import java.io.IOException; + +/** + * A search monitor to send data to cp-profiler. + * It enables to profile and to visualize Constraint Programming. An installation is needed and is + * described here. This monitor relies on + * its java integration.

Note that + * CPProfiler is {@link Closeable} and can be used as follow:

+ *

 {@code
+ * Model model = ProblemMaker.makeCostasArrays(7);
+ *  try (CPProfiler profiler = new CPProfiler(model)) {
+ *      while (model.getSolver().solve()) ;
+ *      out.println(model.getSolver().getSolutionCount());
+ * }
+ * }
+ *

Created by cprudhom on 22/10/2015. Project: choco. + * + * @author Charles Prud'homme + * @since 3.3.2 + */ +public class CPProfiler extends SearchViz { + + /** + * Used to communicate every node + */ + private Connector connector; + + /** + * Active connection to cp-profiler. + * This requires cp-profiler to be installed and launched before. + * + * @param aSolver solver to observe resolution + */ + public CPProfiler(Solver aSolver) { + this(aSolver, -1, 6565, false); + } + + /** + * Active connection to cp-profiler. + * This requires cp-profiler to be installed and launched before. + * + * @param aSolver solver to observe resolution + * @param sendDomain set to true to send domain into 'info' field (beware, it can + * increase the memory consumption and slow down the overall execution), set + * to false otherwise. + */ + public CPProfiler(Solver aSolver, int executionId, int port, boolean sendDomain) { + super(aSolver, sendDomain); + connect(aSolver.getModelName(), executionId, port); + } + + protected boolean connect(String label, int executionId, int port) { + if (connector == null) { + connector = new Connector(port); // 6565 is the port used by cpprofiler by default + } + try { + connector.connect(); + connector.start(label, executionId, true); // starting a new tree (also used in case of a restart) + } catch (IOException e) { + System.err.println("Unable to connect to CPProfiler, make sure it is started. No information will be sent."); + return false; + } + return true; + } + + @Override + protected void disconnect() { + if (connected) { + try { + connector.done(); + connector.disconnect(); + } catch (IOException e) { + System.err.println("Unable to disconnect CPProfiler."); + } + } + } + + @Override + protected void sendNode(int nc, int pid, int alt, int kid, int rid, String label, String info) { + send(nc, pid, alt, kid, rid, Message.NodeStatus.BRANCH, label, info); + } + + @Override + protected void sendSolution(int nc, int pid, int alt, int kid, int rid, String label, String info) { + send(nc, pid, alt, kid, rid, Message.NodeStatus.SOLVED, label, info); + } + + @Override + protected void sendFailure(int nc, int pid, int alt, int kid, int rid, String label, String info) { + send(nc, pid, alt, kid, rid, Message.NodeStatus.FAILED, label, info); + } + + @Override + protected void sendRestart(int rid) { + try { + connector.restart(rid); + } catch (IOException e) { + System.err.println("Lost connection with CPProfiler. No more information will be sent."); + connected = false; + } + } + + private void send(int nc, int pid, int alt, int kid, int rid, Message.NodeStatus status, String label, String info) { + try { + connector.createNode(nc, pid, rid, alt, kid, status) + .setInfo(info) + .setLabel(label) + .send(); + } catch (IOException e) { + System.err.println("Lost connection with CPProfiler. No more information will be sent."); + connected = false; + } + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/trace/GephiGenerator.java b/solver/src/main/java/org/chocosolver/solver/trace/GephiGenerator.java index b52caa52cf..b1553b73c0 100644 --- a/solver/src/main/java/org/chocosolver/solver/trace/GephiGenerator.java +++ b/solver/src/main/java/org/chocosolver/solver/trace/GephiGenerator.java @@ -41,11 +41,6 @@ public GephiGenerator(String gexfFile, Solver aSolver) { this.edges = new StringBuilder(); } - @Override - protected boolean connect(String label) { - return true; - } - @Override protected void disconnect() { Path file = Paths.get(instance); diff --git a/solver/src/main/java/org/chocosolver/solver/trace/GraphvizGenerator.java b/solver/src/main/java/org/chocosolver/solver/trace/GraphvizGenerator.java index a4ae768b17..e374e7b1a6 100644 --- a/solver/src/main/java/org/chocosolver/solver/trace/GraphvizGenerator.java +++ b/solver/src/main/java/org/chocosolver/solver/trace/GraphvizGenerator.java @@ -67,12 +67,6 @@ public GraphvizGenerator(String gvFile, Solver aSolver) { connected = true; } - - @Override - protected boolean connect(String label) { - return true; - } - @Override protected void disconnect() { try { diff --git a/solver/src/main/java/org/chocosolver/solver/trace/SearchViz.java b/solver/src/main/java/org/chocosolver/solver/trace/SearchViz.java index 3883572b2c..52aca2b7ee 100644 --- a/solver/src/main/java/org/chocosolver/solver/trace/SearchViz.java +++ b/solver/src/main/java/org/chocosolver/solver/trace/SearchViz.java @@ -136,16 +136,12 @@ public String print() { public SearchViz(Solver aSolver, boolean sendDomain) { this.mSolver = aSolver; this.sendDomain = sendDomain; - if(connected = connect(mSolver.getModel().getName())) { - mSolver.plugMonitor(this); - } + mSolver.plugMonitor(this); alt_stack.push(-1); // -1 is alt for the root node pid_stack.push(-1); // -1 is pid for the root node last_stack.push(-1); } - protected abstract boolean connect(String label); - protected abstract void disconnect(); protected abstract void sendNode(int nc, int pid, int alt, int kid, int rid, String label, String info); diff --git a/solver/src/test/java/org/chocosolver/solver/search/loop/monitors/CPProfilerTest.java b/solver/src/test/java/org/chocosolver/solver/search/loop/monitors/CPProfilerTest.java new file mode 100644 index 0000000000..118cf08cff --- /dev/null +++ b/solver/src/test/java/org/chocosolver/solver/search/loop/monitors/CPProfilerTest.java @@ -0,0 +1,61 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2022, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.search.loop.monitors; + +import org.chocosolver.solver.Model; +import org.chocosolver.solver.search.loop.lns.neighbors.RandomNeighborhood; +import org.chocosolver.solver.trace.CPProfiler; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.util.ProblemMaker; +import org.testng.annotations.Test; + +import java.io.IOException; + +import static java.lang.System.out; + +/** + *

+ * Project: choco-solver. + * + * @author Charles Prud'homme + * @since 13/09/2016. + */ +public class CPProfilerTest { + + @Test(groups = "1s", timeOut = 60000) + public void test1() throws IOException { + Model s1 = ProblemMaker.makeCostasArrays(7); + try (CPProfiler profiler = new CPProfiler(s1.getSolver())) { + while (s1.getSolver().solve()) ; + out.println(s1.getSolver().getSolutionCount()); + } + } + + @Test(groups = "1s", timeOut = 60000) + public void test2() throws IOException { + Model s1 = ProblemMaker.makeCostasArrays(7); + CPProfiler profiler = new CPProfiler(s1.getSolver()); + while (s1.getSolver().solve()) ; + out.println(s1.getSolver().getSolutionCount()); + profiler.close(); + } + + @Test(groups = "1s", timeOut = 60000) + public void test3() throws IOException { + Model s1 = ProblemMaker.makeGolombRuler(11); + s1.getSolver().setLNS(new RandomNeighborhood((IntVar[]) s1.getHook("ticks"), 10, 0)); + CPProfiler profiler = new CPProfiler(s1.getSolver()); + s1.getSolver().limitSolution(9); + while (s1.getSolver().solve()) ; + out.println(s1.getSolver().getSolutionCount()); + profiler.close(); + } + +} \ No newline at end of file