org.chocosolver.solver.search.strategy.Search Maven / Gradle / Ivy
Show all versions of choco-solver Show documentation
/*
* This file is part of choco-solver, http://choco-solver.org/
*
* Copyright (c) 2023, IMT Atlantique. All rights reserved.
*
* Licensed under the BSD 4-clause license.
*
* See LICENSE file in the project root for full license information.
*/
package org.chocosolver.solver.search.strategy;
import org.chocosolver.solver.search.restart.GeometricalCutoff;
import org.chocosolver.solver.search.restart.LinearCutoff;
import org.chocosolver.solver.search.restart.LubyCutoff;
import org.chocosolver.solver.Model;
import org.chocosolver.solver.ResolutionPolicy;
import org.chocosolver.solver.Solution;
import org.chocosolver.solver.Solver;
import org.chocosolver.solver.objective.ObjectiveStrategy;
import org.chocosolver.solver.objective.OptimizationPolicy;
import org.chocosolver.solver.search.loop.monitors.IMonitorOpenNode;
import org.chocosolver.solver.search.restart.MonotonicCutoff;
import org.chocosolver.solver.search.strategy.assignments.DecisionOperator;
import org.chocosolver.solver.search.strategy.assignments.DecisionOperatorFactory;
import org.chocosolver.solver.search.strategy.decision.Decision;
import org.chocosolver.solver.search.strategy.decision.IbexDecision;
import org.chocosolver.solver.search.strategy.selectors.values.*;
import org.chocosolver.solver.search.strategy.selectors.values.graph.edge.GraphEdgeSelector;
import org.chocosolver.solver.search.strategy.selectors.values.graph.edge.GraphLexEdge;
import org.chocosolver.solver.search.strategy.selectors.values.graph.edge.GraphRandomEdge;
import org.chocosolver.solver.search.strategy.selectors.values.graph.node.GraphLexNode;
import org.chocosolver.solver.search.strategy.selectors.values.graph.node.GraphNodeSelector;
import org.chocosolver.solver.search.strategy.selectors.values.graph.node.GraphRandomNode;
import org.chocosolver.solver.search.strategy.selectors.values.graph.priority.GraphNodeOrEdgeSelector;
import org.chocosolver.solver.search.strategy.selectors.values.graph.priority.GraphNodeThenEdges;
import org.chocosolver.solver.search.strategy.selectors.variables.*;
import org.chocosolver.solver.search.strategy.strategy.*;
import org.chocosolver.solver.variables.*;
import org.chocosolver.util.bandit.MOSS;
import org.chocosolver.util.bandit.Static;
import org.chocosolver.util.tools.VariableUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.function.ToDoubleBiFunction;
public class Search {
// ************************************************************************************
// GENERIC PATTERNS
// ************************************************************************************
/**
* Use the last conflict heuristic as a pluggin to improve a former search heuristic Should be
* set after specifying a search strategy.
*
* @return last conflict strategy
*/
public static AbstractStrategy lastConflict(
AbstractStrategy formerSearch) {
return lastConflict(formerSearch, 1);
}
/**
* Search heuristic combined with a constraint performing strong consistency on the next
* decision variable and branching on the value with the best objective bound (for optimization)
* and branches on the lower bound for SAT problems.
*
* BEWARE: ONLY FOR INTEGERS (lets the former search work for other variable types)
*
* @param formerSearch default search to branch on variables (defines the variable selector and
* the value selector when this does not hold)
* @return best bound strategy
*/
public static AbstractStrategy bestBound(AbstractStrategy formerSearch) {
if (formerSearch == null) {
throw new UnsupportedOperationException(
"the search strategy in parameter cannot be null! Consider using Search.defaultSearch(model)");
}
return new BoundSearch(formerSearch);
}
/**
* Use the last conflict heuristic as a pluggin to improve a former search heuristic Should be
* set after specifying a search strategy.
*
* @param k the maximum number of conflicts to store
* @return last conflict strategy
*/
public static AbstractStrategy lastConflict(
AbstractStrategy formerSearch, int k) {
if (formerSearch == null) {
throw new UnsupportedOperationException(
"the search strategy in parameter cannot be null! Consider using Search.defaultSearch(model)");
}
return new LastConflict<>(formerSearch.getVariables()[0].getModel(), formerSearch, k);
}
/**
* Use the conflict ordering search as a pluggin to improve a former search heuristic Should be
* set after specifying a search strategy.
*
* @return last conflict strategy
*/
public static AbstractStrategy conflictOrderingSearch(
AbstractStrategy formerSearch) {
return new ConflictOrderingSearch<>(formerSearch.getVariables()[0].getModel(),
formerSearch);
}
/**
* Make the input search strategy greedy, that is, decisions can be applied but not refuted.
*
* @param search a search heuristic building branching decisions
* @return a greedy form of search
*/
public static AbstractStrategy> greedySearch(AbstractStrategy> search) {
return new GreedyBranching(search);
}
/**
* Apply sequentialy enumeration strategies. Strategies are considered in input order. When
* strategy i returns null (all variables are instantiated) the i+1 ones is
* activated.
*
* @param searches ordered set of enumeration strategies
* @throws IllegalArgumentException when the array of strategies is either null or empty.
*/
public static AbstractStrategy sequencer(AbstractStrategy... searches) {
if (searches == null || searches.length == 0) {
throw new IllegalArgumentException("The array of strategies cannot be null or empty");
}
return new StrategiesSequencer(searches);
}
// ************************************************************************************
// SETVAR STRATEGIES
// ************************************************************************************
/**
* Generic strategy to branch on set variables
*
* @param varS variable selection strategy
* @param valS integer selection strategy
* @param enforceFirst branching order true = enforce first; false = remove first
* @param sets SetVar array to branch on
* @return a strategy to instantiate sets
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
public static SetStrategy setVarSearch(VariableSelector varS, SetValueSelector valS,
boolean enforceFirst, SetVar... sets) {
if (sets == null || sets.length == 0) {
throw new IllegalArgumentException("The array of variables cannot be null or empty");
}
return new SetStrategy(sets, varS, valS, enforceFirst);
}
/**
* strategy to branch on sets by choosing the first unfixed variable and forcing its first
* unfixed value
*
* @param sets variables to branch on
* @return a strategy to instantiate sets
*/
public static SetStrategy setVarSearch(SetVar... sets) {
return setVarSearch(new GeneralizedMinDomVarSelector<>(), new SetDomainMin(), true, sets);
}
/**
* Assignment strategy which selects a variable according to DomOverWDeg
and assign
* it to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Boosting Systematic Search by Weighting Constraints."
* Boussemart et al. ECAI 2004.
* https://dblp.org/rec/conf/ecai/BoussemartHLS04
*/
public static AbstractStrategy domOverWDegSearch(SetVar... vars) {
return setVarSearch(new DomOverWDeg<>(vars, 0), new SetDomainMin(), true, vars);
}
/**
* Assignment strategy which selects a variable according to refined DomOverWDeg
and assign
* it to its lower bound, where the weight incrementer is "ca.cd".
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Refining Constraint Weighting." Wattez et al. ICTAI 2019.
* https://dblp.org/rec/conf/ictai/WattezLPT19
*/
public static AbstractStrategy domOverWDegRefSearch(SetVar... vars) {
return setVarSearch(new DomOverWDegRef<>(vars, 0), new SetDomainMin(), true, vars);
}
/**
* Assignment strategy which selects a variable according to Conflict History
* and assigns it to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Conflict history based search for constraint satisfaction problem."
* Habet et al. SAC 2019.
* https://dblp.org/rec/conf/sac/HabetT19
*/
public static AbstractStrategy conflictHistorySearch(SetVar... vars) {
return setVarSearch(new ConflictHistorySearch<>(vars, 0), new SetDomainMin(), true, vars);
}
/**
* Assignment strategy which selects a variable according to Failure rate based
* variable ordering and assigns it to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Failure Based Variable Ordering Heuristics for Solving CSPs."
* H. Li, M. Yin, and Z. Li, CP 2021.
* https://dblp.org/rec/conf/cp/LiYL21
*/
public static AbstractStrategy failureRateBasedSearch(SetVar... vars) {
return setVarSearch(new FailureBased<>(vars, 0, 2), new SetDomainMin(), true, vars);
}
/**
* Assignment strategy which selects a variable according to Failure length based
* variable ordering and assigns it to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Failure Based Variable Ordering Heuristics for Solving CSPs."
* H. Li, M. Yin, and Z. Li, CP 2021.
* https://dblp.org/rec/conf/cp/LiYL21
*/
public static AbstractStrategy failureLengthBasedSearch(SetVar... vars) {
return setVarSearch(new FailureBased<>(vars, 0, 4), new SetDomainMin(), true, vars);
}
// ************************************************************************************
// GRAPHVAR STRATEGIES
// ************************************************************************************
/**
* Generic strategy to branch on graph variables
*
* @param varS Variable selection strategy
* @param nodeOrEdgeS Node or edge selection (defines if whenever a decision must be on nodes or edges)
* @param nodeS Node selector (defines which node to enforce/remove if decision is on nodes)
* @param edgeS Edge selector (defines which edge to enforce/remove if decision is on edges)
* @param enforceFirst branching order true = enforce first; false = remove first
* @param graphs GraphVar array to branch on
* @return a search strategy on GraphVar
*/
public static GraphStrategy graphVarSearch(VariableSelector varS, GraphNodeOrEdgeSelector nodeOrEdgeS,
GraphNodeSelector nodeS, GraphEdgeSelector edgeS, boolean enforceFirst,
GraphVar... graphs) {
if (graphs == null || graphs.length == 0) {
throw new IllegalArgumentException("The set of variables cannot be null or empty");
}
return new GraphStrategy(graphs, varS, nodeOrEdgeS, nodeS, edgeS, enforceFirst);
}
/**
* Default graph var search.
*
* Variable selection: input order.
* Node or edges selection: nodes first then edges.
* Node selection: lexicographic order.
* Edge selection lexicographic order.
* Enforce first.
*
*
node branching:
* Let i be the first node such that
* i in envelope(g) and i not in kernel(g).
* The decision adds i to the kernel of g.
* It is fails, then i is removed from the envelope of g.
*
* edge branching:
*
node branching:
* Let (i,j) be the first edge such that
* (i,j) in envelope(g) and (i,j) not in kernel(g).
* The decision adds (i,j) to the kernel of g.
* It is fails, then (i,j) is removed from the envelope of g
*
* @param graphs graph variables to branch on
*/
public static GraphStrategy graphVarSearch(GraphVar... graphs) {
return graphVarSearch(
new InputOrder<>(graphs[0].getModel()),
new GraphNodeThenEdges(),
new GraphLexNode(),
new GraphLexEdge(),
true,
graphs
);
}
/**
* Random graph var search.
*
* Variable selection: random.
* Node or edges selection: nodes first then edges.
* Node selection: random.
* Edge selection random.
* Enforce first.
*
* @param seed the seed for random selection
* @param graphs graph variables to branch on
* @return a randomized graph variables search strategy
*/
public static GraphStrategy randomGraphVarSearch(long seed, GraphVar... graphs) {
return graphVarSearch(
new Random<>(seed),
new GraphNodeThenEdges(),
new GraphRandomNode(seed),
new GraphRandomEdge(seed),
true,
graphs
);
}
// ************************************************************************************
// REALVAR STRATEGIES
// ************************************************************************************
/**
* Generic strategy to branch on real variables, based on domain splitting. A real decision is
* like:
*
* - left branch: X ≤ v
* - right branch: X ≥ v + e
*
* where 'e' is given by epsilon.
*
*
* @param varS variable selection strategy
* @param valS strategy to select where to split domains
* @param epsilon gap for refutation
* @param rvars RealVar array to branch on
* @param leftFirst select left range first
* @return a strategy to instantiate reals
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
public static RealStrategy realVarSearch(VariableSelector varS, RealValueSelector valS,
double epsilon, boolean leftFirst, RealVar... rvars) {
if (rvars == null || rvars.length == 0) {
throw new IllegalArgumentException("The array of variables cannot be null or empty");
}
return new RealStrategy(rvars, varS, valS, epsilon, leftFirst);
}
/**
* strategy to branch on real variables by choosing sequentially the next variable domain to
* split in two, wrt the middle value. A real decision is like:
*
* - left branch: X ≤ v
* - right branch: X ≥ v + e
*
* where 'e' is given by epsilon.
*
*
* @param epsilon gap for refutation
* @param reals variables to branch on
* @return a strategy to instantiate real variables
*/
public static RealStrategy realVarSearch(double epsilon, RealVar... reals) {
return realVarSearch(new Cyclic<>(), new RealDomainMiddle(), epsilon, true, reals);
}
/**
* Generic strategy to branch on real variables, based on domain splitting.
*
* A real decision is like:
*
* - left branch: X ≤ v
* - right branch: X ≥ v + epsilon
*
* where epsilon is given or equal to the smallest precision among rvars divide by 10.
*
*
* @param varS variable selection strategy
* @param valS strategy to select where to split domains
* @param leftFirst select left range first
* @param rvars RealVar array to branch on
* @return a strategy to instantiate reals
*/
public static RealStrategy realVarSearch(VariableSelector varS, RealValueSelector valS,
boolean leftFirst, RealVar... rvars) {
return realVarSearch(varS, valS, Double.NaN, leftFirst, rvars);
}
/**
* strategy to branch on real variables by choosing sequentially the next variable domain to
* split in two, wrt the middle value.
*
* A real decision is like:
*
* - left branch: X ≤ v
* - right branch: X ≥ v + {@link Double#MIN_VALUE}
*
*
*
* @param reals variables to branch on
* @return a strategy to instantiate real variables
*/
public static RealStrategy realVarSearch(RealVar... reals) {
return realVarSearch(new Cyclic<>(), new RealDomainMiddle(), true, reals);
}
// ************************************************************************************
// INTVAR STRATEGIES
// ************************************************************************************
/**
* Builds your own search strategy based on binary decisions.
*
* @param varSelector defines how to select a variable to branch on.
* @param valSelector defines how to select a value in the domain of the selected variable
* @param decisionOperator defines how to modify the domain of the selected variable with the
* selected value
* @param vars variables to branch on
* @return a custom search strategy
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
public static IntStrategy intVarSearch(VariableSelector varSelector,
IntValueSelector valSelector,
DecisionOperator decisionOperator,
IntVar... vars) {
if (vars == null || vars.length == 0) {
throw new IllegalArgumentException("The array of variables cannot be null or empty");
}
return new IntStrategy(vars, varSelector, valSelector, decisionOperator);
}
/**
* Builds your own assignment strategy based on binary decisions. Selects a variable X
* and a value V to make the decision X = V. Note that value assignments are the public static
* decision operators. Therefore, they are not mentioned in the search heuristic name.
*
* @param varSelector defines how to select a variable to branch on.
* @param valSelector defines how to select a value in the domain of the selected variable
* @param vars variables to branch on
* @return a custom search strategy
*/
public static IntStrategy intVarSearch(VariableSelector varSelector,
IntValueSelector valSelector,
IntVar... vars) {
return intVarSearch(varSelector, valSelector, DecisionOperatorFactory.makeIntEq(), vars);
}
/**
* Builds a default search heuristics of integer variables Variable selection relies on {@link
* #domOverWDegSearch(IntVar...)} Value selection relies on InDomainBest for optimization and
* InDomainMin for satisfaction
*
* @param vars variables to branch on
* @return a default search strategy
*/
public static AbstractStrategy intVarSearch(IntVar... vars) {
Model model = vars[0].getModel();
IntValueSelector valueSelector;
if (model.getResolutionPolicy() == ResolutionPolicy.SATISFACTION
|| !(model.getObjective() instanceof IntVar)) {
valueSelector = new IntDomainMin();
} else {
valueSelector = new IntDomainBest();
model.getSolver().attach(model.getSolver().defaultSolution());
valueSelector = new IntDomainLast(model.getSolver().defaultSolution(), valueSelector, null);
}
return intVarSearch(new DomOverWDeg<>(vars, 0), valueSelector, vars);
}
/**
* Assignment strategy which selects a variable according to DomOverWDeg
and assign
* it to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Boosting Systematic Search by Weighting Constraints."
* Boussemart et al. ECAI 2004.
* https://dblp.org/rec/conf/ecai/BoussemartHLS04
*/
public static AbstractStrategy domOverWDegSearch(IntVar... vars) {
return intVarSearch(new DomOverWDeg<>(vars, 0), new IntDomainMin(), vars);
}
/**
* Assignment strategy which selects a variable according to refined DomOverWDeg
and assign
* it to its lower bound, where the weight incrementer is "ca.cd".
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Refining Constraint Weighting." Wattez et al. ICTAI 2019.
* https://dblp.org/rec/conf/ictai/WattezLPT19
*/
public static AbstractStrategy domOverWDegRefSearch(IntVar... vars) {
return intVarSearch(new DomOverWDegRef<>(vars, 0), new IntDomainMin(), vars);
}
/**
* Create an Activity based search strategy.
*
*
Uses public static parameters
* (GAMMA=0.999d, DELTA=0.2d, ALPHA=8, RESTART=1.1d, FORCE_SAMPLING=1)
*
* @param vars collection of variables
* @return an Activity based search strategy.
* @implNote This is based on "Activity-Based Search for Black-Box Constraint Programming Solvers."
* Michel et al. CPAIOR 2012.
* https://dblp.org/rec/conf/cpaior/MichelH12
* @throws IllegalArgumentException when the array of variables is either null or empty.
*/
public static AbstractStrategy activityBasedSearch(IntVar... vars) {
if (vars == null || vars.length == 0) {
throw new IllegalArgumentException("The array of variables cannot be null or empty");
}
return new ActivityBased(vars);
}
/**
* Assignment strategy which selects a variable according to Conflict History
* and assigns it to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Conflict history based search for constraint satisfaction problem."
* Habet et al. SAC 2019.
* https://dblp.org/rec/conf/sac/HabetT19
*/
public static AbstractStrategy conflictHistorySearch(IntVar... vars) {
return intVarSearch(new ConflictHistorySearch<>(vars, 0), new IntDomainMin(), vars);
}
/**
* Assignment strategy which selects a variable according to Failure rate based
* variable ordering and assigns it to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Failure Based Variable Ordering Heuristics for Solving CSPs."
* H. Li, M. Yin, and Z. Li, CP 2021.
* https://dblp.org/rec/conf/cp/LiYL21
*/
public static AbstractStrategy failureRateBasedSearch(IntVar... vars) {
return intVarSearch(new FailureBased<>(vars, 0, 2), new IntDomainMin(), vars);
}
/**
* Assignment strategy which selects a variable according to Failure length based
* variable ordering and assigns it to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
* @implNote This is based on "Failure Based Variable Ordering Heuristics for Solving CSPs."
* H. Li, M. Yin, and Z. Li, CP 2021.
* https://dblp.org/rec/conf/cp/LiYL21
*/
public static AbstractStrategy failureLengthBasedSearch(IntVar... vars) {
return intVarSearch(new FailureBased<>(vars, 0, 4), new IntDomainMin(), vars);
}
/**
* Randomly selects a variable and assigns it to a value randomly taken in - the domain in case
* the variable has an enumerated domain - {LB,UB} (one of the two bounds) in case the domain is
* bounded
*
* @param vars list of variables
* @param seed a seed for random
* @return assignment strategy
*/
public static IntStrategy randomSearch(IntVar[] vars, long seed) {
IntValueSelector value = new IntDomainRandom(seed);
IntValueSelector bound = new IntDomainRandomBound(seed);
IntValueSelector selector = var -> {
if (var.hasEnumeratedDomain()) {
return value.selectValue(var);
} else {
return bound.selectValue(var);
}
};
return intVarSearch(new Random<>(seed), selector, vars);
}
/**
* Defines a branching strategy over the objective variable Note that it is only activated after
* a first solution. This should be completed with another strategy with a larger scope.
*
* @param objective objective variable
* @param optPolicy policy to adopt for the optimization process
* @return a assignment strategy
*/
public static AbstractStrategy objectiveStrategy(IntVar objective,
OptimizationPolicy optPolicy) {
return new ObjectiveStrategy(objective, optPolicy);
}
// ************************************************************************************
// SOME EXAMPLES OF STRATEGIES YOU CAN BUILD
// ************************************************************************************
/**
* Assigns the first non-instantiated variable to its lower bound.
*
* @param vars list of variables
* @return int strategy based on value assignments
*/
public static IntStrategy inputOrderLBSearch(IntVar... vars) {
return intVarSearch(new InputOrder<>(vars[0].getModel()), new IntDomainMin(), vars);
}
/**
* Assigns the first non-instantiated variable to its upper bound.
*
* @param vars list of variables
* @return assignment strategy
*/
public static IntStrategy inputOrderUBSearch(IntVar... vars) {
return intVarSearch(new InputOrder<>(vars[0].getModel()), new IntDomainMax(), vars);
}
/**
* Assigns the non-instantiated variable of the smallest domain size to its lower bound.
*
* @param vars list of variables
* @return assignment strategy
*/
public static IntStrategy minDomLBSearch(IntVar... vars) {
return intVarSearch(new FirstFail(vars[0].getModel()), new IntDomainMin(), vars);
}
/**
* Assigns the non-instantiated variable of the smallest domain size to its upper bound.
*
* @param vars list of variables
* @return assignment strategy
*/
public static IntStrategy minDomUBSearch(IntVar... vars) {
return intVarSearch(new FirstFail(vars[0].getModel()), new IntDomainMax(), vars);
}
// ************************************************************************************
// DEFAULT STRATEGY (COMPLETE)
// ************************************************************************************
/**
* Creates a default search strategy for the given model. This heuristic is complete (handles
* IntVar, BoolVar, SetVar, GraphVar, and RealVar)
*
* @param model a model requiring a default search strategy
*/
public static AbstractStrategy defaultSearch(Model model) {
Solver r = model.getSolver();
// 1. retrieve variables, keeping the declaration order, and put them in four groups:
List livars = new ArrayList<>(); // integer and boolean variables
List lsvars = new ArrayList<>(); // set variables
List> lgvars = new ArrayList<>(); // graph variables
List lrvars = new ArrayList<>();// real variables.
Variable[] variables = model.getVars();
Variable objective = null;
for (Variable var : variables) {
int type = var.getTypeAndKind();
if ((type & (Variable.CSTE)) == 0) {
int kind = type & Variable.KIND;
switch (kind) {
case Variable.BOOL:
case Variable.INT:
livars.add((IntVar) var);
break;
case Variable.SET:
lsvars.add((SetVar) var);
break;
case Variable.GRAPH:
lgvars.add((GraphVar>) var);
break;
case Variable.REAL:
lrvars.add((RealVar) var);
break;
default:
break; // do not throw exception to allow ad hoc variable kinds
}
}
}
// 2. extract the objective variable if any (to avoid branching on it)
if (r.getObjectiveManager().isOptimization()) {
objective = r.getObjectiveManager().getObjective();
if ((objective.getTypeAndKind() & Variable.REAL) != 0) {
//noinspection SuspiciousMethodCalls
lrvars.remove(objective);// real var objective
} else {
assert (objective.getTypeAndKind() & Variable.INT) != 0;
//noinspection SuspiciousMethodCalls
livars.remove(objective);// bool/int var objective
}
}
// 3. Creates a default search strategy for each variable kind
ArrayList strats = new ArrayList<>();
if (livars.size() > 0) {
strats.add(intVarSearch(livars.toArray(new IntVar[0])));
}
if (lsvars.size() > 0) {
strats.add(setVarSearch(lsvars.toArray(new SetVar[0])));
}
if (lgvars.size() > 0) {
strats.add(graphVarSearch(lgvars.toArray(new GraphVar[0])));
}
if (lrvars.size() > 0) {
strats.add(realVarSearch(lrvars.toArray(new RealVar[0])));
}
// 4. lexico LB/UB branching for the objective variable
if (objective != null) {
boolean max = r.getObjectiveManager().getPolicy() == ResolutionPolicy.MAXIMIZE;
if ((objective.getTypeAndKind() & Variable.REAL) != 0) {
strats.add(
realVarSearch(new Cyclic<>(), max ? new RealDomainMax() : new RealDomainMin(),
!max, (RealVar) objective));
} else {
strats.add(
max ? minDomUBSearch((IntVar) objective) : minDomLBSearch((IntVar) objective));
}
}
// 5. avoid null pointers in case all variables are instantiated
if (strats.isEmpty()) {
strats.add(minDomLBSearch(model.boolVar(true)));
}
// 6. add last conflict
return lastConflict(sequencer(strats.toArray(new AbstractStrategy[0])));
}
/**
*
* Create a strategy which lets Ibex terminates the solving process for the CSP,
* once all integer variables have been instantiated.
*
* Note that if the system is not constrained enough, there can be an infinite number of
* solutions.
*
* For example, solving the function
x,y in [0.0,1.0] with
x + y = 1.0
will
* return x,y in [0.0,1.0] and not a single solution.
*
* If one wants a unique solution, calling {@link #realVarSearch(RealVar...)} should be
* considered.
*
*
* @param model declaring model
* @return a strategy that lets Ibex terminates the solving process.
*/
public static AbstractStrategy ibexSolving(Model model) {
//noinspection unchecked
return new AbstractStrategy(model.getVars()) {
final IbexDecision dec = new IbexDecision(model);
@Override
public Decision getDecision() {
if (dec.inUse()) {
return null;
} else {
return dec;
}
}
};
}
/**
* Enum for commonly used variable selectors.
*
* To declare a variable selector to be part of a search strategy,
* use the following code:
*
* {@code
* AbstractStrategy strat = Search.VarH.CHS.make(solver, vars, Search.VarH.MIN, true);
* solver.setSearch(strat);
*
*/
public enum VarH {
/**
* To select variables according Activity-based Search.
* {@code valueSelector} parameter is ignored.
*
* @see ActivityBased
*/
ABS {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return ACTIVITY.make(solver, vars, Search.ValH.DEFAULT, flushThs, last);
}
},
/**
* To select variables according to {@link #ABS}
* Values can be selected with another heuristic.
*
* @see ActivityBased
*/
ACTIVITY {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
Model model = solver.getModel();
return new ActivityBased(model,
vars,
valueSelector == Search.ValH.DEFAULT ? null : valueSelector.make(solver, last),
0.999d,
0.2d,
8,
1,
model.getSeed());
}
},
/**
* To select variables according to Conflict History-based Search.
*
* @see ConflictHistorySearch
*/
CHS {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return new IntStrategy(vars,
new ConflictHistorySearch<>(vars, solver.getModel().getSeed(), flushThs),
valueSelector.make(solver, last));
}
},
/**
* To select variables according to the size of their current domain.
*
* @see FirstFail
*/
DOM {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return Search.intVarSearch(
new FirstFail(solver.getModel()),
valueSelector.make(solver, last),
vars);
}
},
/**
* To select variables to constraint weighting.
*
* @see DomOverWDeg
*/
DOMWDEG {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return new IntStrategy(vars,
new DomOverWDeg<>(vars, solver.getModel().getSeed(), flushThs),
valueSelector.make(solver, last));
}
},
/**
* To select variables to refined constraint weighting.
*
* @see DomOverWDegRef
*/
DOMWDEGR {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return new IntStrategy(vars,
new DomOverWDegRef<>(vars, solver.getModel().getSeed(), flushThs),
valueSelector.make(solver, last));
}
},
/**
* To select {@link Search#defaultSearch(Model)}
*/
DEFAULT {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
//noinspection unchecked
return defaultSearch(solver.getModel());
}
},
/**
* To select variables according to Failure rate based variable ordering with decaying factor.
*/
FRBA {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return new IntStrategy(vars,
new FailureBased<>(vars, solver.getModel().getSeed(), 2),
valueSelector.make(solver, last));
}
},
/**
* To select variables according to Failure length based variable ordering with decaying factor.
*/
FLBA {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return new IntStrategy(vars,
new FailureBased<>(vars, solver.getModel().getSeed(), 4),
valueSelector.make(solver, last));
}
},
/**
* To select variables according to Impact-based Search.
* {@code valueSelector} parameter is ignored.
*
* @see ImpactBased
*/
IBS {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return IMPACT.make(solver, vars, Search.ValH.DEFAULT, flushThs, last);
}
},
/**
* To select variables according to Impact-based Search.
* Values can be selected with another heuristic.
*
* @see ImpactBased
*/
IMPACT {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return new ImpactBased(vars,
valueSelector == Search.ValH.DEFAULT ? null : valueSelector.make(solver, last),
2,
512,
2048,
solver.getModel().getSeed(),
false);
}
},
/**
* To select variables according to their order in {@code vars}.
*/
INPUT {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return Search.intVarSearch(
new InputOrder<>(solver.getModel()),
valueSelector.make(solver, last),
vars);
}
},
/**
* To select variables randomly.
*/
RAND {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
return Search.intVarSearch(
new Random<>(solver.getModel().getSeed()),
valueSelector.make(solver, last),
vars);
}
},
MAB_CHS_DWDEG_STATIC {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
//noinspection unchecked
return new MultiArmedBanditSequencer(
new AbstractStrategy[]{
CHS.make(solver, vars, valueSelector, flushThs, last),
DOMWDEG.make(solver, vars, valueSelector, flushThs, last)
},
new Static(new double[]{.7, .3}, new java.util.Random(solver.getModel().getSeed())),
(a, t) -> 0.d
);
}
},
MAB_CHS_DWDEG_MOSS {
@Override
public AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last) {
final long[] pat = {0, 0};
final HashSet selected = new HashSet<>();
ToDoubleBiFunction reward = (a, t) -> {
double r = Math.log(solver.getNodeCount() - pat[0]) /
Math.log(VariableUtils.searchSpaceSize(selected.iterator()))
//+ solver.getSolutionCount() - pat[1]
;
pat[0] = solver.getNodeCount();
pat[1] = solver.getSolutionCount();
selected.clear();
return r;
};
solver.plugMonitor(new IMonitorOpenNode() {
@Override
public void afterOpenNode() {
selected.add((IntVar) solver.getDecisionPath().getLastDecision().getDecisionVariable());
}
});
//noinspection unchecked
return new MultiArmedBanditSequencer(
new AbstractStrategy[]{
CHS.make(solver, vars, valueSelector, flushThs, last),
DOMWDEG.make(solver, vars, valueSelector, flushThs, last)
},
new MOSS(2),
reward
);
}
};
/**
* Declare the search strategy based on parameters
*
* @param solver target solver
* @param vars array of integer variables
* @param valueSelector the value selector enum
* @param flushThs flush threshold, when reached, it flushes scores
* @param last set to {@code true} to use {@link IntDomainLast} meta value strategy.
* @return a search strategy on {@code IntVar[]}
*/
public abstract AbstractStrategy make(Solver solver, IntVar[] vars, Search.ValH valueSelector, int flushThs, boolean last);
}
/**
* Enum for commonly used value selectors.
*
* To declare a value selector to be part of a search strategy,
* use the following code:
*
* {@code
* AbstractStrategy strat = Search.VarH.CHS.declare(solver, vars, Search.VarH.MIN, true);
* solver.setSearch(strat);
*
*/
public enum ValH {
/**
* To select the best value according to the best objective bound.
*
* @see IntDomainBest
*/
BEST {
@Override
public IntValueSelector make(Solver solver, boolean last) {
if (solver.getModel().getResolutionPolicy() == ResolutionPolicy.SATISFACTION) {
return MIN.make(solver, last);
}
return last(solver, new IntDomainBest(), last);
}
},
/**
* To select the best value according to the best objective bound when looking for
* the first solution, then return the lowest bound.
*
* @see IntDomainBest
* @see IntDomainMin
*/
BMIN {
@Override
public IntValueSelector make(Solver solver, boolean last) {
if (solver.getModel().getResolutionPolicy() == ResolutionPolicy.SATISFACTION) {
return MIN.make(solver, last);
}
return last(solver,
new IntValueSelector() {
IntValueSelector sel = new IntDomainBest();
@Override
public int selectValue(IntVar var) {
if (var.getModel().getSolver().getSolutionCount() > 0) {
sel = new IntDomainMin();
}
return sel.selectValue(var);
}
}, last);
}
},
/**
* To select the best value according to the best objective bound.
*
* @see IntDomainBest
*/
BLAST {
@Override
public IntValueSelector make(Solver solver, boolean last) {
if (solver.getModel().getResolutionPolicy() == ResolutionPolicy.SATISFACTION) {
return MIN.make(solver, last);
}
Solution lastSol = solver.defaultSolution();
return last(solver, new IntDomainBest((v, i) -> lastSol.exists() && lastSol.getIntVal(v) == i), last);
}
},
/**
* Return {@link #BEST}.
*/
DEFAULT {
@Override
public IntValueSelector make(Solver solver, boolean last) {
return BEST.make(solver, last);
}
},
/**
* To select the maximal value in the current domain of the selected variable.
*
* @see IntDomainMax
*/
MAX {
@Override
public IntValueSelector make(Solver solver, boolean last) {
return last(solver, new IntDomainMax(), last);
}
},
/**
* To select the median value in the current domain of the selected variable.
*
* @see IntDomainMedian
*/
MED {
@Override
public IntValueSelector make(Solver solver, boolean last) {
return last(solver, new IntDomainMedian(), last);
}
},
/**
* To select the middle value in the current domain of the selected variable with floor rounding.
*
* @see IntDomainMiddle
*/
MIDFLOOR {
@Override
public IntValueSelector make(Solver solver, boolean last) {
return last(solver, new IntDomainMiddle(true), last);
}
},
/**
* To select the middle value in the current domain of the selected variable with ceil rouding.
*
* @see IntDomainMiddle
*/
MIDCEIL {
@Override
public IntValueSelector make(Solver solver, boolean last) {
return last(solver, new IntDomainMiddle(false), last);
}
},
/**
* To select the minimal value in the current domain of the selected variable.
*
* @see IntDomainMin
*/
MIN {
@Override
public IntValueSelector make(Solver solver, boolean last) {
return last(solver, new IntDomainMin(), last);
}
},
/**
* To select values randomly.
*
* @see IntDomainRandom
*/
RAND {
@Override
public IntValueSelector make(Solver solver, boolean last) {
return last(solver, new IntDomainRandom(solver.getModel().getSeed()), last);
}
};
/**
* Build the value selector
*
* @param solver solver to use in
* @param last set to {@code true} to use meta value selector based on last solution found.
* @return a value selector
*/
public abstract IntValueSelector make(Solver solver, boolean last);
/**
* If {@code last} is set to {@code true}, add {@link IntDomainLast} meta value selector.
*
* @param solver the solver to record solutions from
* @param selector the defined value selector
* @param last use meta value selector.
* @return a value selector
*/
IntValueSelector last(Solver solver, IntValueSelector selector, boolean last) {
if (last) {
// default
Model model = solver.getModel();
if (model.getResolutionPolicy() == ResolutionPolicy.SATISFACTION) {
return selector;
}
model.getSolver().attach(model.getSolver().defaultSolution());
return new IntDomainLast(model.getSolver().defaultSolution(), selector, null);
} else {
return selector;
}
}
}
/**
* Enum for commonly used value restarting policies.
*
* To declare a value selector to be part of a search strategy,
* use the following code:
*
* {@code
* Search.Restarts.LUBY.declare(solver, 50, 5000);
*
*/
public enum Restarts {
/**
* Define no restart strategy.
*
* @apiNote Does not remove or erase previously defined restart policy
*/
NONE {
@Override
public void declare(Solver solver, int cutoff, double factor, int offset) {
// nothing to do
}
},
/**
* To use a monotonic restart strategy.
* This policy will restart every {@code cutoff} failures, until {@code offset} restarts occur.
*
* @implNote {@code factor} is ignored.
* @see MonotonicCutoff
*/
MONOTONIC {
@Override
public void declare(Solver solver, int cutoff, double factor, int offset) {
solver.setRestarts(
count -> solver.getFailCount() >= count,
new MonotonicCutoff(cutoff),
offset
);
solver.setNoGoodRecordingFromRestarts();
}
},
/**
* To use a linear restart strategy.
*
* @implNote {@code factor} is ignored.
* @see LinearCutoff
*/
LINEAR {
@Override
public void declare(Solver solver, int cutoff, double factor, int offset) {
solver.setRestarts(
count -> solver.getFailCount() >= count,
new LinearCutoff(cutoff),
offset
);
solver.setNoGoodRecordingFromRestarts();
}
},
/**
* To use a Luby restart strategy.
*
* @implNote {@code factor} is ignored.
* @see LubyCutoff
*/
LUBY {
@Override
public void declare(Solver solver, int cutoff, double factor, int offset) {
solver.setRestarts(
count -> solver.getFailCount() >= count,
new LubyCutoff(cutoff),
offset
);
solver.setNoGoodRecordingFromRestarts();
}
},
/**
* To use a geometric restart strategy.
*
* @see GeometricalCutoff
*/
GEOMETRIC {
@Override
public void declare(Solver solver, int cutoff, double factor, int offset) {
solver.setRestarts(
count -> solver.getFailCount() >= count,
new GeometricalCutoff(cutoff, factor),
offset
);
solver.setNoGoodRecordingFromRestarts();
}
};
public abstract void declare(Solver solver, int cutoff, double factor, int offset);
}
}