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

org.chocosolver.solver.constraints.real.IbexHandler 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.constraints.real;

import gnu.trove.list.TDoubleList;
import gnu.trove.list.array.TDoubleArrayList;
import gnu.trove.map.hash.TObjectIntHashMap;
import org.chocosolver.solver.Cause;
import org.chocosolver.solver.ICause;
import org.chocosolver.solver.constraints.Propagator;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.RealVar;
import org.chocosolver.solver.variables.Variable;
import org.chocosolver.util.tools.VariableUtils;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;

import static java.lang.Math.ceil;
import static java.lang.Math.floor;

/**
 * Utility class to build the right Ibex instance.
 *
 * The main idea is to delegate filtering of full real constraints and partially real constraints to
 * Ibex.
 *
 * Because Ibex does not support removing a contractor, this class makes up for this lack.
 *
 * Anytime a contractor has to be removed from Ibex, the instance is killed and recreated lazily.
 * 

Project: choco-solver. * * @author Charles Prud'homme * @since 29/09/2017. */ public class IbexHandler { private static final Pattern p1 = Pattern.compile("\\{_"); /** * Ibex instances. */ private Ibex ibex; /** * Store for each function declared, the index of the corresponding contractor in Ibex. Since * Ibex instance is lazily created, the mapping can be dynamically updated. */ private final TObjectIntHashMap ibexCtr = new TObjectIntHashMap<>(16, 0.5f, -1); /** * Store, for each function declared, indices of the associated variables. */ private final TObjectIntHashMap ibexVar = new TObjectIntHashMap<>(16, 0.5f, -1); /** * List of all variables known by Ibex */ private final List vars = new ArrayList<>(); /** * Each boolean indicates whether a variable is integral or not. */ private final TDoubleList precisions = new TDoubleArrayList(); /** * Each boolean indicates whether a variable is integral or not. */ private double[] domains; /** * Indicates whether the constraint store changed */ private boolean hasChanged = false; /** * Indicates whether the resolution status 0: not started, 1: started, 2: search over. */ private byte startSolve = 0; /** * To optimize running time ibex only considers domain contractions greater than * a given ratio during constraint propagation. The default value is 1% (0.01). * See issue #653. * * Given: x = [0.0, 100.0], y = [0.5,0.5] and CSTR(x > y) * - When the ratio is 1% (0.01) bounds of X are kept as [0.0, 100.0] * because it's contraction is less than 1%. * - When the ratio is 0.1% (0.001) bounds of X are update to [0.5, 100.0] * because it's contraction is greater than 0.1%. * * @implNote Supported since ibex-java version 1.2.0 */ private double contractionRatio = Ibex.RATIO; /** * To improve the running time, ibex changes the rounding system for double values * during contraction. In Linux/MACOS environments it leads to different results in * calculations like `Math.pow(10, 6)`, see issue #740. * * When preserveRounding is defined as true, after calling ibex, the default Java * rounding system is restored. At the price of a little loss of efficiency. * * @implNote Supported since ibex-java version 1.2.0 */ private boolean preserveRounding = Ibex.PRESERVE_ROUNDING; /** * build Ibex instance. * Since Ibex' parser is not thread safe, this method is synchronized * @param ibex ibex instance to build. */ private synchronized static void build(Ibex ibex){ if(!ibex.build()){ throw new SolverException("Malformed Ibex function"); } } /** * Add new functions to Ibex * * @param prop propagator that manages the function */ public void declare(RealPropagator prop) { ibexCtr.put(prop, -1); hasChanged = true; } /** * Remove 'prop' and thus the function it managed. * * @param prop propagator that manages the function */ public void remove(RealPropagator prop) { ibexCtr.remove(prop); hasChanged = true; } /** * Call Ibex to contract the function managed by 'prop' * * @param prop propagator to propagate */ void contract(RealPropagator prop) throws ContradictionException { Ibex mIbex = getIbexInstance(); boolean filter; int reif; do { filter = false; extractDomains(); reif = getReif(prop); int result; if (contractionRatio == Ibex.RATIO) { // Compatibility with ibex version previous to 2.8.8 result = mIbex.contract(ibexCtr.get(prop), domains, reif); } else { // What's the best way to inform the user to update to ibex 2.8.8? result = mIbex.contract(ibexCtr.get(prop), domains, reif, contractionRatio); } switch (result) { case Ibex.FAIL: // "Ibex failed" if (reif == Ibex.FALSE_OR_TRUE) { prop.reified.setToFalse(prop); filter = true; } else if (reif == Ibex.TRUE) { prop.fails(); } break; case Ibex.CONTRACT: assert reif != Ibex.FALSE_OR_TRUE; injectDomains(prop); filter = true; break; case Ibex.ENTAILED: if (reif == Ibex.FALSE_OR_TRUE) { prop.reified.setToTrue(prop); filter = true; } else if (reif == Ibex.FALSE) { prop.fails(); } else { injectDomains(prop); // Validated by G. Chabert: // When contracting returns ENTAIL, Ibex does not passivate the contractor. // Indeed, even if all combinations are satisfiable the contractor may filter // in the future. This happens when the search is delegated to Ibex, // when calling {@link Ibex#start_solve(double[])}. // so, we can set passivate the propagator in choco. } break; case Ibex.NOTHING: break; default: throw new SolverException("Bad return " + result); } } while (filter); } // NOT TESTED: it has to deal with environment backup, like a move. private void start_solve() { startSolve = 0; Ibex mIbex = getIbexInstance(); extractDomains(); int result = mIbex.start_solve(domains); switch (result) { case Ibex.STARTED: // success, a solution was found break; case Ibex.DISCRETE_NOT_INSTANCIATED: case Ibex.NOT_BUILT: case Ibex.BAD_DOMAIN: throw new IllegalStateException("Ibex cannot initialize the solving, error #" + result + " is thrown"); } } public boolean nextSolution(boolean reinit){ if (startSolve == 0 || (startSolve == 2 && reinit)) { this.start_solve(); startSolve = 1; } if (startSolve == 1) { Ibex mIbex = getIbexInstance(); int result = mIbex.next_solution(domains); switch (result) { case Ibex.SOLUTION: case Ibex.UNKNOWN: return true; case Ibex.SEARCH_OVER: startSolve = 2; return false; case Ibex.NOT_STARTED: case Ibex.BAD_DOMAIN: throw new IllegalStateException("Ibex cannot terminate the solving, error #" + result + " is thrown"); } } return false; } public void injectDomain() throws ContradictionException { injectDomains(Cause.Null); } public double getContractionRatio() { return contractionRatio; } /** * Defines the ratio that real domains must be contract by ibex * to compute the constraint. A contraction is considered as significant * when at least {@param ratio} of a domain has been reduced. * If the contraction is not meet, then it is considered as insufficient * and therefore ignored. * * @param ratio defines the ratio that a domains must be contract to * compute the constraint. */ public void setContractionRatio(double ratio) { this.contractionRatio = ratio; } public boolean isPreserveRounding() { return preserveRounding; } /** * If preserve_rounding is true, Ibex will restore the default * Java rounding method when coming back from Ibex, which is * transparent for Java but causes a little loss of efficiency. * * @param preserveRounding */ public void setPreserveRounding(boolean preserveRounding) { this.preserveRounding = preserveRounding; } private Ibex getIbexInstance() { if (hasChanged && ibex != null) { ibex.release(); ibex = null; } if (ibex == null) { createInstance(); } return ibex; } /** * Extract domains of all variables (required by Ibex) */ private void extractDomains() { for (int i = 0; i < vars.size(); i++) { if (VariableUtils.isReal(vars.get(i))) { RealVar rvar = (RealVar) vars.get(i); domains[2 * i] = rvar.getLB(); domains[2 * i + 1] = rvar.getUB(); } else { IntVar ivar = (IntVar) vars.get(i); domains[2 * i] = ivar.getLB(); domains[2 * i + 1] = ivar.getUB(); } } } /** * Extract domains of all variables (required by Ibex) * * @param cause propagator that trigger the filtering * @throws ContradictionException in case of a failure */ private void injectDomains(ICause cause) throws ContradictionException { for (int i = 0; i < vars.size(); i++) { if (VariableUtils.isReal(vars.get(i))) { RealVar rvar = (RealVar) vars.get(i); rvar.updateBounds(domains[2 * i], domains[2 * i + 1], cause); } else { IntVar ivar = (IntVar) vars.get(i); ivar.updateBounds((int) ceil(domains[2 * i]), (int) floor(domains[2 * i + 1]), cause); } } } /** * Compute the reification indicator of 'prop' * * @param prop propagator to filter * @return {@link Ibex#TRUE} if 'prop' is not reified or reified to 'true', {@link Ibex#FALSE} * if 'prop' is reified to 'false' or {@link Ibex#FALSE_OR_TRUE} if 'prop' reified to * 'undefined' */ private int getReif(RealPropagator prop) { if (prop.reified == null) { return Ibex.TRUE; } else if (prop.reified.isInstantiated()) { return prop.reified.isInstantiatedTo(1) ? Ibex.TRUE : Ibex.FALSE; } else { return Ibex.FALSE_OR_TRUE; } } /** * Create the instance of Ibex */ private synchronized void createInstance() { RealPropagator[] props = ibexCtr.keySet() .stream() .sorted(Comparator.comparingInt(Propagator::getId)) .toArray(RealPropagator[]::new); // first pass to collect variable vars.clear(); precisions.clear(); ibexVar.clear(); for (int i = 0; i < props.length; i++) { for (int j = 0; j < props[i].getNbVars() - (props[i].reified != null ? 1 : 0); j++) { Variable var = props[i].getVar(j); if (!ibexVar.contains(var)) { ibexVar.put(var, vars.size()); vars.add(var); precisions.add(VariableUtils.isReal(var) ? ((RealVar) var).getPrecision() : -1); } } } if (preserveRounding == Ibex.PRESERVE_ROUNDING) { // For backwards compatibility ibex = new Ibex(precisions.toArray()); } else { ibex = new Ibex(precisions.toArray(), preserveRounding); } int k = 0; // first pass to modify functions wrt to variables for (int i = 0; i < props.length; i++) { String fct = props[i].functions; for (int j = 0; j < props[i].getNbVars() - (props[i].reified != null ? 1 : 0); j++) { fct = fct.replaceAll( "\\{" + j + "\\}", "{_" + ibexVar.get(props[i].getVar(j)) + "}"); } ibexCtr.put(props[i], k++); ibex.add_ctr(p1.matcher(fct).replaceAll("{")); } domains = new double[vars.size() * 2]; hasChanged = false; // build is synchronized because Ibex parser is not thread safe IbexHandler.build(ibex); } @Override protected void finalize() throws Throwable { if (ibex != null) { ibex.release(); } super.finalize(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy