From 96ead8f21610c9b282deac241fb09df62c167851 Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Tue, 31 Aug 2021 11:58:46 +0200 Subject: [PATCH] java: sync tests with Google --- examples/tests/ConstraintSolverTest.java | 607 +++++++++------- examples/tests/ConstraintSolverTests.cs | 2 +- examples/tests/CpModelTest.java | 231 ++++++ examples/tests/CpSolverTest.java | 260 +++++++ examples/tests/FlowTest.java | 83 +++ examples/tests/KnapsackSolverTest.java | 54 ++ examples/tests/LinearExprTest.java | 167 +++++ examples/tests/LinearSolverTest.java | 751 ++++++++++++++++---- examples/tests/RoutingSolverTest.java | 780 ++++++++++++++++----- examples/tests/SatSolverTest.java | 10 +- makefiles/Makefile.java.mk | 5 + ortools/java/pom-test.xml.in | 5 + ortools/linear_solver/java/linear_solver.i | 1 + ortools/sat/java/sat.i | 2 - 14 files changed, 2356 insertions(+), 602 deletions(-) create mode 100644 examples/tests/CpModelTest.java create mode 100644 examples/tests/CpSolverTest.java create mode 100644 examples/tests/FlowTest.java create mode 100644 examples/tests/KnapsackSolverTest.java create mode 100644 examples/tests/LinearExprTest.java diff --git a/examples/tests/ConstraintSolverTest.java b/examples/tests/ConstraintSolverTest.java index 1a3df37851..84f11f2ae7 100644 --- a/examples/tests/ConstraintSolverTest.java +++ b/examples/tests/ConstraintSolverTest.java @@ -10,96 +10,281 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package com.google.ortools; + +package com.google.ortools.constraintsolver; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.ortools.Loader; -import com.google.ortools.constraintsolver.Assignment; -import com.google.ortools.constraintsolver.AssignmentIntContainer; -import com.google.ortools.constraintsolver.BaseLns; -import com.google.ortools.constraintsolver.Decision; -import com.google.ortools.constraintsolver.DecisionBuilder; -import com.google.ortools.constraintsolver.IntVar; -import com.google.ortools.constraintsolver.IntVarLocalSearchFilter; -import com.google.ortools.constraintsolver.IntVarLocalSearchOperator; -import com.google.ortools.constraintsolver.LocalSearchFilterManager; -import com.google.ortools.constraintsolver.LocalSearchPhaseParameters; -import com.google.ortools.constraintsolver.OptimizeVar; -import com.google.ortools.constraintsolver.SearchLog; -import com.google.ortools.constraintsolver.SearchMonitor; -import com.google.ortools.constraintsolver.SolutionCollector; -import com.google.ortools.constraintsolver.Solver; -import com.google.ortools.constraintsolver.main; -import java.lang.ref.WeakReference; +import com.google.common.collect.Iterables; +import com.google.ortools.constraintsolver.ConstraintSolverParameters; +import com.google.ortools.constraintsolver.RegularLimitParameters; +import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Supplier; -import java.util.logging.Logger; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; /** Tests the Constraint solver java interface. */ -public class ConstraintSolverTest { - private static void gc() { - Object obj = new Object(); - WeakReference ref = new WeakReference(obj); - obj = null; - while (ref.get() != null) { - System.gc(); - } - } - - private static final Logger logger = Logger.getLogger(ConstraintSolverTest.class.getName()); - - @Test - public void testSolverCtor() throws Exception { +public final class ConstraintSolverTest { + @BeforeEach + public void setUp() { Loader.loadNativeLibraries(); - logger.info("testSolverCtor..."); - Solver solver = new Solver("TestSolver"); - if (!solver.model_name().equals("TestSolver")) { - throw new AssertionError("Solver ill formed"); - } - if (solver.toString().length() < 0) { - throw new AssertionError("Solver ill formed"); - } - logger.info("testSolverCtor...DONE"); } @Test - public void testIntVar() throws Exception { - Loader.loadNativeLibraries(); - logger.info("testIntVar..."); - Solver solver = new Solver("Solver"); - IntVar var = solver.makeIntVar(3, 11, "IntVar"); - if (var.min() != 3) { - throw new AssertionError("IntVar Min wrong"); - } - if (var.max() != 11) { - throw new AssertionError("IntVar Max wrong"); - } - logger.info("testIntVar...DONE"); + public void testSolverCtor() { + final Solver solver = new Solver("TestSolver"); + assertNotNull(solver); + assertEquals("TestSolver", solver.model_name()); + assertNotNull(solver.toString()); } @Test - private static void testIntVarArray() throws Exception { - Loader.loadNativeLibraries(); - logger.info("testIntVarArray..."); - Solver solver = new Solver("Solver"); - IntVar[] vars = solver.makeIntVarArray(7, 3, 5, "vars"); - if (vars.length != 7) { - throw new AssertionError("Vars length wrong"); - } + public void testIntVar() { + final Solver solver = new Solver("Solver"); + final IntVar var = solver.makeIntVar(3, 11, "IntVar"); + assertEquals(3, var.min()); + assertEquals(11, var.max()); + } + + @Test + public void testIntVarArray() { + final Solver solver = new Solver("Solver"); + final IntVar[] vars = solver.makeIntVarArray(7, 3, 5, "vars"); + assertThat(vars).hasLength(7); for (IntVar var : vars) { - if (var.min() != 3) { - throw new AssertionError("IntVar Min wrong"); - } - if (var.max() != 5) { - throw new AssertionError("IntVar Max wrong"); - } + assertEquals(3, var.min()); + assertEquals(5, var.max()); } - logger.info("testIntVarArray...DONE"); } + @Test + public void testRabbitsPheasants() { + final Solver solver = new Solver("testRabbitsPheasants"); + final IntVar rabbits = solver.makeIntVar(0, 100, "rabbits"); + final IntVar pheasants = solver.makeIntVar(0, 100, "pheasants"); + solver.addConstraint(solver.makeEquality(solver.makeSum(rabbits, pheasants), 20)); + solver.addConstraint(solver.makeEquality( + solver.makeSum(solver.makeProd(rabbits, 4), solver.makeProd(pheasants, 2)), 56)); + final DecisionBuilder db = + solver.makePhase(rabbits, pheasants, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE); + solver.newSearch(db); + solver.nextSolution(); + assertEquals(8, rabbits.value()); + assertEquals(12, pheasants.value()); + solver.endSearch(); + } + + // A Decision builder that does nothing. + static class DummyDecisionBuilder extends JavaDecisionBuilder { + @Override + public Decision next(Solver solver) throws Solver.FailException { + System.out.println("In Dummy Decision Builder"); + return null; + } + + @Override + public String toString() { + return "DummyDecisionBuilder"; + } + } + + @Test + public void testDummyDecisionBuilder() { + final Solver solver = new Solver("testDummyDecisionBuilder"); + final DecisionBuilder db = new DummyDecisionBuilder(); + assertTrue(solver.solve(db)); + } + + /** + * A decision builder that fails. + */ + static class FailDecisionBuilder extends JavaDecisionBuilder { + @Override + public Decision next(Solver solver) throws Solver.FailException { + System.out.println("In Fail Decision Builder"); + solver.fail(); + return null; + } + } + + @Test + public void testFailDecisionBuilder() { + final Solver solver = new Solver("testFailDecisionBuilder"); + final DecisionBuilder db = new FailDecisionBuilder(); + assertFalse(solver.solve(db)); + } + + /** Golomb Ruler test */ + @Test + public void testGolombRuler() { + final int m = 8; + final Solver solver = new Solver("GR " + m); + + final IntVar[] ticks = solver.makeIntVarArray(m, 0, (1 << (m + 1)) - 1, "ticks"); + + solver.addConstraint(solver.makeEquality(ticks[0], 0)); + + for (int i = 0; i < ticks.length - 1; i++) { + solver.addConstraint(solver.makeLess(ticks[i], ticks[i + 1])); + } + + final ArrayList diff = new ArrayList<>(); + for (int i = 0; i < m - 1; i++) { + for (int j = i + 1; j < m; j++) { + diff.add(solver.makeDifference(ticks[j], ticks[i]).var()); + } + } + + solver.addConstraint(solver.makeAllDifferent(diff.toArray(new IntVar[0]), true)); + + // break symetries + if (m > 2) { + solver.addConstraint(solver.makeLess(diff.get(0), Iterables.getLast(diff))); + } + + final OptimizeVar opt = solver.makeMinimize(ticks[m - 1], 1); + final DecisionBuilder db = + solver.makePhase(ticks, Solver.CHOOSE_MIN_SIZE_LOWEST_MIN, Solver.ASSIGN_MIN_VALUE); + final SearchMonitor log = solver.makeSearchLog(10000, opt); + assertTrue(solver.solve(db, opt, log)); + assertEquals(34, opt.best()); + } + + @Test + public void testElementFunction() { + final Solver solver = new Solver("testElementFunction"); + final IntVar index = solver.makeIntVar(0, 10, "index"); + final IntExpr element = solver.makeElement((long x) -> x * 2, index); + assertEquals(0, element.min()); + assertEquals(20, element.max()); + } + + @Test + public void testSolverParameters() { + final ConstraintSolverParameters parameters = ConstraintSolverParameters.newBuilder() + .mergeFrom(Solver.defaultSolverParameters()) + .setTraceSearch(true) + .build(); + final Solver solver = new Solver("testSolverParameters", parameters); + final ConstraintSolverParameters stored = solver.parameters(); + assertTrue(stored.getTraceSearch()); + } + + @Test + public void testRegularLimitParameters() { + final Solver solver = new Solver("testRegularLimitParameters"); + final RegularLimitParameters protoLimit = + RegularLimitParameters.newBuilder() + .mergeFrom(solver.makeDefaultRegularLimitParameters()) + .setFailures(20000) + .build(); + assertEquals(20000, protoLimit.getFailures()); + final SearchLimit limit = solver.makeLimit(protoLimit); + assertNotNull(limit); + } + + // verify Closure in Decision. + @Test + public void testClosureDecision() { + final StringProperty call = new StringProperty(""); + final Solver solver = new Solver("ClosureDecisionTest"); + final Decision decision = solver.makeDecision( + (Solver s) -> call.setValue("Apply"), (Solver s) -> call.setValue("Refute")); + System.gc(); + + decision.apply(solver); + assertEquals("Apply", call.toString()); + + decision.refute(solver); + assertEquals("Refute", call.toString()); + } + + @Test + public void testSolverInClosureDecision() { + final Solver solver = new Solver("SolverTestName"); + final String modelName = solver.model_name(); + // Lambda can only capture final or implicit final. + final AtomicInteger countApply = new AtomicInteger(0); + final AtomicInteger countRefute = new AtomicInteger(0); + assertEquals(0, countApply.intValue()); + assertEquals(0, countRefute.intValue()); + final Decision decision = solver.makeDecision( + (Solver s) -> { + assertEquals(s.model_name(), modelName); + countApply.addAndGet(1); + }, + (Solver s) -> { + assertEquals(s.model_name(), modelName); + countRefute.addAndGet(1); + }); + System.gc(); // verify lambda are kept alive + + decision.apply(solver); + assertEquals(1, countApply.intValue()); + assertEquals(0, countRefute.intValue()); + + decision.refute(solver); + assertEquals(1, countApply.intValue()); + assertEquals(1, countRefute.intValue()); + } + + // A Decision builder that does nothing + public static class ActionDecisionBuilder extends JavaDecisionBuilder { + Consumer apply; + Consumer refute; + boolean passed; + + public ActionDecisionBuilder(Consumer a, Consumer r) { + apply = a; + refute = r; + passed = false; + } + + @Override + public Decision next(Solver solver) throws Solver.FailException { + if (passed) { + return null; + } + passed = true; + return solver.makeDecision(apply, refute); + } + + @Override + public String toString() { + return "ActionDecisionBuilder"; + } + } + + // Tests the ActionDecisionBuilder. + @Test + public void testActionDecisionBuilder() { + final Solver solver = new Solver("testActionDecisionBuilder"); + // Lambda can only capture final or implicit final + final AtomicInteger countApply = new AtomicInteger(0); + final AtomicInteger countRefute = new AtomicInteger(0); + assertEquals(0, countApply.intValue()); + assertEquals(0, countRefute.intValue()); + final DecisionBuilder db = new ActionDecisionBuilder( + (Solver s) -> countApply.addAndGet(1), + (Solver s) -> countRefute.addAndGet(1)); + solver.newSearch(db); + assertTrue(solver.nextSolution()); + assertEquals(1, countApply.intValue()); + assertEquals(0, countRefute.intValue()); + assertTrue(solver.nextSolution()); + assertEquals(1, countApply.intValue()); + assertEquals(1, countRefute.intValue()); + solver.endSearch(); + } + + // ----- LocalSearch Test ----- private static class MoveOneVar extends IntVarLocalSearchOperator { public MoveOneVar(IntVar[] variables) { super(variables); @@ -130,23 +315,22 @@ public class ConstraintSolverTest { } @Test - public void testSolver() throws Exception { - Loader.loadNativeLibraries(); - Solver solver = new Solver("Solver"); - IntVar[] vars = solver.makeIntVarArray(4, 0, 4, "vars"); - IntVar sumVar = solver.makeSum(vars).var(); - OptimizeVar obj = solver.makeMinimize(sumVar, 1); - DecisionBuilder db = + public void testSolver() { + final Solver solver = new Solver("Solver"); + final IntVar[] vars = solver.makeIntVarArray(4, 0, 4, "vars"); + final IntVar sumVar = solver.makeSum(vars).var(); + final OptimizeVar obj = solver.makeMinimize(sumVar, 1); + final DecisionBuilder db = solver.makePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE); - MoveOneVar moveOneVar = new MoveOneVar(vars); - LocalSearchPhaseParameters lsParams = + final MoveOneVar moveOneVar = new MoveOneVar(vars); + final LocalSearchPhaseParameters lsParams = solver.makeLocalSearchPhaseParameters(sumVar, moveOneVar, db); - DecisionBuilder ls = solver.makeLocalSearchPhase(vars, db, lsParams); - SolutionCollector collector = solver.makeLastSolutionCollector(); + final DecisionBuilder ls = solver.makeLocalSearchPhase(vars, db, lsParams); + final SolutionCollector collector = solver.makeLastSolutionCollector(); collector.addObjective(sumVar); - SearchMonitor log = solver.makeSearchLog(1000, obj); - solver.solve(ls, collector, obj, log); - logger.info("Objective value = " + collector.objectiveValue(0)); + final SearchMonitor log = solver.makeSearchLog(1000, obj); + + assertTrue(solver.solve(ls, collector, obj, log)); } private static class SumFilter extends IntVarLocalSearchFilter { @@ -183,32 +367,44 @@ public class ConstraintSolverTest { } return newSum < sum; } - private long sum; } + private static class StringProperty { + public StringProperty(String initialValue) { + value = initialValue; + } + public void setValue(String newValue) { + value = newValue; + } + + @Override + public String toString() { + return value; + } + private String value; + } + @Test - public void testSolverWithFilter() throws Exception { - Loader.loadNativeLibraries(); - Solver solver = new Solver("Solver"); - IntVar[] vars = solver.makeIntVarArray(4, 0, 4, "vars"); - IntVar sumVar = solver.makeSum(vars).var(); - OptimizeVar obj = solver.makeMinimize(sumVar, 1); - DecisionBuilder db = + public void testSolverWithLocalSearchFilter() { + final Solver solver = new Solver("Solver"); + final IntVar[] vars = solver.makeIntVarArray(4, 0, 4, "vars"); + final IntVar sumVar = solver.makeSum(vars).var(); + final OptimizeVar obj = solver.makeMinimize(sumVar, 1); + final DecisionBuilder db = solver.makePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE); MoveOneVar moveOneVar = new MoveOneVar(vars); SumFilter filter = new SumFilter(vars); IntVarLocalSearchFilter[] filters = new IntVarLocalSearchFilter[1]; filters[0] = filter; - LocalSearchFilterManager filter_manager = new LocalSearchFilterManager(filters); + LocalSearchFilterManager filterManager = new LocalSearchFilterManager(filters); LocalSearchPhaseParameters lsParams = - solver.makeLocalSearchPhaseParameters(sumVar, moveOneVar, db, null, filter_manager); + solver.makeLocalSearchPhaseParameters(sumVar, moveOneVar, db, null, filterManager); DecisionBuilder ls = solver.makeLocalSearchPhase(vars, db, lsParams); SolutionCollector collector = solver.makeLastSolutionCollector(); collector.addObjective(sumVar); SearchMonitor log = solver.makeSearchLog(1000, obj); solver.solve(ls, collector, obj, log); - logger.info("Objective value = " + collector.objectiveValue(0)); } private static class OneVarLns extends BaseLns { @@ -218,44 +414,44 @@ public class ConstraintSolverTest { @Override public void initFragments() { - index_ = 0; + index = 0; } @Override public boolean nextFragment() { int size = size(); - if (index_ < size) { - appendToFragment(index_); - ++index_; + if (index < size) { + appendToFragment(index); + ++index; return true; } else { return false; } } - - private int index_; + private int index; } @Test - public void testSolverLns() throws Exception { - Loader.loadNativeLibraries(); - Solver solver = new Solver("Solver"); - IntVar[] vars = solver.makeIntVarArray(4, 0, 4, "vars"); - IntVar sumVar = solver.makeSum(vars).var(); - OptimizeVar obj = solver.makeMinimize(sumVar, 1); - DecisionBuilder db = + public void testSolverLns() { + final Solver solver = new Solver("Solver"); + final IntVar[] vars = solver.makeIntVarArray(4, 0, 4, "vars"); + final IntVar sumVar = solver.makeSum(vars).var(); + final OptimizeVar obj = solver.makeMinimize(sumVar, 1); + final DecisionBuilder db = solver.makePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE); - OneVarLns oneVarLns = new OneVarLns(vars); - LocalSearchPhaseParameters lsParams = + final OneVarLns oneVarLns = new OneVarLns(vars); + final LocalSearchPhaseParameters lsParams = solver.makeLocalSearchPhaseParameters(sumVar, oneVarLns, db); - DecisionBuilder ls = solver.makeLocalSearchPhase(vars, db, lsParams); - SolutionCollector collector = solver.makeLastSolutionCollector(); + final DecisionBuilder ls = solver.makeLocalSearchPhase(vars, db, lsParams); + final SolutionCollector collector = solver.makeLastSolutionCollector(); collector.addObjective(sumVar); - SearchMonitor log = solver.makeSearchLog(1000, obj); + final SearchMonitor log = solver.makeSearchLog(1000, obj); solver.solve(ls, collector, obj, log); - logger.info("Objective value = " + collector.objectiveValue(0)); } + // ----- SearchLog Test ----- + // TODO(user): Improve search log tests; currently only tests coverage and callback. + // note: this is more or less what is done in search_test.cc private static void runSearchLog(SearchMonitor searchlog) { searchlog.enterSearch(); searchlog.exitSearch(); @@ -269,160 +465,79 @@ public class ConstraintSolverTest { // Simple Coverage test... @Test - public void testSearchLog() throws Exception { - Loader.loadNativeLibraries(); - logger.info("testSearchLog..."); - Solver solver = new Solver("TestSearchLog"); - IntVar var = solver.makeIntVar(1, 1, "Variable"); - OptimizeVar objective = solver.makeMinimize(var, 1); - SearchMonitor searchlog = solver.makeSearchLog(0); + public void testSearchLog() { + final Solver solver = new Solver("TestSearchLog"); + final IntVar var = solver.makeIntVar(1, 1, "Variable"); + solver.makeMinimize(var, 1); + final SearchMonitor searchlog = solver.makeSearchLog(0); runSearchLog(searchlog); - logger.info("testSearchLog...DONE"); } private static class SearchCount implements Supplier { - public SearchCount(AtomicInteger count_) { - count = count_; + public SearchCount(AtomicInteger initialCount) { + count = initialCount; } @Override public String get() { count.addAndGet(1); return "display callback called..."; } - private AtomicInteger count; + private final AtomicInteger count; } - @ParameterizedTest - @ValueSource(booleans = {false, true}) - public void testSearchLogWithCallback(boolean enableGC) throws Exception { - Loader.loadNativeLibraries(); - logger.info("testSearchLogWithCallback (enable gc:" + enableGC + ")..."); - Solver solver = new Solver("TestSearchLog"); - IntVar var = solver.makeIntVar(1, 1, "Variable"); - OptimizeVar objective = solver.makeMinimize(var, 1); - AtomicInteger count = new AtomicInteger(0); - SearchMonitor searchlog = solver.makeSearchLog(0, // branch period + @Test + public void testSearchLogWithCallback() { + final Solver solver = new Solver("TestSearchLog"); + final AtomicInteger count = new AtomicInteger(0); + final SearchMonitor searchlog = solver.makeSearchLog( + 0, // branch period new SearchCount(count)); - if (enableGC) { - gc(); - } + System.gc(); // verify SearchCount is kept alive by the searchlog runSearchLog(searchlog); - logger.info("count:" + count.intValue()); - - if (count.intValue() != 1) - throw new AssertionError("count != 1"); - ; - logger.info("testSearchLogWithCallback (enable gc:" + enableGC + ")...DONE"); + assertEquals(1, count.intValue()); } - @ParameterizedTest - @ValueSource(booleans = {false, true}) - public void testSearchLogWithIntVarCallback(boolean enableGC) throws Exception { - Loader.loadNativeLibraries(); - logger.info("testSearchLogWithIntVarCallback (enable gc:" + enableGC + ")..."); - Solver solver = new Solver("TestSearchLog"); - IntVar var = solver.makeIntVar(1, 1, "Variable"); - OptimizeVar objective = solver.makeMinimize(var, 1); - AtomicInteger count = new AtomicInteger(0); - SearchMonitor searchlog = solver.makeSearchLog(0, // branch period + @Test + public void testSearchLogWithLambdaCallback() { + final Solver solver = new Solver("TestSearchLog"); + final AtomicInteger count = new AtomicInteger(0); + final SearchMonitor searchlog = solver.makeSearchLog( + 0, // branch period + () -> { + count.addAndGet(1); + return "display callback called..."; + }); + System.gc(); // verify lambda is kept alive by the searchlog + runSearchLog(searchlog); + assertEquals(1, count.intValue()); + } + + @Test + public void testSearchLogWithIntVarCallback() { + final Solver solver = new Solver("TestSearchLog"); + final IntVar var = solver.makeIntVar(1, 1, "Variable"); + final AtomicInteger count = new AtomicInteger(0); + final SearchMonitor searchlog = solver.makeSearchLog( + 0, // branch period var, // IntVar to monitor new SearchCount(count)); - if (enableGC) { - gc(); - } + System.gc(); runSearchLog(searchlog); - if (count.intValue() != 1) - throw new AssertionError("count != 1"); - ; - logger.info("testSearchLogWithIntVarCallback (enable gc:" + enableGC + ")...DONE"); + assertEquals(1, count.intValue()); } - @ParameterizedTest - @ValueSource(booleans = {false, true}) - public void testSearchLogWithObjectiveCallback(boolean enableGC) throws Exception { - Loader.loadNativeLibraries(); - logger.info("testSearchLogWithObjectiveCallback (enable gc:" + enableGC + ")..."); - Solver solver = new Solver("TestSearchLog"); - IntVar var = solver.makeIntVar(1, 1, "Variable"); - OptimizeVar objective = solver.makeMinimize(var, 1); - AtomicInteger count = new AtomicInteger(0); - SearchMonitor searchlog = solver.makeSearchLog(0, // branch period + @Test + public void testSearchLogWithObjectiveCallback() { + final Solver solver = new Solver("TestSearchLog"); + final IntVar var = solver.makeIntVar(1, 1, "Variable"); + final OptimizeVar objective = solver.makeMinimize(var, 1); + final AtomicInteger count = new AtomicInteger(0); + final SearchMonitor searchlog = solver.makeSearchLog( + 0, // branch period objective, // objective var to monitor new SearchCount(count)); - if (enableGC) { - gc(); - } + System.gc(); runSearchLog(searchlog); - if (count.intValue() != 1) - throw new AssertionError("count != 1"); - ; - logger.info("testSearchLogWithObjectiveCallback (enable gc:" + enableGC + ")...DONE"); - } - - private static class StringProperty { - public StringProperty(String value) { - value_ = value; - } - public void setValue(String value) { - value_ = value; - ; - } - public String toString() { - return value_; - } - private String value_; - } - - @ParameterizedTest - @ValueSource(booleans = {false, true}) - public void testClosureDecision(boolean enableGC) throws Exception { - Loader.loadNativeLibraries(); - logger.info("testClosureDecision (enable gc:" + enableGC + ")..."); - final StringProperty call = new StringProperty(""); - Solver solver = new Solver("ClosureDecisionTest"); - Decision decision = solver.makeDecision( - (Solver s) -> { call.setValue("Apply"); }, (Solver s) -> { call.setValue("Refute"); }); - if (enableGC) { - gc(); - } - - decision.apply(solver); - if (!call.toString().equals("Apply")) { - throw new AssertionError("Apply action not called"); - } - - decision.refute(solver); - if (!call.toString().equals("Refute")) { - throw new AssertionError("Refute action not called"); - } - logger.info("testClosureDecision (enable gc:" + enableGC + ")...DONE"); - } - - @ParameterizedTest - @ValueSource(booleans = {false, true}) - public void testSolverInClosureDecision(boolean enableGC) throws Exception { - Loader.loadNativeLibraries(); - logger.info("testSolverInClosureDecision (enable gc:" + enableGC + ")..."); - Solver solver = new Solver("SolverTestName"); - String model_name = solver.model_name(); - Decision decision = solver.makeDecision( - (Solver s) - -> { - if (!s.model_name().equals(model_name)) { - throw new AssertionError("Solver ill formed"); - } - }, - (Solver s) -> { - if (!s.model_name().equals(model_name)) { - throw new AssertionError("Solver ill formed"); - } - }); - if (enableGC) { - gc(); // verify SearchCount is kept alive - } - - decision.apply(solver); - decision.refute(solver); - logger.info("testSolverInClosureDecision (enable gc:" + enableGC + ")...DONE"); + assertEquals(1, count.intValue()); } } diff --git a/examples/tests/ConstraintSolverTests.cs b/examples/tests/ConstraintSolverTests.cs index ed2136e838..4143b99e28 100644 --- a/examples/tests/ConstraintSolverTests.cs +++ b/examples/tests/ConstraintSolverTests.cs @@ -830,7 +830,7 @@ namespace Google.OrTools.Tests Assert.Equal(3, ct.count()); } - // TODO(mizux): Improve search log tests; currently only tests coverage. + // TODO(user): Improve search log tests; currently only tests coverage. void RunSearchLog(in SearchMonitor searchlog) { searchlog.EnterSearch(); diff --git a/examples/tests/CpModelTest.java b/examples/tests/CpModelTest.java new file mode 100644 index 0000000000..04b60f87e8 --- /dev/null +++ b/examples/tests/CpModelTest.java @@ -0,0 +1,231 @@ +// Copyright 2010-2021 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.ortools.sat; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.google.ortools.Loader; +import com.google.ortools.util.Domain; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Tests the CpSolver java interface. */ +public final class CpModelTest { + @BeforeEach + public void setUp() { + Loader.loadNativeLibraries(); + } + + @Test + public void testCpModel_newIntVar() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newIntVar(0, 10, "x"); + final IntVar y = model.newIntVarFromDomain(Domain.fromValues(new long[] {0, 1, 2, 5}), "y"); + final IntVar z = + model.newIntVarFromDomain(Domain.fromIntervals(new long[][] {{0, 2}, {5}}), ""); + final IntVar t = model.newBoolVar("t"); + final IntVar u = model.newConstant(5); + + assertThat(x.getName()).isEqualTo("x"); + assertThat(y.getName()).isEqualTo("y"); + assertThat(z.getName()).isEmpty(); + assertThat(t.getName()).isEqualTo("t"); + assertThat(u.getName()).isEmpty(); + assertThat(x.getDomain().flattenedIntervals()).isEqualTo(new long[] {0, 10}); + + assertThat(x.getShortString()).isEqualTo("x(0..10)"); + assertThat(y.getShortString()).isEqualTo("y(0..2, 5)"); + assertThat(z.getShortString()).isEqualTo("var_2(0..2, 5)"); + assertThat(t.getShortString()).isEqualTo("t(0..1)"); + assertThat(u.getShortString()).isEqualTo("5"); + } + + @Test + public void testCpModel_addBoolOr() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newBoolVar("x"); + final IntVar y = model.newBoolVar("y"); + final IntVar z = model.newBoolVar("z"); + model.addBoolOr(new Literal[] {x, y.not(), z}); + + assertThat(model.model().getConstraintsCount()).isEqualTo(1); + assertThat(model.model().getConstraints(0).hasBoolOr()).isTrue(); + assertThat(model.model().getConstraints(0).getBoolOr().getLiteralsCount()).isEqualTo(3); + } + + @Test + public void testCpModel_addBoolAnd() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newBoolVar("x"); + final IntVar y = model.newBoolVar("y"); + final IntVar z = model.newBoolVar("z"); + model.addBoolAnd(new Literal[] {x, y.not(), z}); + + assertThat(model.model().getConstraintsCount()).isEqualTo(1); + assertThat(model.model().getConstraints(0).hasBoolAnd()).isTrue(); + assertThat(model.model().getConstraints(0).getBoolAnd().getLiteralsCount()).isEqualTo(3); + } + + @Test + public void testCpModel_addBoolXor() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newBoolVar("x"); + final IntVar y = model.newBoolVar("y"); + final IntVar z = model.newBoolVar("z"); + model.addBoolXor(new Literal[] {x, y.not(), z}); + + assertThat(model.model().getConstraintsCount()).isEqualTo(1); + assertThat(model.model().getConstraints(0).hasBoolXor()).isTrue(); + assertThat(model.model().getConstraints(0).getBoolXor().getLiteralsCount()).isEqualTo(3); + } + + @Test + public void testCpModel_addImplication() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newBoolVar("x"); + final IntVar y = model.newBoolVar("y"); + model.addImplication(x, y); + + assertThat(model.model().getConstraintsCount()).isEqualTo(1); + assertThat(model.model().getConstraints(0).hasBoolOr()).isTrue(); + assertThat(model.model().getConstraints(0).getBoolOr().getLiteralsCount()).isEqualTo(2); + } + + @Test + public void testCpModel_addLinear() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newBoolVar("x"); + final IntVar y = model.newBoolVar("y"); + + model.addEquality(LinearExpr.sum(new IntVar[] {x, y}), 1); + assertThat(model.model().getConstraintsCount()).isEqualTo(1); + assertThat(model.model().getConstraints(0).hasLinear()).isTrue(); + assertThat(model.model().getConstraints(0).getLinear().getVarsCount()).isEqualTo(2); + + final IntVar b = model.newBoolVar("b"); + model.addEquality(LinearExpr.sum(new IntVar[] {x, y}), 2).onlyEnforceIf(b.not()); + assertThat(model.model().getConstraintsCount()).isEqualTo(2); + assertThat(model.model().getConstraints(1).hasLinear()).isTrue(); + assertThat(model.model().getConstraints(1).getEnforcementLiteralCount()).isEqualTo(1); + assertThat(model.model().getConstraints(1).getEnforcementLiteral(0)).isEqualTo(-3); + + final IntVar c = model.newBoolVar("c"); + model.addEquality(LinearExpr.sum(new IntVar[] {x, y}), 3).onlyEnforceIf(new Literal[] {b, c}); + assertThat(model.model().getConstraintsCount()).isEqualTo(3); + assertThat(model.model().getConstraints(2).hasLinear()).isTrue(); + assertThat(model.model().getConstraints(2).getEnforcementLiteralCount()).isEqualTo(2); + assertThat(model.model().getConstraints(2).getEnforcementLiteral(0)).isEqualTo(2); + assertThat(model.model().getConstraints(2).getEnforcementLiteral(1)).isEqualTo(3); + } + + @Test + public void testCpModel_addNoOverlap() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final int horizon = 100; + final IntVar startVar1 = model.newIntVar(0, horizon, "start1"); + final IntVar endVar1 = model.newIntVar(0, horizon, "end1"); + // Java code supports IntVar or integer constants in intervals. + final int duration1 = 10; + final IntervalVar interval1 = model.newIntervalVar(startVar1, duration1, endVar1, "interval1"); + final IntVar startVar2 = model.newIntVar(0, horizon, "start2"); + final IntVar endVar2 = model.newIntVar(0, horizon, "end2"); + // Java code supports IntVar or integer constants in intervals. + final int duration2 = 15; + final IntervalVar interval2 = model.newIntervalVar(startVar2, duration2, endVar2, "interval2"); + model.addNoOverlap(new IntervalVar[] {interval1, interval2}); + assertThat(model.model().getConstraintsCount()).isEqualTo(3); + assertThat(model.model().getConstraints(0).hasInterval()).isTrue(); + assertThat(model.model().getConstraints(1).hasInterval()).isTrue(); + assertThat(model.model().getConstraints(2).hasNoOverlap()).isTrue(); + assertThat(model.model().getConstraints(2).getNoOverlap().getIntervalsCount()).isEqualTo(2); + } + + @Test + public void testCpModel_addNoOverlap2D() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final int horizon = 100; + final IntVar startVar1 = model.newIntVar(0, horizon, "start1"); + final IntVar endVar1 = model.newIntVar(0, horizon, "end1"); + // Java code supports IntVar or integer constants in intervals. + final int duration1 = 10; + final IntervalVar interval1 = model.newIntervalVar(startVar1, duration1, endVar1, "interval1"); + final IntVar startVar2 = model.newIntVar(0, horizon, "start2"); + final IntVar endVar2 = model.newIntVar(0, horizon, "end2"); + // Java code supports IntVar or integer constants in intervals. + final int duration2 = 15; + final IntervalVar interval2 = model.newIntervalVar(startVar2, duration2, endVar2, "interval2"); + model.addNoOverlap2D( + new IntervalVar[] {interval1, interval2}, new IntervalVar[] {interval1, interval2}); + assertThat(model.model().getConstraintsCount()).isEqualTo(3); + assertThat(model.model().getConstraints(0).hasInterval()).isTrue(); + assertThat(model.model().getConstraints(1).hasInterval()).isTrue(); + assertThat(model.model().getConstraints(2).hasNoOverlap2D()).isTrue(); + assertThat(model.model().getConstraints(2).getNoOverlap2D().getXIntervalsCount()).isEqualTo(2); + assertThat(model.model().getConstraints(2).getNoOverlap2D().getYIntervalsCount()).isEqualTo(2); + } + + @Test + public void testCpModel_modelStats() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newBoolVar("x"); + final IntVar y = model.newBoolVar("y"); + model.addImplication(x, y); + + String stats = model.modelStats(); + assertThat(stats).isNotEmpty(); + } + + @Test + public void testCpModel_validateOk() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newBoolVar("x"); + final IntVar y = model.newBoolVar("y"); + model.addImplication(x, y); + + String stats = model.validate(); + assertThat(stats).isEmpty(); + } + + @Test + public void testCpModel_validateNotOk() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newIntVar(0, 9223372036854775807L, "x"); + final IntVar y = model.newIntVar(0, 10, "y"); + model.addLinearExpressionInDomain(LinearExpr.sum(new IntVar[] {x, y}), + Domain.fromFlatIntervals(new long[] {6, 9223372036854775807L})); + + String stats = model.validate(); + assertThat(stats).isNotEmpty(); + } + + @Test + public void testCpModel_exceptionVisibility() throws Exception { + CpModel.MismatchedArrayLengths ex1 = new CpModel.MismatchedArrayLengths("test1", "ar1", "ar2"); + CpModel.WrongLength ex2 = new CpModel.WrongLength("test2", "ar"); + assertThat(ex1).hasMessageThat().isEqualTo("test1: ar1 and ar2 have mismatched lengths"); + assertThat(ex2).hasMessageThat().isEqualTo("test2: ar"); + } +} diff --git a/examples/tests/CpSolverTest.java b/examples/tests/CpSolverTest.java new file mode 100644 index 0000000000..b180e856cf --- /dev/null +++ b/examples/tests/CpSolverTest.java @@ -0,0 +1,260 @@ +// Copyright 2010-2021 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.ortools.sat; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.google.ortools.Loader; +import com.google.ortools.sat.CpSolverStatus; +import java.util.function.Consumer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Tests the CpSolver java interface. */ +public final class CpSolverTest { + @BeforeEach + public void setUp() { + Loader.loadNativeLibraries(); + } + + static class SolutionCounter extends CpSolverSolutionCallback { + public SolutionCounter() {} + + @Override + public void onSolutionCallback() { + solutionCount++; + } + + private int solutionCount; + + public int getSolutionCount() { + return solutionCount; + } + } + + static class LogToString { + public LogToString() { + logBuilder = new StringBuilder(); + } + + public void newMessage(String message) { + logBuilder.append(message).append("\n"); + } + + private final StringBuilder logBuilder; + + public String getLog() { + return logBuilder.toString(); + } + } + + @Test + public void testCpSolver_solve() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + // Creates the variables. + int numVals = 3; + + final IntVar x = model.newIntVar(0, numVals - 1, "x"); + final IntVar y = model.newIntVar(0, numVals - 1, "y"); + // Creates the constraints. + model.addDifferent(x, y); + + // Creates a solver and solves the model. + final CpSolver solver = new CpSolver(); + assertNotNull(solver); + final CpSolverStatus status = solver.solve(model); + + assertThat(status).isEqualTo(CpSolverStatus.OPTIMAL); + assertThat(solver.value(x)).isNotEqualTo(solver.value(y)); + final String stats = solver.responseStats(); + assertThat(stats).isNotEmpty(); + } + + @Test + public void testCpSolver_hinting() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newIntVar(0, 5, "x"); + final IntVar y = model.newIntVar(0, 6, "y"); + // Creates the constraints. + model.addEquality(LinearExpr.sum(new IntVar[] {x, y}), 6); + + // Add hints. + model.addHint(x, 2); + model.addHint(y, 4); + + // Creates a solver and solves the model. + final CpSolver solver = new CpSolver(); + assertNotNull(solver); + solver.getParameters().setCpModelPresolve(false); + final CpSolverStatus status = solver.solve(model); + + assertThat(status).isEqualTo(CpSolverStatus.OPTIMAL); + assertThat(solver.value(x)).isEqualTo(2); + assertThat(solver.value(y)).isEqualTo(4); + } + + @Test + public void testCpSolver_booleanValue() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + final IntVar x = model.newBoolVar("x"); + final IntVar y = model.newBoolVar("y"); + model.addBoolOr(new Literal[] {x, y.not()}); + + // Creates a solver and solves the model. + final CpSolver solver = new CpSolver(); + assertNotNull(solver); + final CpSolverStatus status = solver.solve(model); + + assertEquals(CpSolverStatus.OPTIMAL, status); + assertThat(solver.booleanValue(x) || solver.booleanValue(y.not())).isTrue(); + } + + @Test + public void testCpSolver_searchAllSolutions() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + // Creates the variables. + int numVals = 3; + final IntVar x = model.newIntVar(0, numVals - 1, "x"); + final IntVar y = model.newIntVar(0, numVals - 1, "y"); + model.newIntVar(0, numVals - 1, "z"); + // Creates the constraints. + model.addDifferent(x, y); + + // Creates a solver and solves the model. + final CpSolver solver = new CpSolver(); + assertNotNull(solver); + final SolutionCounter cb = new SolutionCounter(); + solver.searchAllSolutions(model, cb); + + assertThat(cb.getSolutionCount()).isEqualTo(18); + assertThat(solver.numBranches()).isGreaterThan(0L); + } + + @Test + public void testCpSolver_objectiveValue() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + // Creates the variables. + final int numVals = 3; + final IntVar x = model.newIntVar(0, numVals - 1, "x"); + final IntVar y = model.newIntVar(0, numVals - 1, "y"); + final IntVar z = model.newIntVar(0, numVals - 1, "z"); + // Creates the constraints. + model.addDifferent(x, y); + + // Maximizes a linear combination of variables. + model.maximize(LinearExpr.scalProd(new IntVar[] {x, y, z}, new int[] {1, 2, 3})); + + // Creates a solver and solves the model. + final CpSolver solver = new CpSolver(); + assertNotNull(solver); + CpSolverStatus status = solver.solve(model); + + assertThat(status).isEqualTo(CpSolverStatus.OPTIMAL); + assertThat(solver.objectiveValue()).isEqualTo(11.0); + } + + @Test + public void testCpModel_crashPresolve() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + // Create decision variables + final IntVar x = model.newIntVar(0, 5, "x"); + final IntVar y = model.newIntVar(0, 5, "y"); + + // Create a linear constraint which enforces that only x or y can be greater than 0. + model.addLinearConstraint(LinearExpr.sum(new IntVar[] {x, y}), 0, 1); + + // Create the objective variable + final IntVar obj = model.newIntVar(0, 3, "obj"); + // Cut the domain of the objective variable + model.addGreaterOrEqual(obj, 2); + // Set a constraint that makes the problem infeasible + model.addMaxEquality(obj, new IntVar[] {x, y}); + // Optimize objective + model.minimize(obj); + + // Create a solver and solve the model. + final CpSolver solver = new CpSolver(); + assertNotNull(solver); + com.google.ortools.sat.CpSolverStatus status = solver.solve(model); + assertThat(status).isEqualTo(CpSolverStatus.INFEASIBLE); + } + + @Test + public void testCpSolver_customLog() throws Exception { + final CpModel model = new CpModel(); + assertNotNull(model); + // Creates the variables. + final int numVals = 3; + final IntVar x = model.newIntVar(0, numVals - 1, "x"); + final IntVar y = model.newIntVar(0, numVals - 1, "y"); + // Creates the constraints. + model.addDifferent(x, y); + + // Creates a solver and solves the model. + final CpSolver solver = new CpSolver(); + assertNotNull(solver); + StringBuilder logBuilder = new StringBuilder(); + Consumer appendToLog = (String message) -> logBuilder.append(message).append('\n'); + solver.setLogCallback(appendToLog); + solver.getParameters().setLogToStdout(false).setLogSearchProgress(true); + CpSolverStatus status = solver.solve(model); + + assertThat(status).isEqualTo(CpSolverStatus.OPTIMAL); + String log = logBuilder.toString(); + assertThat(log).isNotEmpty(); + assertThat(log).contains("Parameters"); + assertThat(log).contains("log_to_stdout: false"); + assertThat(log).contains("OPTIMAL"); + } + + @Test + public void testCpSolver_customLogMultiThread() { + final CpModel model = new CpModel(); + assertNotNull(model); + // Creates the variables. + int numVals = 3; + + IntVar x = model.newIntVar(0, numVals - 1, "x"); + IntVar y = model.newIntVar(0, numVals - 1, "y"); + // Creates the constraints. + model.addDifferent(x, y); + + // Creates a solver and solves the model. + final CpSolver solver = new CpSolver(); + assertNotNull(solver); + StringBuilder logBuilder = new StringBuilder(); + Consumer appendToLog = (String message) -> logBuilder.append(message).append('\n'); + solver.setLogCallback(appendToLog); + solver.getParameters() + .setLogToStdout(false) + .setLogSearchProgress(true) + .setNumSearchWorkers(12); + CpSolverStatus status = solver.solve(model); + + assertThat(status).isEqualTo(CpSolverStatus.OPTIMAL); + String log = logBuilder.toString(); + assertThat(log).isNotEmpty(); + assertThat(log).contains("Parameters"); + assertThat(log).contains("log_to_stdout: false"); + assertThat(log).contains("OPTIMAL"); + } +} diff --git a/examples/tests/FlowTest.java b/examples/tests/FlowTest.java new file mode 100644 index 0000000000..562064fe56 --- /dev/null +++ b/examples/tests/FlowTest.java @@ -0,0 +1,83 @@ +// Copyright 2010-2021 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.ortools.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.google.ortools.Loader; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Test the Min/Max Flow solver java interface. */ +public final class FlowTest { + @BeforeEach + public void setUp() { + Loader.loadNativeLibraries(); + } + + @Test + public void testMinCostFlow() { + final int numSources = 4; + final int numTargets = 4; + final int[][] costs = {{90, 75, 75, 80}, + {35, 85, 55, 65}, + {125, 95, 90, 105}, + {45, 110, 95, 115}}; + final int expectedCost = 275; + final MinCostFlow minCostFlow = new MinCostFlow(); + assertNotNull(minCostFlow); + for (int source = 0; source < numSources; ++source) { + for (int target = 0; target < numTargets; ++target) { + minCostFlow.addArcWithCapacityAndUnitCost( + source, numSources + target, 1, costs[source][target]); + } + } + + for (int source = 0; source < numSources; ++source) { + minCostFlow.setNodeSupply(source, 1); + } + for (int target = 0; target < numTargets; ++target) { + minCostFlow.setNodeSupply(numSources + target, -1); + } + final MinCostFlowBase.Status solveStatus = minCostFlow.solve(); + assertEquals(solveStatus, MinCostFlow.Status.OPTIMAL); + final long totalFlowCost = minCostFlow.getOptimalCost(); + assertEquals(expectedCost, totalFlowCost); + } + + @Test + public void testMaxFlow() { + final int numNodes = 6; + final int numArcs = 9; + final int[] tails = {0, 0, 0, 0, 1, 2, 3, 3, 4}; + final int[] heads = {1, 2, 3, 4, 3, 4, 4, 5, 5}; + final int[] capacities = {5, 8, 2, 0, 4, 5, 6, 0, 4}; + final int[] expectedFlows = {4, 4, 2, 0, 4, 4, 0, 6, 4}; + final int expectedTotalFlow = 10; + final MaxFlow maxFlow = new MaxFlow(); + assertNotNull(maxFlow); + for (int i = 0; i < numArcs; ++i) { + maxFlow.addArcWithCapacity(tails[i], heads[i], capacities[i]); + } + maxFlow.setArcCapacity(7, 6); + final MaxFlow.Status solveStatus = maxFlow.solve(/*source=*/0, /*sink=*/numNodes - 1); + assertEquals(solveStatus, MaxFlow.Status.OPTIMAL); + final long totalFlow = maxFlow.getOptimalFlow(); + assertEquals(expectedTotalFlow, totalFlow); + for (int i = 0; i < numArcs; ++i) { + assertEquals(maxFlow.getFlow(i), expectedFlows[i]); + } + } +} diff --git a/examples/tests/KnapsackSolverTest.java b/examples/tests/KnapsackSolverTest.java new file mode 100644 index 0000000000..d41d69a727 --- /dev/null +++ b/examples/tests/KnapsackSolverTest.java @@ -0,0 +1,54 @@ +// Copyright 2010-2021 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.ortools.algorithms; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.google.ortools.Loader; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Test the Knapsack solver java interface. */ +public final class KnapsackSolverTest { + @BeforeEach + public void setUp() { + Loader.loadNativeLibraries(); + } + + @Test + public void testKnapsackSolver() { + final KnapsackSolver solver = new KnapsackSolver( + KnapsackSolver.SolverType.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, "test"); + assertNotNull(solver); + + final long[] profits = {360, 83, 59, 130, 431, 67, 230, 52, 93, + 125, 670, 892, 600, 38, 48, 147, 78, 256, + 63, 17, 120, 164, 432, 35, 92, 110, 22, + 42, 50, 323, 514, 28, 87, 73, 78, 15, + 26, 78, 210, 36, 85, 189, 274, 43, 33, + 10, 19, 389, 276, 312}; + final long[][] weights = {{7, 0, 30, 22, 80, 94, 11, 81, 70, + 64, 59, 18, 0, 36, 3, 8, 15, 42, + 9, 0, 42, 47, 52, 32, 26, 48, 55, + 6, 29, 84, 2, 4, 18, 56, 7, 29, + 93, 44, 71, 3, 86, 66, 31, 65, 0, + 79, 20, 65, 52, 13}}; + final long[] capacities = {850}; + solver.init(profits, weights, capacities); + + final long computedProfit = solver.solve(); + assertEquals(7534, computedProfit); + } +} diff --git a/examples/tests/LinearExprTest.java b/examples/tests/LinearExprTest.java new file mode 100644 index 0000000000..694400e812 --- /dev/null +++ b/examples/tests/LinearExprTest.java @@ -0,0 +1,167 @@ +// Copyright 2010-2021 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.ortools.sat; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.google.ortools.Loader; +import com.google.ortools.util.Domain; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public final class LinearExprTest { + @BeforeEach + public void setUp() { + Loader.loadNativeLibraries(); + } + + @Test + public void testLinearExpr_term() { + final CpModel model = new CpModel(); + assertNotNull(model); + final Domain domain = new Domain(0, 10); + final IntVar y = model.newIntVarFromDomain(domain, "y"); + + final LinearExpr expr = LinearExpr.term(y, 12); + assertNotNull(expr); + + assertEquals(1, expr.numElements()); + assertEquals(y, expr.getVariable(0)); + assertEquals(12, expr.getCoefficient(0)); + assertEquals(0, expr.getOffset()); + } + + @Test + public void testLinearExpr_booleanTerm() { + final CpModel model = new CpModel(); + assertNotNull(model); + final Literal y = model.newBoolVar("y"); + + final LinearExpr expr = LinearExpr.term(y.not(), 12); + assertNotNull(expr); + + assertThat(expr.numElements()).isEqualTo(1); + assertThat(expr.getVariable(0)).isEqualTo(y); + assertThat(expr.getCoefficient(0)).isEqualTo(-12); + assertThat(expr.getOffset()).isEqualTo(12); + } + + @Test + public void testLinearExpr_affine() { + final CpModel model = new CpModel(); + assertNotNull(model); + final Domain domain = new Domain(0, 10); + final IntVar y = model.newIntVarFromDomain(domain, "y"); + + final LinearExpr expr = LinearExpr.affine(y, 12, 5); + assertNotNull(expr); + + assertThat(expr.numElements()).isEqualTo(1); + assertThat(expr.getVariable(0)).isEqualTo(y); + assertThat(expr.getCoefficient(0)).isEqualTo(12); + assertThat(expr.getOffset()).isEqualTo(5); + } + + @Test + public void testLinearExpr_booleanAffine() { + final CpModel model = new CpModel(); + assertNotNull(model); + final Literal y = model.newBoolVar("y"); + + final LinearExpr expr = LinearExpr.affine(y.not(), 12, 5); + assertNotNull(expr); + + assertThat(expr.numElements()).isEqualTo(1); + assertThat(expr.getVariable(0)).isEqualTo(y); + assertThat(expr.getCoefficient(0)).isEqualTo(-12); + assertThat(expr.getOffset()).isEqualTo(17); + } + + @Test + public void testLinearExpr_sum() { + final CpModel model = new CpModel(); + assertNotNull(model); + final Domain domain = new Domain(0, 10); + final IntVar x = model.newIntVarFromDomain(domain, "x"); + final IntVar y = model.newIntVarFromDomain(domain, "y"); + + final LinearExpr expr = LinearExpr.sum(new IntVar[] {x, y}); + assertNotNull(expr); + + assertThat(expr.numElements()).isEqualTo(2); + assertThat(expr.getVariable(0)).isEqualTo(x); + assertThat(expr.getCoefficient(0)).isEqualTo(1); + assertThat(expr.getVariable(1)).isEqualTo(y); + assertThat(expr.getCoefficient(1)).isEqualTo(1); + assertThat(expr.getOffset()).isEqualTo(0); + } + + @Test + public void testLinearExpr_scalProd() { + final CpModel model = new CpModel(); + assertNotNull(model); + final Domain domain = new Domain(0, 10); + final IntVar x = model.newIntVarFromDomain(domain, "x"); + final IntVar y = model.newIntVarFromDomain(domain, "y"); + + final LinearExpr expr = LinearExpr.scalProd(new IntVar[] {x, y}, new long[] {3, 5}); + assertNotNull(expr); + + assertThat(expr.numElements()).isEqualTo(2); + assertThat(expr.getVariable(0)).isEqualTo(x); + assertThat(expr.getCoefficient(0)).isEqualTo(3); + assertThat(expr.getVariable(1)).isEqualTo(y); + assertThat(expr.getCoefficient(1)).isEqualTo(5); + assertThat(expr.getOffset()).isEqualTo(0); + } + + @Test + public void testLinearExpr_booleanSum() { + final CpModel model = new CpModel(); + assertNotNull(model); + final Literal x = model.newBoolVar("x"); + final Literal y = model.newBoolVar("y"); + + final LinearExpr expr = LinearExpr.booleanSum(new Literal[] {x, y.not()}); + assertNotNull(expr); + + assertThat(expr.numElements()).isEqualTo(2); + assertThat(expr.getVariable(0)).isEqualTo(x); + assertThat(expr.getCoefficient(0)).isEqualTo(1); + assertThat(expr.getVariable(1)).isEqualTo(y); + assertThat(expr.getCoefficient(1)).isEqualTo(-1); + assertThat(expr.getOffset()).isEqualTo(-1); + } + + @Test + public void testLinearExpr_booleanScalProd() { + final CpModel model = new CpModel(); + assertNotNull(model); + final Literal x = model.newBoolVar("x"); + final Literal y = model.newBoolVar("y"); + + final LinearExpr expr = + LinearExpr.booleanScalProd(new Literal[] {x, y.not()}, new long[] {3, 5}); + assertNotNull(expr); + + assertThat(expr.numElements()).isEqualTo(2); + assertThat(expr.getVariable(0)).isEqualTo(x); + assertThat(expr.getCoefficient(0)).isEqualTo(3); + assertThat(expr.getVariable(1)).isEqualTo(y); + assertThat(expr.getCoefficient(1)).isEqualTo(-5); + assertThat(expr.getOffset()).isEqualTo(-5); + } +} diff --git a/examples/tests/LinearSolverTest.java b/examples/tests/LinearSolverTest.java index 57200dc415..a6204fe5bf 100644 --- a/examples/tests/LinearSolverTest.java +++ b/examples/tests/LinearSolverTest.java @@ -10,181 +10,617 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package com.google.ortools; + +package com.google.ortools.linearsolver; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.ortools.Loader; import com.google.ortools.linearsolver.MPConstraint; import com.google.ortools.linearsolver.MPObjective; import com.google.ortools.linearsolver.MPSolver; import com.google.ortools.linearsolver.MPVariable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.logging.Logger; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -public class LinearSolverTest { - static { - System.setProperty( - "java.util.logging.SimpleFormatter.format", "[%1$tF %1$tT] [%4$-7s] %5$s %n"); +/** Test the Linear Solver java interface. */ +public final class LinearSolverTest { + // Numerical tolerance for checking primal, dual, objective values + // and other values. + private static final double NUM_TOLERANCE = 1e-5; + + @BeforeEach + public void setUp() { + Loader.loadNativeLibraries(); } - private static final Logger logger = Logger.getLogger(LinearSolverTest.class.getName()); - - private static void solveAndPrint( - MPSolver solver, MPVariable[] variables, MPConstraint[] constraints) { - logger.info("Number of variables = " + solver.numVariables()); - logger.info("Number of constraints = " + solver.numConstraints()); - - final MPSolver.ResultStatus status = solver.solve(); - // Check that the problem has an optimal solution. - if (status != MPSolver.ResultStatus.OPTIMAL) { - logger.severe("The problem does not have an optimal solution!"); + private void runBasicCtor(MPSolver.OptimizationProblemType solverType) { + if (!MPSolver.supportsProblemType(solverType)) { + return; } - - logger.info("Solution:"); - ArrayList vars = new ArrayList<>(Arrays.asList(variables)); - vars.forEach(var -> logger.info(var.name() + " = " + var.solutionValue())); - logger.info("Optimal objective value = " + solver.objective().value()); - logger.info(""); - logger.info("Advanced usage:"); - logger.info("Problem solved in " + solver.wallTime() + " milliseconds"); - logger.info("Problem solved in " + solver.iterations() + " iterations"); - if (solver.isMip()) - return; - - vars.forEach(var -> logger.info(var.name() + ": reduced cost " + var.reducedCost())); - - final double[] activities = solver.computeConstraintActivities(); - ArrayList cts = new ArrayList<>(Arrays.asList(constraints)); - cts.forEach(ct - -> logger.info(ct.name() + ": dual value = " + ct.dualValue() - + " activity = " + activities[ct.index()])); - } - - @ParameterizedTest - @ValueSource(strings = {"GLOP", "GLPK_LP", "CLP", "GUROBI_LP"}) - private static void testLinearProgramming(String problem_type) { - logger.info("------ Linear programming example with " + problem_type + " ------"); - - MPSolver solver = MPSolver.createSolver(problem_type); - if (solver == null) - return; - - // x and y are continuous non-negative variables. - MPVariable x = solver.makeNumVar(0.0, Double.POSITIVE_INFINITY, "x"); - MPVariable y = solver.makeNumVar(0.0, Double.POSITIVE_INFINITY, "y"); - - // Objectif function: Maximize 3x + 4y). - MPObjective objective = solver.objective(); - objective.setCoefficient(x, 3); - objective.setCoefficient(y, 4); - objective.setMaximization(); - - // x + 2y <= 14. - final MPConstraint c0 = solver.makeConstraint(-Double.POSITIVE_INFINITY, 14.0, "c0"); - c0.setCoefficient(x, 1); - c0.setCoefficient(y, 2); - - // 3x - y >= 0. - final MPConstraint c1 = solver.makeConstraint(0.0, Double.POSITIVE_INFINITY, "c1"); - c1.setCoefficient(x, 3); - c1.setCoefficient(y, -1); - - // x - y <= 2. - final MPConstraint c2 = solver.makeConstraint(-Double.POSITIVE_INFINITY, 2.0, "c2"); - c2.setCoefficient(x, 1); - c2.setCoefficient(y, -1); - - solveAndPrint(solver, new MPVariable[] {x, y}, new MPConstraint[] {c0, c1, c2}); - } - - @ParameterizedTest - @ValueSource(strings = {"GLPK", "CBC", "SCIP", "SAT"}) - private static void testMixedIntegerProgramming(String problem_type) { - logger.info("------ Mixed integer programming example with " + problem_type + " ------"); - - MPSolver solver = MPSolver.createSolver(problem_type); - if (solver == null) - return; - - // x and y are continuous non-negative variables. - MPVariable x = solver.makeIntVar(0.0, Double.POSITIVE_INFINITY, "x"); - MPVariable y = solver.makeIntVar(0.0, Double.POSITIVE_INFINITY, "y"); - - // Objectif function: Maximize x + 10 * y. - MPObjective objective = solver.objective(); - objective.setCoefficient(x, 1); - objective.setCoefficient(y, 10); - objective.setMaximization(); - - // x + 7 * y <= 17.5. - final MPConstraint c0 = solver.makeConstraint(-Double.POSITIVE_INFINITY, 17.5, "c0"); - c0.setCoefficient(x, 1); - c0.setCoefficient(y, 7); - - // x <= 3.5. - final MPConstraint c1 = solver.makeConstraint(-Double.POSITIVE_INFINITY, 3.5, "c1"); - c1.setCoefficient(x, 1); - c1.setCoefficient(y, 0); - - solveAndPrint(solver, new MPVariable[] {x, y}, new MPConstraint[] {c0, c1}); - } - - @ParameterizedTest - @ValueSource(strings = {"SAT", "BOP"}) - private static void testBooleanProgramming(String problem_type) { - logger.info("------ Boolean programming example with " + problem_type + " ------"); - - MPSolver solver = MPSolver.createSolver(problem_type); - if (solver == null) - return; - - // x and y are continuous non-negative variables. - MPVariable x = solver.makeBoolVar("x"); - MPVariable y = solver.makeBoolVar("y"); - - // Objectif function: Maximize 2 * x + y. - MPObjective objective = solver.objective(); - objective.setCoefficient(x, 2); - objective.setCoefficient(y, 1); - objective.setMinimization(); - - // 1 <= x + 2 * y <= 3. - final MPConstraint c0 = solver.makeConstraint(1, 3, "c0"); - c0.setCoefficient(x, 1); - c0.setCoefficient(y, 2); - - solveAndPrint(solver, new MPVariable[] {x, y}, new MPConstraint[] {c0}); + final MPSolver solver = new MPSolver("testBasicCtor", solverType); + assertNotNull(solver); + solver.solve(); } @Test - public void testSameConstraintName() { - Loader.loadNativeLibraries(); - MPSolver solver = MPSolver.createSolver("CBC"); - boolean success = true; + public void testMPSolver_basicCtor() { + runBasicCtor(MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING); + runBasicCtor(MPSolver.OptimizationProblemType.GLPK_LINEAR_PROGRAMMING); + runBasicCtor(MPSolver.OptimizationProblemType.GLPK_MIXED_INTEGER_PROGRAMMING); + runBasicCtor(MPSolver.OptimizationProblemType.CLP_LINEAR_PROGRAMMING); + runBasicCtor(MPSolver.OptimizationProblemType.CBC_MIXED_INTEGER_PROGRAMMING); + } + + @Test + public void testMPSolver_destructor() { + final MPSolver solver = + new MPSolver("testDestructor", MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING); + assertNotNull(solver); + solver.delete(); + } + + private void runLinearSolver(MPSolver.OptimizationProblemType problemType, + boolean integerVariables) { + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("Solver", problemType); + assertNotNull(solver); + + final double infinity = MPSolver.infinity(); + final MPVariable x1 = solver.makeNumVar(0.0, infinity, "x1"); + final MPVariable x2 = solver.makeNumVar(0.0, infinity, "x2"); + final MPVariable x3 = solver.makeNumVar(0.0, infinity, "x3"); + if (integerVariables) { + x1.setInteger(true); + x2.setInteger(true); + x3.setInteger(true); + } + assertEquals(3, solver.numVariables()); + + final MPObjective objective = solver.objective(); + objective.setCoefficient(x1, 10); + objective.setCoefficient(x2, 6); + objective.setCoefficient(x3, 4); + objective.setMaximization(); + assertEquals(6.0, objective.getCoefficient(x2), 1e-6); + assertTrue(objective.maximization()); + assertFalse(objective.minimization()); + + final MPConstraint c0 = solver.makeConstraint(-1000, 100.0); + c0.setCoefficient(x1, 1); + c0.setCoefficient(x2, 1); + c0.setCoefficient(x3, 1); + + final MPConstraint c1 = solver.makeConstraint(-1000, 600.0); + c1.setCoefficient(x1, 10); + c1.setCoefficient(x2, 4); + c1.setCoefficient(x3, 5); + assertEquals(4.0, c1.getCoefficient(x2), 1e-6); + + final MPConstraint c2 = solver.makeConstraint(-1000, 300.0); + c2.setCoefficient(x1, 2); + c2.setCoefficient(x2, 2); + c2.setCoefficient(x3, 6); + + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + if (integerVariables) { + assertEquals(732.0, objective.value(), NUM_TOLERANCE); + assertEquals(33.0, x1.solutionValue(), NUM_TOLERANCE); + assertEquals(67.0, x2.solutionValue(), NUM_TOLERANCE); + assertEquals(0.0, x3.solutionValue(), NUM_TOLERANCE); + } else { + assertEquals(733.333333, objective.value(), NUM_TOLERANCE); + assertEquals(33.333333, x1.solutionValue(), NUM_TOLERANCE); + assertEquals(66.666667, x2.solutionValue(), NUM_TOLERANCE); + assertEquals(0, x3.solutionValue(), NUM_TOLERANCE); + } + } + + @Test + public void testMPSolver_linearSolver() { + runLinearSolver(MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING, false); + runLinearSolver(MPSolver.OptimizationProblemType.CLP_LINEAR_PROGRAMMING, false); + runLinearSolver(MPSolver.OptimizationProblemType.GLPK_LINEAR_PROGRAMMING, false); + + runLinearSolver(MPSolver.OptimizationProblemType.CBC_MIXED_INTEGER_PROGRAMMING, true); + runLinearSolver(MPSolver.OptimizationProblemType.GLPK_MIXED_INTEGER_PROGRAMMING, true); + runLinearSolver(MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING, true); + } + + private void runFirstLinearExample(MPSolver.OptimizationProblemType problemType) { + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("Solver", problemType); + assertNotNull(solver); + + final MPVariable x1 = solver.makeNumVar(0.0, Double.POSITIVE_INFINITY, "x1"); + final MPVariable x2 = solver.makeNumVar(0.0, Double.POSITIVE_INFINITY, "x2"); + final MPVariable x3 = solver.makeNumVar(0.0, Double.POSITIVE_INFINITY, "x3"); + assertEquals(3, solver.numVariables()); + + final double[] obj = {10.0, 6.0, 4.0}; + final MPObjective objective = solver.objective(); + objective.setCoefficient(x1, obj[0]); + objective.setCoefficient(x2, obj[1]); + objective.setCoefficient(x3, obj[2]); + objective.setMaximization(); + + final double rhs0 = 100.0; + final MPConstraint c0 = solver.makeConstraint(-Double.POSITIVE_INFINITY, rhs0, "c0"); + final double[] coef0 = {1.0, 1.0, 1.0}; + c0.setCoefficient(x1, coef0[0]); + c0.setCoefficient(x2, coef0[1]); + c0.setCoefficient(x3, coef0[2]); + final double rhs1 = 600.0; + final MPConstraint c1 = solver.makeConstraint(-Double.POSITIVE_INFINITY, rhs1, "c1"); + final double[] coef1 = {10.0, 4.0, 5.0}; + c1.setCoefficient(x1, coef1[0]); + c1.setCoefficient(x2, coef1[1]); + c1.setCoefficient(x3, coef1[2]); + final double rhs2 = 300.0; + final MPConstraint c2 = solver.makeConstraint(-Double.POSITIVE_INFINITY, rhs2); + final double[] coef2 = {2.0, 2.0, 6.0}; + c2.setCoefficient(x1, coef2[0]); + c2.setCoefficient(x2, coef2[1]); + c2.setCoefficient(x3, coef2[2]); + assertEquals(3, solver.numConstraints()); + assertEquals("c0", c0.name()); + assertEquals("c1", c1.name()); + assertEquals("auto_c_000000002", c2.name()); + + // The problem has an optimal solution.; + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + + assertEquals(733.333333, objective.value(), NUM_TOLERANCE); + assertEquals(33.333333, x1.solutionValue(), NUM_TOLERANCE); + assertEquals(66.666667, x2.solutionValue(), NUM_TOLERANCE); + assertEquals(0, x3.solutionValue(), NUM_TOLERANCE); + + // c0 and c1 are binding; + final double[] activities = solver.computeConstraintActivities(); + assertEquals(3, activities.length); + assertEquals(3.333333, c0.dualValue(), NUM_TOLERANCE); + assertEquals(0.666667, c1.dualValue(), NUM_TOLERANCE); + assertEquals(rhs0, activities[c0.index()], NUM_TOLERANCE); + assertEquals(rhs1, activities[c1.index()], NUM_TOLERANCE); + assertEquals(MPSolver.BasisStatus.AT_UPPER_BOUND, c0.basisStatus()); + assertEquals(MPSolver.BasisStatus.AT_UPPER_BOUND, c1.basisStatus()); + // c2 is not binding; + assertEquals(0.0, c2.dualValue(), NUM_TOLERANCE); + assertEquals(200.0, activities[c2.index()], NUM_TOLERANCE); + assertEquals(MPSolver.BasisStatus.BASIC, c2.basisStatus()); + // The optimum of the dual problem is equal to the optimum of the; + // primal problem.; + final double dualObjectiveValue = c0.dualValue() * rhs0 + c1.dualValue() * rhs1; + assertEquals(objective.value(), dualObjectiveValue, NUM_TOLERANCE); + + // x1 and x2 are basic; + assertEquals(0.0, x1.reducedCost(), NUM_TOLERANCE); + assertEquals(0.0, x2.reducedCost(), NUM_TOLERANCE); + assertEquals(MPSolver.BasisStatus.BASIC, x1.basisStatus()); + assertEquals(MPSolver.BasisStatus.BASIC, x2.basisStatus()); + // x3 is non-basic; + final double x3ExpectedReducedCost = + (obj[2] - coef0[2] * c0.dualValue() - coef1[2] * c1.dualValue()); + assertEquals(x3ExpectedReducedCost, x3.reducedCost(), NUM_TOLERANCE); + assertEquals(MPSolver.BasisStatus.AT_LOWER_BOUND, x3.basisStatus()); + + if (solver.problemType() == MPSolver.OptimizationProblemType.GLPK_LINEAR_PROGRAMMING) { + assertEquals(56.333333, solver.computeExactConditionNumber(), NUM_TOLERANCE); + } + } + + @Test + public void testMPSolver_firstLinearExample() { + runFirstLinearExample(MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING); + runFirstLinearExample(MPSolver.OptimizationProblemType.CLP_LINEAR_PROGRAMMING); + runFirstLinearExample(MPSolver.OptimizationProblemType.GLPK_LINEAR_PROGRAMMING); + runFirstLinearExample(MPSolver.OptimizationProblemType.GUROBI_LINEAR_PROGRAMMING); + } + + private void runFirstMIPExample(MPSolver.OptimizationProblemType problemType) { + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("Solver", problemType); + assertNotNull(solver); + + // Integer variables shouldn't have infinite bounds, nor really large bounds: + // it can make your solver behave erratically. If you have integer variables + // with a truly large dynamic range you should consider making it non-integer. + final double upperBound = 1000; + final MPVariable x1 = solver.makeIntVar(0.0, upperBound, "x1"); + final MPVariable x2 = solver.makeIntVar(0.0, upperBound, "x2"); + + solver.objective().setCoefficient(x1, 1); + solver.objective().setCoefficient(x2, 2); + + final MPConstraint ct = solver.makeConstraint(17, Double.POSITIVE_INFINITY); + ct.setCoefficient(x1, 3); + ct.setCoefficient(x2, 2); + + // Check the solution. + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + final double optObjValue = 6.0; + assertEquals(optObjValue, solver.objective().value(), 1e-6); + assertEquals(optObjValue, solver.objective().bestBound(), 1e-6); + final double optRowActivity = 18.0; + assertEquals(optRowActivity, solver.computeConstraintActivities()[ct.index()], NUM_TOLERANCE); + // BOP does not support nodes(). + if (solver.problemType() != MPSolver.OptimizationProblemType.BOP_INTEGER_PROGRAMMING) { + assertThat(solver.nodes()).isAtLeast(0); + } + } + + @Test + public void testMPSolver_firstMIPExample() { + runFirstMIPExample(MPSolver.OptimizationProblemType.BOP_INTEGER_PROGRAMMING); + runFirstMIPExample(MPSolver.OptimizationProblemType.CBC_MIXED_INTEGER_PROGRAMMING); + runFirstMIPExample(MPSolver.OptimizationProblemType.GLPK_MIXED_INTEGER_PROGRAMMING); + runFirstMIPExample(MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING); + runFirstMIPExample(MPSolver.OptimizationProblemType.SAT_INTEGER_PROGRAMMING); + runFirstMIPExample(MPSolver.OptimizationProblemType.GUROBI_MIXED_INTEGER_PROGRAMMING); + } + + private void runSuccessiveObjectives(MPSolver.OptimizationProblemType problemType) { + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("Solver", problemType); + assertNotNull(solver); + + final MPVariable x1 = solver.makeNumVar(0, 10, "var1"); + final MPVariable x2 = solver.makeNumVar(0, 10, "var2"); + final MPConstraint ct = solver.makeConstraint(0, 10); + ct.setCoefficient(x1, 1); + ct.setCoefficient(x2, 2); + + final MPObjective objective = solver.objective(); + objective.setCoefficient(x1, 1); + objective.setCoefficient(x2, 0); + objective.setOptimizationDirection(true); + + // Check the solution. + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + assertEquals(10.0, x1.solutionValue(), NUM_TOLERANCE); + assertEquals(0.0, x2.solutionValue(), NUM_TOLERANCE); + + objective.setCoefficient(x1, 0); + objective.setCoefficient(x2, 1); + objective.setOptimizationDirection(true); + + // Check the solution + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + assertEquals(0.0, x1.solutionValue(), NUM_TOLERANCE); + assertEquals(5.0, x2.solutionValue(), NUM_TOLERANCE); + + objective.setCoefficient(x1, -1); + objective.setCoefficient(x2, 0); + objective.setOptimizationDirection(false); + + // Check the solution. + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + assertEquals(10.0, x1.solutionValue(), NUM_TOLERANCE); + assertEquals(0.0, x2.solutionValue(), NUM_TOLERANCE); + } + + @Test + public void testMPSolver_successiveObjectives() { + runSuccessiveObjectives(MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING); + runSuccessiveObjectives(MPSolver.OptimizationProblemType.CLP_LINEAR_PROGRAMMING); + runSuccessiveObjectives(MPSolver.OptimizationProblemType.GLPK_LINEAR_PROGRAMMING); + runSuccessiveObjectives(MPSolver.OptimizationProblemType.GUROBI_LINEAR_PROGRAMMING); + + runSuccessiveObjectives(MPSolver.OptimizationProblemType.CBC_MIXED_INTEGER_PROGRAMMING); + runSuccessiveObjectives(MPSolver.OptimizationProblemType.GLPK_MIXED_INTEGER_PROGRAMMING); + runSuccessiveObjectives(MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING); + runSuccessiveObjectives(MPSolver.OptimizationProblemType.SAT_INTEGER_PROGRAMMING); + runSuccessiveObjectives(MPSolver.OptimizationProblemType.GUROBI_MIXED_INTEGER_PROGRAMMING); + } + + private void runObjectiveOffset(MPSolver.OptimizationProblemType problemType) { + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("Solver", problemType); + assertNotNull(solver); + + final MPVariable x1 = solver.makeNumVar(1.0, 10.0, "x1"); + final MPVariable x2 = solver.makeNumVar(1.0, 10.0, "x2"); + + final MPConstraint ct = solver.makeConstraint(0, 4.0); + ct.setCoefficient(x1, 1); + ct.setCoefficient(x2, 2); + + final double objectiveOffset = 10.0; + // Simple minimization. + final MPObjective objective = solver.objective(); + objective.setCoefficient(x1, 1.0); + objective.setCoefficient(x2, 1.0); + objective.setOffset(objectiveOffset); + objective.setOptimizationDirection(false); + + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + assertEquals(2.0 + objectiveOffset, objective.value(), 1e-6); + + // Offset is provided in several separate constants. + objective.setCoefficient(x1, 1.0); + objective.setCoefficient(x2, 1.0); + objective.setOffset(-1.0); + objective.setOffset(objectiveOffset + objective.offset()); + objective.setOffset(1.0 + objective.offset()); + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + assertEquals(2.0 + objectiveOffset, objective.value(), 1e-6); + + // Simple maximization. + objective.setCoefficient(x1, 1.0); + objective.setCoefficient(x2, 1.0); + objective.setOffset(objectiveOffset); + objective.setOptimizationDirection(true); + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + assertEquals(3.0 + objectiveOffset, objective.value(), 1e-6); + } + + @Test + public void testMPSolver_objectiveOffset() { + runObjectiveOffset(MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING); + runObjectiveOffset(MPSolver.OptimizationProblemType.CLP_LINEAR_PROGRAMMING); + runObjectiveOffset(MPSolver.OptimizationProblemType.GLPK_LINEAR_PROGRAMMING); + runObjectiveOffset(MPSolver.OptimizationProblemType.GUROBI_LINEAR_PROGRAMMING); + + runObjectiveOffset(MPSolver.OptimizationProblemType.CBC_MIXED_INTEGER_PROGRAMMING); + runObjectiveOffset(MPSolver.OptimizationProblemType.GLPK_MIXED_INTEGER_PROGRAMMING); + runObjectiveOffset(MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING); + runObjectiveOffset(MPSolver.OptimizationProblemType.SAT_INTEGER_PROGRAMMING); + runObjectiveOffset(MPSolver.OptimizationProblemType.GUROBI_MIXED_INTEGER_PROGRAMMING); + } + + @Test + public void testMPSolver_lazyConstraints() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("testLazyConstraints", problemType); + assertNotNull(solver); + + final double infinity = MPSolver.infinity(); + final MPVariable x = solver.makeIntVar(0, infinity, "x"); + final MPVariable y = solver.makeIntVar(0, infinity, "y"); + final MPConstraint ct1 = solver.makeConstraint(0, 10.0); + ct1.setCoefficient(x, 2.0); + ct1.setCoefficient(y, 1.0); + final MPConstraint ct2 = solver.makeConstraint(0, 10.0); + ct2.setCoefficient(x, 1.0); + ct2.setCoefficient(y, 2.0); + ct2.setIsLazy(true); + assertFalse(ct1.isLazy()); + assertTrue(ct2.isLazy()); + final MPObjective objective = solver.objective(); + objective.setCoefficient(x, 1.0); + objective.setCoefficient(y, 1.0); + objective.setOptimizationDirection(true); + assertEquals(MPSolver.ResultStatus.OPTIMAL, solver.solve()); + assertEquals(solver.objective().value(), 6.0, NUM_TOLERANCE); + } + + @Test + public void testMPSolver_sameConstraintName() { + MPSolver solver = MPSolver.createSolver("GLOP"); + assertNotNull(solver); + boolean success = false; solver.makeConstraint("my_const_name"); try { solver.makeConstraint("my_const_name"); } catch (Throwable e) { System.out.println(e); - success = false; + success = true; } - logger.info("Success = " + success); + assertTrue(success); } @Test - public void testSetHintAndSolverGetters() { - Loader.loadNativeLibraries(); - MPSolver solver = MPSolver.createSolver("GLOP"); + public void testMPSolver_exportModelToProto() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("testExportModelToProto", problemType); + assertNotNull(solver); + solver.makeNumVar(0.0, 10.0, "x1"); + solver.makeConstraint(0.0, 0.0); + solver.objective().setOptimizationDirection(true); + final MPModelProto model = solver.exportModelToProto(); + assertEquals(1, model.getVariableCount()); + assertEquals(1, model.getConstraintCount()); + assertTrue(model.getMaximize()); + } + + @Test + public void testMPsolver_createSolutionResponseProto() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("testCreateSolutionResponseProto", problemType); + assertNotNull(solver); + final MPVariable x1 = solver.makeNumVar(0.0, 10.0, "x1"); + solver.objective().setCoefficient(x1, 1.0); + solver.objective().setOptimizationDirection(true); + solver.solve(); + final MPSolutionResponse response = solver.createSolutionResponseProto(); + assertEquals(MPSolverResponseStatus.MPSOLVER_OPTIMAL, response.getStatus()); + assertEquals(10.0, response.getObjectiveValue(), 1e-6); + } + + @Test + public void testMPSolver_solveWithProto() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPModelProto.Builder modelBuilder = MPModelProto.newBuilder().setMaximize(true); + final MPVariableProto variable = MPVariableProto.newBuilder() + .setLowerBound(0.0) + .setUpperBound(10.0) + .setName("x1") + .setIsInteger(false) + .setObjectiveCoefficient(1.0) + .build(); + modelBuilder.addVariable(variable); + final MPModelRequest request = MPModelRequest.newBuilder() + .setModel(modelBuilder.build()) + .setSolverType(MPModelRequest.SolverType.GLOP_LINEAR_PROGRAMMING) + .build(); + final MPSolutionResponse response = MPSolver.solveWithProto(request); + assertEquals(MPSolverResponseStatus.MPSOLVER_OPTIMAL, response.getStatus()); + assertEquals(10.0, response.getObjectiveValue(), 1e-6); + } + + @Test + public void testModelExport() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("tesModelExport", problemType); + assertNotNull(solver); + final double infinity = MPSolver.infinity(); + // x1, x2 and x3 are continuous non-negative variables. + final MPVariable x1 = solver.makeNumVar(0.0, infinity, "x1"); + + // Maximize 10 * x1. + solver.objective().setCoefficient(x1, 10); + solver.objective().setMinimization(); + + // 5 * x1 <= 30. + final MPConstraint c0 = solver.makeConstraint(-infinity, 100.0); + c0.setCoefficient(x1, 5); + + final MPModelExportOptions obfuscate = new MPModelExportOptions(); + obfuscate.setObfuscate(true); + String out = solver.exportModelAsLpFormat(); + assertThat(out).isNotEmpty(); + out = solver.exportModelAsLpFormat(obfuscate); + assertThat(out).isNotEmpty(); + out = solver.exportModelAsMpsFormat(); + assertThat(out).isNotEmpty(); + out = solver.exportModelAsMpsFormat(obfuscate); + assertThat(out).isNotEmpty(); + } + + @Test + public void testMPSolver_wrongModelExport() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("testWrongModelExport", problemType); + assertNotNull(solver); + // Test that forbidden names are renamed. + solver.makeBoolVar("<-%$#!&~-+ ⌂"); // Some illegal name. + String out = solver.exportModelAsLpFormat(); + assertThat(out).isNotEmpty(); + out = solver.exportModelAsMpsFormat(); + assertThat(out).isNotEmpty(); + } + + @Test + public void testMPSolver_setHint() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("testSetHint", problemType); + assertNotNull(solver); + final MPVariable[] variables = { + solver.makeNumVar(0.0, 10.0, "x1"), solver.makeNumVar(0.0, 10.0, "x2") + }; + final double[] values = {5.0, 6.0}; + solver.setHint(variables, values); + + final MPModelProto model = solver.exportModelToProto(); + final PartialVariableAssignment hint = model.getSolutionHint(); + assertEquals(2, hint.getVarIndexCount()); + assertEquals(2, hint.getVarValueCount()); + assertEquals(0, hint.getVarIndex(0)); + assertEquals(5.0, hint.getVarValue(0), 1e-6); + assertEquals(1, hint.getVarIndex(1)); + assertEquals(6.0, hint.getVarValue(1), 1e-6); + } + + @Test + public void testMPSolver_issue132() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.CLP_LINEAR_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("CoinError", problemType); + assertNotNull(solver); + final double infinity = MPSolver.infinity(); + final MPVariable x0 = solver.makeNumVar(0.0, 1.0, "x0"); + final MPVariable x1 = solver.makeNumVar(0.0, 0.3, "x1"); + final MPVariable x2 = solver.makeNumVar(0.0, 0.3, "x2"); + final MPVariable x3 = solver.makeNumVar(-infinity, infinity, "x3"); + + final MPObjective obj = solver.objective(); + obj.setCoefficient(x1, 2.655523); + obj.setCoefficient(x2, -2.70917); + obj.setCoefficient(x3, 1); + obj.setMaximization(); + + final MPConstraint c0 = solver.makeConstraint(-infinity, 0.302499); + c0.setCoefficient(x3, 1); + c0.setCoefficient(x0, -3.484345); + + final MPConstraint c1 = solver.makeConstraint(-infinity, 0.507194); + c1.setCoefficient(x3, 1); + c1.setCoefficient(x0, -3.074807); + + final MPConstraint c2 = solver.makeConstraint(0.594, 0.594); + c2.setCoefficient(x0, 1); + c2.setCoefficient(x1, 1.01); + c2.setCoefficient(x2, -0.99); + + System.out.println("Number of variables = " + solver.numVariables()); + System.out.println("Number of constraints = " + solver.numConstraints()); + + solver.enableOutput(); + System.out.println(solver.exportModelAsLpFormat()); + System.out.println(solver.solve()); + } + + @Test + public void testMPSolver_setHintAndSolverGetters() { + final MPSolver.OptimizationProblemType problemType = + MPSolver.OptimizationProblemType.GLOP_LINEAR_PROGRAMMING; + if (!MPSolver.supportsProblemType(problemType)) { + return; + } + final MPSolver solver = new MPSolver("glop", problemType); + assertNotNull(solver); // x and y are continuous non-negative variables. - MPVariable x = solver.makeIntVar(0.0, Double.POSITIVE_INFINITY, "x"); - MPVariable y = solver.makeIntVar(0.0, Double.POSITIVE_INFINITY, "y"); + final MPVariable x = solver.makeIntVar(0.0, Double.POSITIVE_INFINITY, "x"); + final MPVariable y = solver.makeIntVar(0.0, Double.POSITIVE_INFINITY, "y"); // Objectif function: Maximize x + 10 * y. - MPObjective objective = solver.objective(); + final MPObjective objective = solver.objective(); objective.setCoefficient(x, 1); objective.setCoefficient(y, 10); objective.setMaximization(); @@ -199,13 +635,18 @@ public class LinearSolverTest { c1.setCoefficient(x, 1); c1.setCoefficient(y, 0); - if (solver.constraints().length != 2) { - throw new RuntimeException("WrongConstraintLength"); - } - if (solver.variables().length != 2) { - throw new RuntimeException("WrongConstraintLength"); - } + // Test solver getters. + final MPVariable[] variables = solver.variables(); + assertThat(variables).hasLength(2); + final MPConstraint[] constraints = solver.constraints(); + assertThat(constraints).hasLength(2); - solver.setHint(new MPVariable[] {x, y}, new double[] {2.0, 3.0}); + // Test API compiles. + solver.setHint(variables, new double[] {2.0, 3.0}); + assertEquals("y", variables[1].name()); + assertEquals("c0", constraints[0].name()); + // TODO(user): Add API to query the hint. + + assertFalse(solver.setNumThreads(4)); } } diff --git a/examples/tests/RoutingSolverTest.java b/examples/tests/RoutingSolverTest.java index ac1f18007a..1ac795926d 100644 --- a/examples/tests/RoutingSolverTest.java +++ b/examples/tests/RoutingSolverTest.java @@ -10,9 +10,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package com.google.ortools; -import static java.lang.Math.abs; +package com.google.ortools.constraintsolver; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.ortools.Loader; import com.google.ortools.constraintsolver.Assignment; @@ -22,225 +26,615 @@ import com.google.ortools.constraintsolver.RoutingModel; import com.google.ortools.constraintsolver.RoutingSearchParameters; import com.google.ortools.constraintsolver.main; import com.google.ortools.constraintsolver.IntBoolPair; -import java.util.logging.Logger; +import com.google.protobuf.Duration; +import java.util.ArrayList; +import java.util.function.LongBinaryOperator; +import java.util.function.LongUnaryOperator; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; /** Tests the Routing java interface. */ -public class RoutingSolverTest { - private static final Logger logger = Logger.getLogger(RoutingSolverTest.class.getName()); +public final class RoutingSolverTest { + private ArrayList coordinates; - @Test - public void testRoutingTransitMatrix() { + @BeforeEach + public void setUp() { Loader.loadNativeLibraries(); - logger.info("testRoutingTransitMatrix..."); - // Create Routing Index Manager - RoutingIndexManager manager = - new RoutingIndexManager(5 /*location*/, 1 /*vehicle*/, 0 /*depot*/); - // Create Routing Model. - RoutingModel routing = new RoutingModel(manager); - // Define cost of each arc. - int transitCallbackIndex; - long[][] matrix = { - {1, 1, 1, 1, 1}, - {1, 1, 1, 1, 1}, - {1, 1, 1, 1, 1}, - {1, 1, 1, 1, 1}, - {1, 1, 1, 1, 1}, - }; - transitCallbackIndex = routing.registerTransitMatrix(matrix); - routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex); - // Setting first solution heuristic. - RoutingSearchParameters searchParameters = - main.defaultRoutingSearchParameters() - .toBuilder() - .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) - .build(); - // Solve the problem. - Assignment solution = routing.solveWithParameters(searchParameters); - if (null == solution) - throw new AssertionError("null == solution"); - if (5 != solution.objectiveValue()) - throw new AssertionError("5 != objective"); - logger.info("testRoutingTransitMatrix...DONE"); + coordinates = new ArrayList<>(); + coordinates.add(new Integer[] {0, 0}); + coordinates.add(new Integer[] {-1, 0}); + coordinates.add(new Integer[] {-1, 2}); + coordinates.add(new Integer[] {2, 1}); + coordinates.add(new Integer[] {1, 0}); } - @ParameterizedTest - @ValueSource(booleans = {false, true}) - public void testRoutingTransitCallback(boolean enableGC) { - Loader.loadNativeLibraries(); - logger.info("testRoutingTransitCallback (enable gc:" + enableGC + ")..."); - // Create Routing Index Manager - RoutingIndexManager manager = - new RoutingIndexManager(5 /*location*/, 1 /*vehicle*/, 0 /*depot*/); - // Create Routing Model. - RoutingModel routing = new RoutingModel(manager); - // Define cost of each arc. - int transitCallbackIndex; - if (true) { - transitCallbackIndex = routing.registerTransitCallback((long fromIndex, long toIndex) -> { - // Convert from routing variable Index to user NodeIndex. - int fromNode = manager.indexToNode(fromIndex); - int toNode = manager.indexToNode(toIndex); - return abs(toNode - fromNode); - }); - } - if (enableGC) { - System.gc(); - } - routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex); - // Setting first solution heuristic. - RoutingSearchParameters searchParameters = - main.defaultRoutingSearchParameters() - .toBuilder() - .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) - .build(); - // Solve the problem. - Assignment solution = routing.solveWithParameters(searchParameters); - if (null == solution) - throw new AssertionError("null == solution"); - if (8 != solution.objectiveValue()) - throw new AssertionError("8 != objective"); - logger.info("testRoutingTransitCallback (enable gc:" + enableGC + ")...DONE"); + public LongBinaryOperator createManhattanCostCallback(RoutingIndexManager manager) { + return (long i, long j) -> { + final int firstIndex = manager.indexToNode(i); + final int secondIndex = manager.indexToNode(j); + final Integer[] firstCoordinate = coordinates.get(firstIndex); + final Integer[] secondCoordinate = coordinates.get(secondIndex); + return (long) Math.abs(firstCoordinate[0] - secondCoordinate[0]) + + Math.abs(firstCoordinate[1] - secondCoordinate[1]); + }; + } + + public LongUnaryOperator createUnaryCostCallback(RoutingIndexManager manager) { + return (long fromIndex) -> { + final int fromNode = manager.indexToNode(fromIndex); + final Integer[] firstCoordinate = coordinates.get(fromNode); + return (long) Math.abs(firstCoordinate[0]) + Math.abs(firstCoordinate[1]); + }; + } + + public LongBinaryOperator createReturnOneCallback() { + return (long i, long j) -> 1; } @Test - public void testRoutingMatrixDimension() { - Loader.loadNativeLibraries(); - logger.info("testRoutingMatrixDimension..."); - // Create Routing Index Manager - RoutingIndexManager manager = - new RoutingIndexManager(5 /*location*/, 1 /*vehicle*/, 0 /*depot*/); - // Create Routing Model. - RoutingModel routing = new RoutingModel(manager); - // Define cost of each arc. - int transitCallbackIndex; - long[][] matrix = { - {1, 1, 1, 1, 1}, - {1, 1, 1, 1, 1}, - {1, 1, 1, 1, 1}, - {1, 1, 1, 1, 1}, - {1, 1, 1, 1, 1}, + public void testRoutingIndexManager() { + final RoutingIndexManager manager = new RoutingIndexManager(42, 3, 7); + assertNotNull(manager); + assertEquals(42, manager.getNumberOfNodes()); + assertEquals(3, manager.getNumberOfVehicles()); + assertEquals(42 + 3 * 2 - 1, manager.getNumberOfIndices()); + for (int i = 0; i < manager.getNumberOfVehicles(); ++i) { + assertEquals(7, manager.indexToNode(manager.getStartIndex(i))); + assertEquals(7, manager.indexToNode(manager.getEndIndex(i))); + } + } + + @Test + public void testRoutingIndexManager_multiDepotSame() { + final RoutingIndexManager manager = + new RoutingIndexManager(42, 3, new int[] {7, 7, 7}, new int[] {7, 7, 7}); + assertNotNull(manager); + assertEquals(42, manager.getNumberOfNodes()); + assertEquals(3, manager.getNumberOfVehicles()); + assertEquals(42 + 3 * 2 - 1, manager.getNumberOfIndices()); + for (int i = 0; i < manager.getNumberOfVehicles(); ++i) { + assertEquals(7, manager.indexToNode(manager.getStartIndex(i))); + assertEquals(7, manager.indexToNode(manager.getEndIndex(i))); + } + } + + @Test + public void testRoutingIndexManager_multiDepotAllDifferent() { + final RoutingIndexManager manager = + new RoutingIndexManager(42, 3, new int[] {1, 2, 3}, new int[] {4, 5, 6}); + assertNotNull(manager); + assertEquals(42, manager.getNumberOfNodes()); + assertEquals(3, manager.getNumberOfVehicles()); + assertEquals(42, manager.getNumberOfIndices()); + for (int i = 0; i < manager.getNumberOfVehicles(); ++i) { + assertEquals(i + 1, manager.indexToNode(manager.getStartIndex(i))); + assertEquals(i + 4, manager.indexToNode(manager.getEndIndex(i))); + } + } + + @Test + public void testRoutingModel() { + final RoutingIndexManager manager = + new RoutingIndexManager(42, 3, new int[] {1, 2, 3}, new int[] {4, 5, 6}); + assertNotNull(manager); + final RoutingModel model = new RoutingModel(manager); + assertNotNull(model); + for (int i = 0; i < manager.getNumberOfVehicles(); ++i) { + assertEquals(i + 1, manager.indexToNode(model.start(i))); + assertEquals(i + 4, manager.indexToNode(model.end(i))); + } + } + + @Test + public void testRoutingModelParameters() { + final RoutingModelParameters parameters = main.defaultRoutingModelParameters(); + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager, parameters); + assertEquals(1, model.vehicles()); + } + + @Test + public void testRoutingModel_solveWithParameters() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + final RoutingSearchParameters parameters = main.defaultRoutingSearchParameters(); + final LongBinaryOperator callback = createManhattanCostCallback(manager); + final int cost = model.registerTransitCallback(callback); + System.gc(); + model.setArcCostEvaluatorOfAllVehicles(cost); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + Assignment solution = model.solveWithParameters(parameters); + assertEquals(10, solution.objectiveValue()); + solution = model.solveFromAssignmentWithParameters(solution, parameters); + assertEquals(10, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_costsAndSolve() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(5, model.nodes()); + final LongBinaryOperator callback = createManhattanCostCallback(manager); + final int cost = model.registerTransitCallback(callback); + System.gc(); + model.setArcCostEvaluatorOfAllVehicles(cost); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(10, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_matrixTransitOwnership() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(5, model.nodes()); + final long[][] matrix = { + {0, 1, 3, 3, 1}, + {1, 0, 2, 4, 2}, + {3, 2, 0, 4, 4}, + {3, 4, 4, 0, 2}, + {1, 2, 4, 2, 0}, }; - IntBoolPair result = routing.addMatrixDimension( - matrix, - /*capacity=*/10, + final int cost = model.registerTransitMatrix(matrix); + System.gc(); // model should keep alive the callback + model.setArcCostEvaluatorOfAllVehicles(cost); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(10, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_transitCallbackOwnership() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(5, model.nodes()); + final int cost = model.registerTransitCallback(createManhattanCostCallback(manager)); + System.gc(); // model should keep alive the callback + model.setArcCostEvaluatorOfAllVehicles(cost); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(10, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_lambdaTransitCallbackOwnership() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(5, model.nodes()); + final int cost = model.registerTransitCallback( + (long fromIndex, long toIndex) -> { + final int fromNode = manager.indexToNode(fromIndex); + final int toNode = manager.indexToNode(toIndex); + return (long) Math.abs(toNode - fromNode); + }); + System.gc(); // model should keep alive the callback + model.setArcCostEvaluatorOfAllVehicles(cost); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(8, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_unaryTransitVectorOwnership() { + final RoutingIndexManager manager = new RoutingIndexManager(10, 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(10, model.nodes()); + final long[] vector = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + final int cost = model.registerUnaryTransitVector(vector); + System.gc(); // model should keep alive the callback + model.setArcCostEvaluatorOfAllVehicles(cost); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(45, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_unaryTransitCallbackOwnership() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(5, model.nodes()); + final int cost = model.registerUnaryTransitCallback(createUnaryCostCallback(manager)); + System.gc(); // model should keep alive the callback + model.setArcCostEvaluatorOfAllVehicles(cost); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(8, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_lambdaUnaryTransitCallbackOwnership() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(5, model.nodes()); + final int cost = model.registerUnaryTransitCallback( + (long fromIndex) -> { + final int fromNode = manager.indexToNode(fromIndex); + return (long) Math.abs(fromNode); + }); + System.gc(); // model should keep alive the callback + model.setArcCostEvaluatorOfAllVehicles(cost); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(10, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_routesToAssignment() { + final int vehicles = coordinates.size() - 1; + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), vehicles, 0); + final RoutingModel model = new RoutingModel(manager); + model.closeModel(); + long[][] routes = new long[vehicles][]; + for (int i = 0; i < vehicles; ++i) { + // Each route has a single node + routes[i] = new long[1]; + routes[i][0] = manager.nodeToIndex(i + 1); + } + Assignment assignment = new Assignment(model.solver()); + model.routesToAssignment(routes, false, true, assignment); + for (int i = 0; i < vehicles; ++i) { + assertEquals(assignment.value(model.nextVar(model.start(i))), i + 1); + assertEquals(assignment.value(model.nextVar(i + 1)), model.end(i)); + } + } + + @Test + public void testRoutingModel_addDisjunction() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator callback = createManhattanCostCallback(manager); + final int cost = model.registerTransitCallback(callback); + model.setArcCostEvaluatorOfAllVehicles(cost); + + int[] a = new int[2]; + a[0] = 2; + a[1] = 3; + int[] b = new int[1]; + b[0] = 1; + int[] c = new int[1]; + c[0] = 4; + model.addDisjunction(manager.nodesToIndices(a)); + model.addDisjunction(manager.nodesToIndices(b)); + model.addDisjunction(manager.nodesToIndices(c)); + + Assignment solution = model.solve(null); + assertEquals(8, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_addConstantDimension() { + final RoutingIndexManager manager = new RoutingIndexManager(10, 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(10, model.nodes()); + final IntBoolPair pair = model.addConstantDimension( + 1, + /*capacity=*/100, /*fix_start_cumul_to_zero=*/true, "Dimension"); - routing.setArcCostEvaluatorOfAllVehicles(result.getFirst()); - // Setting first solution heuristic. + assertEquals(1, pair.getFirst()); + assertTrue(pair.getSecond()); + RoutingDimension dimension = model.getMutableDimension("Dimension"); + dimension.setSpanCostCoefficientForAllVehicles(2); + RoutingSearchParameters searchParameters = main.defaultRoutingSearchParameters() .toBuilder() .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) + .setTimeLimit(Duration.newBuilder().setSeconds(10)) .build(); - // Solve the problem. - Assignment solution = routing.solveWithParameters(searchParameters); - if (null == solution) - throw new AssertionError("null == solution"); - if (5 != solution.objectiveValue()) - throw new AssertionError("5 != objective"); - logger.info("testRoutingMatrixDimension...DONE"); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solveWithParameters(searchParameters); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(20, solution.objectiveValue()); } @Test - public void testRoutingUnaryTransitVector() { - Loader.loadNativeLibraries(); - logger.info("testRoutingUnaryTransitVector..."); - // Create Routing Index Manager - RoutingIndexManager manager = - new RoutingIndexManager(5 /*location*/, 1 /*vehicle*/, 0 /*depot*/); - // Create Routing Model. - RoutingModel routing = new RoutingModel(manager); - // Define cost of each arc. - int transitCallbackIndex; - long[] vector = {1, 1, 1, 1, 1}; - transitCallbackIndex = routing.registerUnaryTransitVector(vector); - routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex); - // Setting first solution heuristic. - RoutingSearchParameters searchParameters = - main.defaultRoutingSearchParameters() - .toBuilder() - .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) - .build(); - // Solve the problem. - Assignment solution = routing.solveWithParameters(searchParameters); - if (null == solution) - throw new AssertionError("null == solution"); - if (5 != solution.objectiveValue()) - throw new AssertionError("5 != objective"); - logger.info("testRoutingUnaryTransitVector...DONE"); - } - - @ParameterizedTest - @ValueSource(booleans = {false, true}) - public void testRoutingUnaryTransitCallback(boolean enableGC) { - Loader.loadNativeLibraries(); - logger.info("testRoutingUnaryTransitCallback (enable gc:" + enableGC + ")..."); - // Create Routing Index Manager - RoutingIndexManager manager = - new RoutingIndexManager(5 /*location*/, 1 /*vehicle*/, 0 /*depot*/); - // Create Routing Model. - RoutingModel routing = new RoutingModel(manager); - // Define cost of each arc. - int transitCallbackIndex; - if (true) { - transitCallbackIndex = routing.registerUnaryTransitCallback((long fromIndex) -> { - // Convert from routing variable Index to user NodeIndex. - int fromNode = manager.indexToNode(fromIndex); - return abs(fromNode); - }); - } - if (enableGC) { - System.gc(); - } - routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex); - // Setting first solution heuristic. - RoutingSearchParameters searchParameters = - main.defaultRoutingSearchParameters() - .toBuilder() - .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) - .build(); - // Solve the problem. - Assignment solution = routing.solveWithParameters(searchParameters); - if (null == solution) - throw new AssertionError("null == solution"); - if (10 != solution.objectiveValue()) - throw new AssertionError("10 != objective"); - logger.info("testRoutingUnaryTransitCallback (enable gc:" + enableGC + ")...DONE"); - } - - @Test - public void testRoutingVectorDimension() { - Loader.loadNativeLibraries(); - logger.info("testRoutingVectorDimension..."); - // Create Routing Index Manager - RoutingIndexManager manager = - new RoutingIndexManager(5 /*location*/, 1 /*vehicle*/, 0 /*depot*/); - // Create Routing Model. - RoutingModel routing = new RoutingModel(manager); - // Define cost of each arc. - int transitCallbackIndex; - long[] vector = {1, 1, 1, 1, 1}; - IntBoolPair result = routing.addVectorDimension( + public void testRoutingModel_addVectorDimension() { + final RoutingIndexManager manager = new RoutingIndexManager(10, 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(10, model.nodes()); + final long[] vector = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + final IntBoolPair pair = model.addVectorDimension( vector, - /*capacity=*/10, + /*capacity=*/100, /*fix_start_cumul_to_zero=*/true, "Dimension"); - routing.setArcCostEvaluatorOfAllVehicles(result.getFirst()); - // Setting first solution heuristic. + assertEquals(1, pair.getFirst()); + assertTrue(pair.getSecond()); + System.gc(); // model should keep alive the callback + model.setArcCostEvaluatorOfAllVehicles(pair.getFirst()); + RoutingSearchParameters searchParameters = main.defaultRoutingSearchParameters() .toBuilder() .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) + .setTimeLimit(Duration.newBuilder().setSeconds(10)) .build(); - // Solve the problem. - Assignment solution = routing.solveWithParameters(searchParameters); - if (null == solution) - throw new AssertionError("null == solution"); - if (5 != solution.objectiveValue()) - throw new AssertionError("5 != objective"); - logger.info("testRoutingMatrixDimension...DONE"); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solveWithParameters(searchParameters); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(45, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_addMatrixDimension() { + final RoutingIndexManager manager = new RoutingIndexManager(5, 1, 0); + final RoutingModel model = new RoutingModel(manager); + assertEquals(5, model.nodes()); + final long[][] matrix = { + {0, 1, 3, 3, 1}, + {1, 0, 2, 4, 2}, + {3, 2, 0, 4, 4}, + {3, 4, 4, 0, 2}, + {1, 2, 4, 2, 0}, + }; + final IntBoolPair pair = model.addMatrixDimension( + matrix, + /*capacity=*/100, + /*fix_start_cumul_to_zero=*/true, + "Dimension"); + assertEquals(1, pair.getFirst()); + assertTrue(pair.getSecond()); + System.gc(); // model should keep alive the callback + model.setArcCostEvaluatorOfAllVehicles(pair.getFirst()); + + final RoutingSearchParameters searchParameters = + main.defaultRoutingSearchParameters() + .toBuilder() + .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) + .setTimeLimit(Duration.newBuilder().setSeconds(10)) + .build(); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solveWithParameters(searchParameters); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(10, solution.objectiveValue()); + } + + @Test + public void testRoutingModel_addDimension() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator manhattanCostCallback = createManhattanCostCallback(manager); + final int cost = model.registerTransitCallback(manhattanCostCallback); + model.setArcCostEvaluatorOfAllVehicles(cost); + + final LongBinaryOperator transit = (long firstIndex, long secondIndex) -> { + int firstNode = manager.indexToNode(firstIndex); + int secondNode = manager.indexToNode(secondIndex); + if (firstNode >= coordinates.size()) { + firstNode = 0; + } + if (secondNode >= coordinates.size()) { + secondNode = 0; + } + long distanceTime = manhattanCostCallback.applyAsLong(firstIndex, secondIndex) * 10; + long visitingTime = 1; + return distanceTime + visitingTime; + }; + assertTrue( + model.addDimension(model.registerTransitCallback(transit), 1000, 1000, true, "time")); + RoutingDimension dimension = model.getMutableDimension("time"); + + for (int i = 1; i < coordinates.size(); i++) { + int[] a = new int[1]; + a[0] = i; + model.addDisjunction(manager.nodesToIndices(a), 10); + + dimension.cumulVar(i).setMin(0); + dimension.cumulVar(i).setMax(40); + } + + RoutingSearchParameters searchParameters = + main.defaultRoutingSearchParameters() + .toBuilder() + .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) + .setTimeLimit(Duration.newBuilder().setSeconds(10)) + .build(); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + Assignment solution = model.solveWithParameters(searchParameters); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + solution = model.solve(solution); + assertNotNull(solution); + assertTrue(solution.objectiveValue() >= 24 && solution.objectiveValue() <= 30); + for (long i = 1; i < coordinates.size(); i++) { + assertTrue( + solution.min(dimension.cumulVar(i)) >= 0 && solution.max(dimension.cumulVar(i)) <= 40); + } + } + + @Test + public void testRoutingModel_dimensionVehicleSpanCost() { + final RoutingIndexManager manager = new RoutingIndexManager(2, 1, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator callback = createReturnOneCallback(); + assertTrue( + model.addDimension(model.registerTransitCallback(callback), 1000, 1000, true, "time")); + RoutingDimension dimension = model.getMutableDimension("time"); + dimension.cumulVar(1).setMin(10); + dimension.setSpanCostCoefficientForAllVehicles(2); + assertEquals(2, dimension.getSpanCostCoefficientForVehicle(0)); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(2 * (10 + 1), solution.objectiveValue()); + } + + @Test + public void testRoutingModel_dimensionGlobalSpanCost() { + final RoutingIndexManager manager = new RoutingIndexManager(3, 2, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator callback = createReturnOneCallback(); + assertTrue( + model.addDimension(model.registerTransitCallback(callback), 1000, 1000, false, "time")); + RoutingDimension timeDimension = model.getMutableDimension("time"); + timeDimension.cumulVar(1).setMin(10); + model.vehicleVar(1).setValue(0); + timeDimension.cumulVar(2).setMax(2); + model.vehicleVar(2).setValue(1); + timeDimension.setGlobalSpanCostCoefficient(2); + assertEquals(2, timeDimension.getGlobalSpanCostCoefficient()); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + assertEquals(2 * (11 - 1), solution.objectiveValue()); + } + + @Test + public void testRoutingModel_cumulVarSoftUpperBound() { + final RoutingIndexManager manager = new RoutingIndexManager(2, 1, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator callback = createReturnOneCallback(); + assertTrue( + model.addDimension(model.registerTransitCallback(callback), 1000, 1000, false, "time")); + RoutingDimension dimension = model.getMutableDimension("time"); + assertEquals(1000, dimension.getCumulVarSoftUpperBound(1)); + assertEquals(0, dimension.getCumulVarSoftUpperBoundCoefficient(1)); + dimension.setCumulVarSoftUpperBound(1, 5, 1); + assertEquals(5, dimension.getCumulVarSoftUpperBound(1)); + assertEquals(1, dimension.getCumulVarSoftUpperBoundCoefficient(1)); + } + + @Test + public void testRoutingModel_addDimensionWithVehicleCapacity() { + final RoutingIndexManager manager = new RoutingIndexManager(1, 3, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator callback = createReturnOneCallback(); + final long[] capacity = {5, 6, 7}; + model.addDimensionWithVehicleCapacity( + model.registerTransitCallback(callback), 1000, capacity, false, "dim"); + RoutingDimension dimension = model.getMutableDimension("dim"); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + for (int vehicle = 0; vehicle < 3; ++vehicle) { + assertEquals(vehicle + 4, solution.max(dimension.cumulVar(model.start(vehicle)))); + assertEquals(vehicle + 5, solution.max(dimension.cumulVar(model.end(vehicle)))); + } + } + + @Test + public void testRoutingModel_addDimensionWithVehicleTransits() { + final RoutingIndexManager manager = new RoutingIndexManager(1, 3, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator[] callbacks = new LongBinaryOperator[3]; + int[] transits = new int[3]; + for (int i = 0; i < 3; ++i) { + final int value = i + 1; + callbacks[i] = (long firstIndex, long secondIndex) -> value; + transits[i] = model.registerTransitCallback(callbacks[i]); + } + long capacity = 5; + model.addDimensionWithVehicleTransits(transits, 1000, capacity, false, "dim"); + RoutingDimension dimension = model.getMutableDimension("dim"); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + for (int vehicle = 0; vehicle < 3; ++vehicle) { + assertEquals( + capacity - (vehicle + 1), solution.max(dimension.cumulVar(model.start(vehicle)))); + assertEquals(capacity, solution.max(dimension.cumulVar(model.end(vehicle)))); + } + } + + @Test + public void testRoutingModel_addDimensionWithVehicleTransitAndCapacity() { + final RoutingIndexManager manager = new RoutingIndexManager(1, 3, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator[] callbacks = new LongBinaryOperator[3]; + final int[] transits = new int[3]; + for (int i = 0; i < 3; ++i) { + final int value = i + 1; + callbacks[i] = (long firstIndex, long secondIndex) -> value; + transits[i] = model.registerTransitCallback(callbacks[i]); + } + long[] capacity = new long[3]; + for (int i = 0; i < 3; ++i) { + capacity[i] = i + 5L; + } + model.addDimensionWithVehicleTransitAndCapacity(transits, 1000, capacity, false, "dim"); + final RoutingDimension dimension = model.getMutableDimension("dim"); + + assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); + final Assignment solution = model.solve(null); + assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertNotNull(solution); + for (int vehicle = 0; vehicle < 3; ++vehicle) { + assertEquals(4, solution.max(dimension.cumulVar(model.start(vehicle)))); + assertEquals(vehicle + 5, solution.max(dimension.cumulVar(model.end(vehicle)))); + } + } + + @Test + public void testRoutingModel_intVarVectorGetter() { + final RoutingIndexManager manager = new RoutingIndexManager(coordinates.size(), 1, 0); + final RoutingModel model = new RoutingModel(manager); + final LongBinaryOperator manhattanCostCallback = createManhattanCostCallback(manager); + final int cost = model.registerTransitCallback(manhattanCostCallback); + model.setArcCostEvaluatorOfAllVehicles(cost); + + final LongBinaryOperator transit = (long firstIndex, long secondIndex) -> { + int firstNode = manager.indexToNode(firstIndex); + int secondNode = manager.indexToNode(secondIndex); + if (firstNode >= coordinates.size()) { + firstNode = 0; + } + if (secondNode >= coordinates.size()) { + secondNode = 0; + } + long distanceTime = manhattanCostCallback.applyAsLong(firstIndex, secondIndex) * 10; + long visitingTime = 1; + return distanceTime + visitingTime; + }; + assertTrue( + model.addDimension(model.registerTransitCallback(transit), 1000, 1000, true, "time")); + RoutingDimension timeDimension = model.getMutableDimension("time"); + IntVar[] cumuls = timeDimension.cumuls(); + assertThat(cumuls).isNotEmpty(); + IntVar[] transits = timeDimension.transits(); + assertThat(transits).isNotEmpty(); + IntVar[] slacks = timeDimension.slacks(); + assertThat(slacks).isNotEmpty(); + IntVar[] nexts = model.nexts(); + assertThat(nexts).isNotEmpty(); + IntVar[] vehicleVars = model.vehicleVars(); + assertThat(vehicleVars).isNotEmpty(); } } diff --git a/examples/tests/SatSolverTest.java b/examples/tests/SatSolverTest.java index 943c73af26..656bdc4f95 100644 --- a/examples/tests/SatSolverTest.java +++ b/examples/tests/SatSolverTest.java @@ -23,15 +23,19 @@ import java.lang.Thread; import java.util.Random; import java.util.function.Consumer; import java.util.logging.Logger; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** Tests the CP-SAT java interface. */ public class SatSolverTest { private static final Logger logger = Logger.getLogger(SatSolverTest.class.getName()); + @BeforeEach + public void setUp() { + Loader.loadNativeLibraries(); + } @Test public void testDomainGetter() { - Loader.loadNativeLibraries(); System.out.println("testDomainGetter"); CpModel model = new CpModel(); @@ -47,7 +51,6 @@ public class SatSolverTest { @Test public void testCrashInPresolve() { - Loader.loadNativeLibraries(); System.out.println("testCrashInPresolve"); CpModel model = new CpModel(); @@ -83,7 +86,6 @@ public class SatSolverTest { private IntVar[] entitiesOne; @Test public void testCrashInSolveWithAllowedAssignment() { - Loader.loadNativeLibraries(); System.out.println("testCrashInSolveWithAllowedAssignment"); final CpModel model = new CpModel(); final int numEntityOne = 50000; @@ -113,7 +115,6 @@ public class SatSolverTest { @Test public void testCrashEquality() { - Loader.loadNativeLibraries(); System.out.println("testCrashInSolveWithAllowedAssignment"); final CpModel model = new CpModel(); @@ -164,7 +165,6 @@ public class SatSolverTest { @Test public void testLogCapture() { - Loader.loadNativeLibraries(); System.out.println("testLogCapture"); // Creates the model. diff --git a/makefiles/Makefile.java.mk b/makefiles/Makefile.java.mk index a309fc687c..1bb8e22106 100644 --- a/makefiles/Makefile.java.mk +++ b/makefiles/Makefile.java.mk @@ -751,9 +751,14 @@ check_java_pimpl: \ .PHONY: test_java_tests # Build and Run all Java Tests (located in examples/tests) test_java_tests: \ + rjava_KnapsackSolverTest \ + rjava_FlowTest \ rjava_LinearSolverTest \ rjava_ConstraintSolverTest \ rjava_RoutingSolverTest \ + rjava_LinearExprTest \ + rjava_CpModelTest \ + rjava_CpSolverTest \ rjava_SatSolverTest \ .PHONY: test_java_contrib # Build and Run all Java Contrib (located in examples/contrib) diff --git a/ortools/java/pom-test.xml.in b/ortools/java/pom-test.xml.in index e8e4ffe331..1999c2a71f 100644 --- a/ortools/java/pom-test.xml.in +++ b/ortools/java/pom-test.xml.in @@ -85,6 +85,11 @@ 1.1.3 test + + com.google.guava + guava + 30.1.1-jre + diff --git a/ortools/linear_solver/java/linear_solver.i b/ortools/linear_solver/java/linear_solver.i index 06ee824ea5..a66f55cfda 100644 --- a/ortools/linear_solver/java/linear_solver.i +++ b/ortools/linear_solver/java/linear_solver.i @@ -356,6 +356,7 @@ PROTO2_RETURN( // - loadSolutionFromProto; // Use hand-written version. // Expose some of the more advanced MPSolver API. +%rename (problemType) operations_research::MPSolver::ProblemType; // no test %rename (supportsProblemType) operations_research::MPSolver::SupportsProblemType; // no test %rename (setSolverSpecificParametersAsString) operations_research::MPSolver::SetSolverSpecificParametersAsString; // no test diff --git a/ortools/sat/java/sat.i b/ortools/sat/java/sat.i index 7619147498..3feab360d0 100644 --- a/ortools/sat/java/sat.i +++ b/ortools/sat/java/sat.i @@ -68,12 +68,10 @@ PROTO2_RETURN(operations_research::sat::CpSolverResponse, // processing to be passed to the std::function. // // TODO(user): cleanup java/functions.i and move the code there. - %{ #include // std::make_shared %} - %typemap(in) std::function %{ JavaVM* jvm; jenv->GetJavaVM(&jvm);