All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.chocosolver.solver.search.IResolutionHelper Maven / Gradle / Ivy

There is a newer version: 4.10.17
Show newest version
/*
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2022, IMT Atlantique. All rights reserved.
 *
 * Licensed under the BSD 4-clause license.
 *
 * See LICENSE file in the project root for full license information.
 */
package org.chocosolver.solver.search;

import org.chocosolver.solver.ISelf;
import org.chocosolver.solver.Solution;
import org.chocosolver.solver.Solver;
import org.chocosolver.solver.constraints.Constraint;
import org.chocosolver.solver.constraints.Propagator;
import org.chocosolver.solver.constraints.UpdatablePropagator;
import org.chocosolver.solver.constraints.nary.lex.PropLexInt;
import org.chocosolver.solver.constraints.unary.Member;
import org.chocosolver.solver.constraints.unary.NotMember;
import org.chocosolver.solver.objective.ParetoMaximizer;
import org.chocosolver.solver.search.limits.ACounter;
import org.chocosolver.solver.search.measure.IMeasures;
import org.chocosolver.solver.search.strategy.Search;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.util.ESat;
import org.chocosolver.util.criteria.Criterion;
import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Spliterator;
import java.util.function.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Interface to define most commonly used resolution procedures.
 * 

* Project: choco-solver. * * @author Jean-Guillaum Fages * @author Charles Prud'homme * @author Guillaume Lelouet * @author Dimitri Justeau-Allaire ([email protected]) * @since 25/04/2016. */ public interface IResolutionHelper extends ISelf { /** * Attempts to find a solution of the declared satisfaction problem. *

    *
  • If the method returns null:
  • *
      *
    • either a stop criterion (e.g., a time limit) stops the search before a solution has been found,
    • *
    • or no solution exists for the problem (i.e., over-constrained).
    • *
    *
  • if the method returns a {@link Solution}:
  • *
      *
    • a solution has been found. This method can be called anew to look for the next solution, if any.
    • *
    *
*

* If a solution has been found, since the search process stops on that solution, variables' value can be read, e.g., * {@code intvar.getValue()} or the solution can be recorded: *

*

     *    {@code
     * 	Solution s = new Solution(model);
     * 	s.record();
     * }
     * 
*

* Basically, this method runs the following instructions: *

*

     *     {@code
     *     if(ref().solve()) {
     *          return new Solution(ref()).record();
     *     }else{
     *          return null;
     *       }
     *     }
     * 
*

* Note that all variables will be recorded *

* Note that it clears the current objective function, if any * * @param stop optional criterion to stop the search before finding a solution * @return a {@link Solution} if and only if a solution has been found, null otherwise. */ default Solution findSolution(Criterion... stop) { ref().getModel().clearObjective(); ref().addStopCriterion(stop); boolean found = ref().solve(); ref().removeStopCriterion(stop); if (found) { return new Solution(ref().getModel()).record(); } else { return null; } } /** * Attempts to find all solutions of the declared satisfaction problem. *

    *
  • If the method returns an empty list:
  • *
      *
    • * either a stop criterion (e.g., a time limit) stops the search before any solution has been found, *
    • *
    • * or no solution exists for the problem (i.e., over-constrained). *
    • *
    *
  • if the method returns a list with at least one element in it:
  • *
      *
    • either the resolution stops eagerly du to a stop criterion before finding all solutions,
    • *
    • or all solutions have been found.
    • *
    *
*

* This method run the following instructions: *

     *     {@code
     *     List solutions = new ArrayList<>();
     *     while (model.getSolver().solve()){
     *          solutions.add(new Solution(model).record());
     *     }
     *     return solutions;
     *     }
     * 
*

* Note that all variables will be recorded *

* Note that it clears the current objective function, if any * * @param stop optional criterion to stop the search before finding all solutions * @return a list that contained the found solutions. */ default List findAllSolutions(Criterion... stop) { ref().getModel().clearObjective(); ref().addStopCriterion(stop); List solutions = new ArrayList<>(); while (ref().solve()) { solutions.add(new Solution(ref().getModel()).record()); } ref().removeStopCriterion(stop); return solutions; } /** * Attempts to find all solutions of the declared problem. *

    *
  • If the method returns an empty list:
  • *
      *
    • either a stop criterion (e.g., a time limit) stops the search before any solution has been found,
    • *
    • or no solution exists for the problem (i.e., over-constrained).
    • *
    *
  • if the method returns a list with at least one element in it:
  • *
      *
    • either the resolution stops eagerly du to a stop criterion before finding all solutions,
    • *
    • or all solutions have been found.
    • *
    *
*

* Basically, this method runs the following instructions: *

*

     * {@code
     * 	List solutions = new ArrayList<>();
     * 	while (model.getSolver().solve()) {
     * 		solutions.add(new Solution(model).record());
     *    }
     * 	return solutions;
     * }
     * 
*

* Note that all variables will be recorded * * @param stop optional criterion to stop the search before finding all/best solution * @return a list that contained the found solutions. */ default Stream streamSolutions(Criterion... stop) { ref().addStopCriterion(stop); /*CPRU cannot infer type arguments for java.util.Spliterator*/ Spliterator it = new Spliterator() { @Override public boolean tryAdvance(Consumer action) { if (ref().solve()) { action.accept(new Solution(ref().getModel()).record()); return true; } ref().removeStopCriterion(stop); return false; } @Override public Spliterator trySplit() { return null; } @Override public long estimateSize() { return Long.MAX_VALUE; } @Override public int characteristics() { return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.CONCURRENT; } }; return StreamSupport.stream(it, false); } /** * Attempt to find the solution that optimizes the mono-objective problem defined by a unique objective variable and * an optimization criteria. *

    *
  • If this method returns null:
  • *
      *
    • either the resolution stops eagerly du to a stop criterion (e.g., a time limit) and no solution has been found * so far,
    • *
    • or the problem cannot be satisfied (i.e., over constrained).
    • *
    *
  • If this method returns a {@link Solution}:
  • *
      *
    • either the resolution stops eagerly du to a stop criterion and the solution is the best found so far but not * necessarily the optimal one,
    • *
    • or it is the optimal one.
    • *
    *
*

* Basically, this method runs the following instructions: *

*

     *     {@code
     *     model.setObjective(maximize, objective);
     *     Solution s = new Solution(model);
     *     while (model.getSolver().solve()) {
     *          s.record();
     *     }
     *     return model.getSolver().isFeasible() == ESat.TRUE ? s : null;
     *     }
     * 
*

* Note that all variables will be recorded * * @param objective integer variable to optimize * @param maximize set to true to solve a maximization problem, set to false to solve a minimization * problem. * @param stop optional criterion to stop the search before finding all/best solution * @return

    *
  • null if the problem has no solution or a stop criterion stops the search before finding a * first solution
  • *
  • a {@link Solution} if at least one solution has been found. The solution is proven to be optimal if no * stop criterion stops the search.
  • *
*/ default Solution findOptimalSolution(IntVar objective, boolean maximize, Criterion... stop) { ref().getModel().setObjective(maximize, objective); ref().addStopCriterion(stop); Solution s = new Solution(ref().getModel()); while (ref().solve()) { s.record(); } ref().removeStopCriterion(stop); return ref().isFeasible() == ESat.TRUE ? s : null; } /** * Attempt to find the solution that optimizes the mono-objective problem defined by * a unique objective variable and an optimization criteria, then finds and stores all optimal solution. * Searching for all optimal solutions is only triggered if the first search is complete. * This method works as follow: *
    *
  1. It finds and prove the optimum
  2. *
  3. It resets the search and enumerates all solutions of optimal cost
  4. *
* Note that the returned list can be empty. *
    *
  • If the method returns an empty list:
  • *
      *
    • * either a stop criterion (e.g., a time limit) stops the search before any solution has been found, *
    • *
    • * or no solution exists for the problem (i.e., over-constrained). *
    • *
    *
  • if the method returns a list with at least one element in it:
  • *
      *
    • either the resolution stops eagerly du to a stop criterion before finding all solutions,
    • *
    • or all optimal solutions have been found.
    • *
    *
*

* This method runs the following instructions: *

     *     {@code
     *     ref().findOptimalSolution(objective, maximize, stop);
     *     if (!ref().isStopCriterionMet()  &&
     *          model.getSolver().getMeasures().getSolutionCount() > 0) {
     *         int opt = _model.getSolver().getObjectiveManager().getBestSolutionValue().intValue();
     *         model.getSolver().reset();
     *         model.clearObjective();
     *         model.arithm(objective, "=", opt).post();
     *         return findAllSolutions();
     *     } else {
     *          return Collections.emptyList();
     *     }
     *     }
     * 
*

* Note that all variables will be recorded * * @param objective the variable to optimize * @param maximize set to true to solve a maximization problem, * set to false to solve a minimization problem. * @param stop optional criterion to stop the search before finding all/best solution * @return a list that contained the solutions found. */ default List findAllOptimalSolutions(IntVar objective, boolean maximize, Criterion... stop) { ref().addStopCriterion(stop); boolean defaultS = ref().getSearch() == null;// best bound (in default) is only for optim ref().findOptimalSolution(objective, maximize); if (!ref().isStopCriterionMet() && ref().getSolutionCount() > 0) { ref().removeStopCriterion(stop); int opt = ref().getObjectiveManager().getBestSolutionValue().intValue(); ref().reset(); ref().getModel().clearObjective(); Constraint forceOptimal = ref().getModel().arithm(objective, "=", opt); forceOptimal.post(); if (defaultS) ref().setSearch(Search.defaultSearch(ref().getModel()));// best bound (in default) is only for optim List solutions = findAllSolutions(stop); ref().getModel().unpost(forceOptimal); return solutions; } else { ref().removeStopCriterion(stop); return Collections.emptyList(); } } /** * Attempt to find the solution that optimizes the mono-objective problem defined by a unique objective variable and * an optimization criteria, then finds and stores all optimal solution. This method works as follow: *

    *
  1. It finds and prove the optimum
  2. *
  3. It resets the search and enumerates all solutions of optimal cost
  4. *
* Note that the returned list can be empty. *
    *
  • If the method returns an empty list:
  • *
      *
    • either a stop criterion (e.g., a time limit) stops the search before any solution has been found,
    • *
    • or no solution exists for the problem (i.e., over-constrained).
    • *
    *
  • if the method returns a list with at least one element in it:
  • *
      *
    • either the resolution stops eagerly du to a stop criterion before finding all solutions,
    • *
    • or all optimal solutions have been found.
    • *
    *
*

* Basically, this method runs the following instructions: *

*

     *     {@code
     *     ref().findOptimalSolution(objective, maximize);
     *     if (model.getSolver().getMeasures().getSolutionCount() > 0) {
     *         int opt = _model.getSolver().getObjectiveManager().getBestSolutionValue().intValue();
     *         model.getSolver().reset();
     *         model.clearObjective();
     *         model.arithm(objective, "=", opt).post();
     *         return findAllSolutions();
     *     } else {
     *          return Collections.emptyList();
     *     }
     *     }
     * 
*

* Note that all variables will be recorded * * @param objective the variable to optimize * @param maximize set to true to solve a maximization problem, set to false to solve a minimization * problem. * @param stop optional criterion to stop the search before finding all/best solution * @return a list that contained the solutions found. */ default Stream streamOptimalSolutions(IntVar objective, boolean maximize, Criterion... stop) { ref().addStopCriterion(stop); boolean defaultS = ref().getSearch() == null;// best bound (in default) is only for optim ref().findOptimalSolution(objective, maximize); if (!ref().isStopCriterionMet() && ref().getSolutionCount() > 0) { ref().removeStopCriterion(stop); int opt = ref().getObjectiveManager().getBestSolutionValue().intValue(); ref().reset(); ref().getModel().clearObjective(); Constraint forceOptimal = ref().getModel().arithm(objective, "=", opt); forceOptimal.post(); ref().getModel().getEnvironment().save(() -> ref().getModel().unpost(forceOptimal)); if (defaultS) ref().setSearch(Search.defaultSearch(ref().getModel()));// best bound (in default) is only for optim /*CPRU cannot infer type arguments for java.util.Spliterator*/ Spliterator it = new Spliterator() { @Override public boolean tryAdvance(Consumer action) { if (ref().solve()) { action.accept(new Solution(ref().getModel()).record()); return true; } ref().getModel().unpost(forceOptimal); ref().removeStopCriterion(stop); return false; } @Override public Spliterator trySplit() { return null; } @Override public long estimateSize() { return Long.MAX_VALUE; } @Override public int characteristics() { return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.CONCURRENT; } }; return StreamSupport.stream(it, false); } else { ref().removeStopCriterion(stop); return Stream.empty(); } } /** * Attempts optimize the value of the objectives variable w.r.t. to an optimization criteria. Finds and stores * all optimal solution. Note that the returned list can be empty. *

    *
  • If the method returns an empty list:
  • *
      *
    • either a stop criterion (e.g., a time limit) stops the search before any solution has been found,
    • *
    • or no solution exists for the problem (i.e., over-constrained).
    • *
    *
  • if the method returns a list with at least one element in it:
  • *
      *
    • either the resolution stops eagerly du to a stop criterion before finding all solutions,
    • *
    • or all optimal solutions have been found.
    • *
    *
* Basically, this method runs the following instructions: *

*

     * {@code
     * ParetoMaximizer pareto = new ParetoMaximizer(maximize, objectives);
     * 	while (ref().solve()) {
     * 		pareto.onSolution();
     *    }
     * 	return pareto.getParetoFront();
     * }
     * 
*

* Note that all variables will be recorded * * @param objectives the array of variables to optimize * @param maximize set to true to solve a maximization problem, set to false to solve a minimization * problem. * @param stop optional criteria to stop the search before finding all/best solution * @return a list that contained the solutions found. */ default List findParetoFront(IntVar[] objectives, boolean maximize, Criterion... stop) { ref().addStopCriterion(stop); ParetoMaximizer pareto = new ParetoMaximizer( Stream.of(objectives).map(o -> maximize?o:ref().getModel().intMinusView(o)).toArray(IntVar[]::new) ); Constraint c = new Constraint("PARETO", pareto); c.post(); while (ref().solve()) { pareto.onSolution(); } ref().removeStopCriterion(stop); ref().getModel().unpost(c); return pareto.getParetoFront(); } /** * Attempts optimize the value of the objectives variable w.r.t. to an optimization criteria. * Finds and stores the optimal solution, if any. * Moreover, the objective variables are ordered wrt their significance. * The first objective variable is more significant or equally significant to the second one, * which in turn is more significant or equally significant to the third one, etc. * On an optimal solution of a maximization problem, the first variable is maximized, then the second one is maximized, etc. *

* Note that if a stop criteria stops the search eagerly, no optimal solution may have been found. * In that case, the best solution, if at least one has been found, is returned. *

* Note that all variables will be recorded * * @param objectives the list of objectives to find the optimal. A solution o1..on is optimal if lexicographically better than * any other correct solution s1..sn * @param maximize to maximize the objective, false to minimize. * @param stop stop criterion are added before search and removed after search. * @return A solution with the optimal objectives value, null if no solution exists or search was stopped before a * solution could be found. If null, check if a criterion was met to find out was caused the null. */ default Solution findLexOptimalSolution(IntVar[] objectives, boolean maximize, Criterion... stop) { if (objectives == null || objectives.length == 0) { return findSolution(stop); } ref().addStopCriterion(stop); Solution sol = null; Constraint clint = null; UpdatablePropagator plint = null; // 1. copy objective variables and transform it if necessary IntVar[] mobj = new IntVar[objectives.length]; for (int i = 0; i < objectives.length; i++) { mobj[i] = maximize ? ref().getModel().intMinusView(objectives[i]) : objectives[i]; } // 2. try to find a first solution while (ref().solve()) { if (sol == null) { sol = new Solution(ref().getModel()); } sol.record(); // 3. extract values of each objective int[] bestFound = new int[objectives.length]; for (int vIdx = 0; vIdx < objectives.length; vIdx++) { bestFound[vIdx] = sol.getIntVal(objectives[vIdx]) * (maximize ? -1 : 1); } // 4. either update the constraint, or declare it if first solution if (plint != null) { plint.update(bestFound, true); } else { plint = new PropLexInt(mobj, bestFound, true); clint = new Constraint("lex objectives", (Propagator) plint); clint.post(); } } if (clint != null) { ref().getModel().unpost(clint); } ref().removeStopCriterion(stop); return sol; } /** * Calling this method attempts to solve an optimization problem with the following strategy: *

    *
  • Phase 1: As long as solutions are found, * it imposes that {@code bounded} is bounded is member of {@code bounder}
  • *
  • Phase 2:When no solution can be found or {@code limitPerAttempt} is reached: *
      *
    1. the objective best value is recorded
    2. *
    3. {@code bounded} is bounded out of last bounds provided by {@code bounder}
    4. *
    5. last bounds provided by {@code bounder} are then relaxed using {@code boundsRelaxer}
    6. *
    7. the search is then reset (calling {@link Solver#reset()}
    8. *
    9. if {@code stopCriterion} returns {@code false}, go back to Phase 1
    10. *
    *
  • Reset the search (calling {@link Solver#reset()}
  • *
  • Update the objective variable with the best value found so far and quit.
  • * *
*

* Note that it is required that {@code bounded} is the objective variable. *

*

The call to {@link Solver#reset()} removes any limits declared, * that is why {@code limitPerAttempt} can be needed. It will be re-declared upon any attempt. *

*

* To make sure the solving loop ends, it is possible to declare a {@code stopCriterion} which gives as * parameter the current number of attempts done so far. *

*

{@code onSolution} makes possible to do something on a solution, for instance recording it.

* *

Example of usage: * *

 {@code
     *     Solution solution = new Solution(model);
     *     model.getSolver().findOptimalSolutionWithBounds(
     *                 minLoad,
     *                 () -> new int[]{minLoad.getValue() * 2, 1000},
     *                 (i, b) -> i, // always restart from initial bounds
     *                 () -> model.getSolver().getNodeCount() > 10_000, // 10_000 nodes per attempt
     *                 r -> r > 1 && model.getSolver().getNodeCount() == 0, // run at least twice then stop on trivial unsatisfaction
     *                 solution::record  // record solutions
     *         )}
*

*

* This strategy should be used when it is easy to find a solution, but quite hard to the optimal solution. * Using this strategy with a sharp but accurate bounder strategy is expected to reduce drastically the search space * at the expense of the completeness of the exploration. * Indeed, a too optimistic bound may result in a dead-end, that's why a relaxation is applied, * to allow completeness back even if it is not required. *

*

* Since {@link Solver#reset()} is called, limits might not be respected, that's why a * function {@code stopCriterion} parametrized with the number of attempts is needed. *

* * @param bounded the variable to bound, may be different from the declared objective variable * @param bounder the value to bound the variable with * @param boundsRelaxer relaxation function, take init bounds and last bounds found as parameters and return relaxed bounds * @param stopCriterion function that {@code true} when conditions are met to stop this strategy. * @param onSolution instruction to execute when a solution is found (for instance, solution recording) * @return {@code true} if at least one solution has been found, {@code false} otherwise. * @implNote If the given problem is a satisfaction problem, calling this method will do nothing and return false. */ @SuppressWarnings({"unchecked"}) default boolean findOptimalSolutionWithBounds(IntVar bounded, Supplier bounder, BiFunction boundsRelaxer, Criterion limitPerAttempt, IntPredicate stopCriterion, Runnable onSolution) { if (!ref().getObjectiveManager().isOptimization()) return false; // Record initial bounds int[] initBounds = new int[]{bounded.getLB(), bounded.getUB()}; // Prepare the cut IntIterableRangeSet interval = new IntIterableRangeSet(initBounds[0], initBounds[1]); Member cut = new Member(bounded, interval); UpdatablePropagator prop = (UpdatablePropagator) cut.getPropagator(0); // Prepare opposite cut IntIterableRangeSet oppinterval = interval.duplicate(); oppinterval.flip(initBounds[0] - 1, initBounds[1] + 1); NotMember oppcut = new NotMember(bounded, oppinterval); UpdatablePropagator oppprop = (UpdatablePropagator) oppcut.getPropagator(0); cut.post(); oppcut.post(); boolean found = false; int objective; int[] bounds = initBounds; int run = 0; do { run++; // set the limit, which will be deleted on reset() ref().limitSearch(limitPerAttempt); while (ref().solve()) { bounds = bounder.get(); interval.retainBetween(bounds[0], bounds[1]); prop.update(interval, true); onSolution.run(); found = true; } objective = ref().getObjectiveManager().getBestSolutionValue().intValue(); oppinterval.addAll(interval); oppprop.update(oppinterval, false); ref().reset(); ref().getObjectiveManager().updateBestSolution(objective); bounds = boundsRelaxer.apply(initBounds, bounds); interval.clear(); interval.addBetween(bounds[0], bounds[1]); prop.update(interval, false); } while (!stopCriterion.test(run)); ref().reset(); ref().getModel().unpost(cut); ref().getModel().unpost(oppcut); ref().getObjectiveManager().updateBestSolution(objective); return found; } /** * Explore the model, calling a {@link BiConsumer} for each {@link Solution} with its corresponding {@link IMeasures}. *

* The {@link Solution} and the {@link IMeasures} provided by the Biconsumer are always the same reference, consider * either extracting values from them or copy them. See {@link IMeasures} and {@link Solution#copySolution()} *

*

* The consumer and the criterion should not be linked ; instead use {@link ACounter} sub-classes. *

*

* Note that all variables will be recorded * * @param cons the consumer of solution and measure couples * @param stop optional criterions to stop the search before finding all/best solution */ default void eachSolutionWithMeasure(BiConsumer cons, Criterion... stop) { ref().addStopCriterion(stop); Solution s = new Solution(ref().getModel()); while (ref().solve()) { cons.accept(s.record(), ref().getMeasures()); } ref().removeStopCriterion(stop); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy