com.exigen.ie.constrainer.GoalFastMinimize Maven / Gradle / Ivy
package com.exigen.ie.constrainer;
import com.exigen.ie.constrainer.impl.ConstraintExpLessValue;
import com.exigen.ie.tools.Log;
///////////////////////////////////////////////////////////////////////////////
/*
* Copyright Exigen Group 1998, 1999, 2000
* 320 Amboy Ave., Metuchen, NJ, 08840, USA, www.exigengroup.com
*
* The copyright to the computer program(s) herein
* is the property of Exigen Group, USA. All rights reserved.
* The program(s) may be used and/or copied only with
* the written permission of Exigen Group
* or in accordance with the terms and conditions
* stipulated in the agreement/contract under which
* the program(s) have been supplied.
*/
///////////////////////////////////////////////////////////////////////////////
/**
* An implementation of a {@link Goal} that finds a solution which minimizes
* a constrained integer variable called the "cost".
*
* This goal uses the search goal provided by the caller, and expects
* that this goal instantiates the cost every time when a solution is found.
*
* To search for a solution which provides the minimal cost,
* GoalFastMinimize finds a solution, posts the constraint that the
* cost variable should be less then the found cost and continues the search.
*
* In contrast with GoalMinimize, when a solution is found GoalFastMinimize
* does not restore all decision variables, but continues the search just with
* a more strict constraint to the cost variable.
*
* When there are no solutions anymore, the latest found solution is the optimal one.
*
* By default, GoalFastMinimize calculates the optimal solution twice.
*
* If the caller's search goal saves every found solution itself, the caller may
* specify the mode "goal_saves_solution" to prevent the duplicate calculation
* at the end of the search.
*
* @see GoalMinimize
*/
public class GoalFastMinimize extends GoalImpl
{
private IntExp _cost;
private int _best_cost;
private Goal _goal_find_solution;
private ConstraintExpLessValue _constraint_limit_cost;
private int _number_of_solutions;
private boolean _trace;
private Object _client_tracer;
private boolean _goal_saves_solution;
private ChoicePointLabel _rootLabel;
private int _initial_cost_min;
private int _initial_cost_max;
private int _cost_search_max;
/**
* Constructor with a given generation goal, cost expression, trace flag,
* and save_solution flag.
*
* Use "goal" to minimize "cost".
*
* If ("trace" = true)
, after each successful iteration, solution# and the
* cost value will be printed.
*
* If ("goal_saves_solution" = true)
, the optimal solution (if any)
* would not be calculated twice: it means the "goal" should save
* the value of decision variables and the cost.
*/
public GoalFastMinimize(Goal goal, IntExp cost, boolean trace, boolean goal_saves_solution)
{
super(cost.constrainer(),"");// "Use "+goal.name()+ " to minimize("+cost.name()+")");
_goal_find_solution = goal;
_cost = cost;
_constraint_limit_cost = new ConstraintExpLessValue(_cost, _cost.max()+1);
_number_of_solutions = 0;
_trace = trace;
_client_tracer = null;
_goal_saves_solution = goal_saves_solution;
_rootLabel = constrainer().createChoicePointLabel();
_initial_cost_min = _cost.min();
_initial_cost_max = _cost.max();
// _cost_search_max = _initial_cost_min; // optimistic
_cost_search_max = _initial_cost_max; // pessimistic
}
public int numberOfSolutions() { // Added by JF
return _number_of_solutions;
}
/**
* Increase _cost_search_max extending the interval
* [_initial_cost_min.._cost_search_max].
*/
boolean increaseCostSearchMax()
{
if(_cost_search_max > _initial_cost_max)
return false;
if(_cost_search_max < _initial_cost_min + 1)
{
_cost_search_max = _initial_cost_min + 1;
}
else
{
_cost_search_max = _initial_cost_min + (_cost_search_max-_initial_cost_min)*2;
if(_cost_search_max > _initial_cost_max + 1)
_cost_search_max = _initial_cost_max + 1;
}
return true;
}
/**
* Constructor with a given generation goal, cost expression, trace flag.
* No tracing information will be printed.
*/
public GoalFastMinimize(Goal goal, IntExp cost, boolean goal_saves_solution)
{
this(goal,cost,false, goal_saves_solution);
}
/**
* Constructor with a given generation goal, cost expression,
* and tracer object.
* After each successful iteration, tracer.toString() will be printed.
*/
public GoalFastMinimize(Goal goal, IntExp cost, Object tracer)
{
this(goal,cost,false);
_client_tracer = tracer;
}
/**
* Constructor with a given generation goal, and cost expression.
* Other settings: trace = false and goal_saves_solution = false.
*/
public GoalFastMinimize(Goal goal, IntExp cost)
{
this(goal,cost,false,false);
}
/**
* Constructor with a given generation goal, cost expression, tracer object,
* and save_solution flag.
*/
public GoalFastMinimize(Goal goal, IntExp cost, Object tracer, boolean goal_saves_solution)
{
this(goal,cost,false,goal_saves_solution);
_client_tracer = tracer;
}
/**
* The implementation of the search algorithm.
*
* It sets labeled choice point from the goals FindAndImprove and AnalyzeAndSet.
*
* FindAndImprove is a loop that finds an optimal solution (if any solution exists).
* FindAndImprove always fails (the _cost can not be improved infinitely).
*
* AnalyzeAndSet do further job:
*
* - Checks if any solution was found and fails if not.
*
- If _goal_save_solution is true then it finishes.
*
- Otherwise the constraint _cost==_best_cost is posted and _goal_find_solution
* is executed to instantiate the optimal solution.
*
*/
public Goal execute() throws Failure
{
if(_number_of_solutions > 0
|| constrainer().isMaxNumberOfSolutionsReached(_number_of_solutions) // added in Mar-2024
|| constrainer().isTimeLimitExceeded())
return new AnalyzeAndSet(constrainer());
if(!increaseCostSearchMax())
constrainer().fail("GoalFastMinimize");
//System.out.println("Search with costSearchMax="+_cost_search_max);
return new GoalOr(new FindAndImprove(constrainer()),
this,
_rootLabel );
}
// public Goal execute() throws Failure
// {
// return new GoalOr(new FindAndImprove(constrainer()),
// new AnalyzeAndSet(constrainer()),
// _rootLabel );
// }
/**
* Returns a String representation of this goal.
*/
public String toString()
{
return "Use "+_goal_find_solution.name()+ " to minimize("+_cost.name()+")";
}
/**
* Activates _constraint_limit_cost and organizes the optimization loop
* using _goal_find_solution and GoalBacktrack.
*/
class FindAndImprove extends GoalImpl
{
FindAndImprove(Constrainer C)
{
super(C,"FindAndImprove");
}
public Goal execute() throws Failure
{
// activate _constraint_limit_cost
_constraint_limit_cost.resetValue(_cost_search_max);
_constraint_limit_cost.post();
return new GoalAnd( _goal_find_solution,
new GoalBacktrack(constrainer()) );
}
};
/**
* This goal is executed after an optimal solution is found or no solution exists.
* The constrainer state is as it was before GoalFastMinimize.execute().
*/
class AnalyzeAndSet extends GoalImpl
{
AnalyzeAndSet(Constrainer C)
{
super(C,"AnalyzeAndSet");
}
public Goal execute() throws Failure
{
// check if the any solution was found
if (_number_of_solutions <= 0)
constrainer().fail();
//Debug.on();Debug.print("Found solution with the cost = "+_best_cost);Debug.off();
if(_goal_saves_solution)
return null;
// === Changed: Added by OR to catch previously found solutions when time limit is exceeded
if (constrainer().isTimeLimitExceeded())
return null;
// post the constraint _cost == _best_cost
_cost.equals(_best_cost).post();
// find the optimal solution
return _goal_find_solution;
}
}
/**
* This goal backtracks while _cost GE _best_cost.
*
* Then it sets _best_cost as a new value in _constraint_limit_cost.
* In this case the search continues with the new limit on the cost.
*
* This goal fails if backtrack can not satisfy _cost LT _best_cost.
* This means that the is no better solution.
* In this case GoalAnalyzeAndSet will be executed as an alternate
* goal for the "_rootLabel" choice point.
*/
class GoalBacktrack extends GoalImpl
{
public GoalBacktrack(Constrainer c)
{
super(c,"GoalBacktrack");
}
/**
* Called when a solution (first or next) is found.
* Saves the current value of the cost, increments _number_of_solutions,
* and performs tracing.
*/
void fixFoundSolution() throws Failure
{
_best_cost = _cost.value();
_number_of_solutions++;
if (_trace)
{
Log.info("Solution "+_number_of_solutions+": cost="+_cost.value());
}
if (_client_tracer != null)
{
Log.info(_client_tracer.toString());
}
}
/**
* violated() for the constraint: _cost < _best_cost
*/
boolean violated()
{
return _cost.min() >= _best_cost;
}
public Goal execute() throws Failure
{
fixFoundSolution();
ChoicePointLabel lbl = constrainer().currentChoicePointLabel();
// backtrack while _cost >= _best_cost
while( ((lbl==null )||(!lbl.equals(_rootLabel))) && violated() )
{
if(!constrainer().backtrack()){
throw new RuntimeException("Internal error in" + this);
}
lbl = constrainer().currentChoicePointLabel();
if (_trace)
{
Log.info(" Backtrack: cost"+_cost.domainToString()+" best_cost="+_best_cost);
}
}
// fail if _cost >= _best_cost
if(violated())
constrainer().fail("GoalBacktrack");
// set _best_cost as a new limit in _constraint_limit_cost
_constraint_limit_cost.resetValue(_best_cost);
return null;
}
} // GoalBacktrack
} // ~GoalFastMinimize