OpenTS expects an objective/constraint penalties function to be
able to evaluate a solution's worth. OpenTS will pass the
ObjectiveFunction a Solution and optionally pass a proposed Move.
If the passed move is not null, then the evaluation should consider
the effect that executing this move on the solution would have before
evaluating it.
If you can use some sort of incremental evaluation
technique, this will save you time. If you must operate on the solution
to actually calculate its value, you must return the solution to its
original state before the method returns.
It may be helfpul to give yourself some helper methods in your
ObjectiveFunction to separate the incremental evaluation from the
absolute evaluation. You might setup your evaluate(...)
method like this:
It May Help to Separate Your Logic:
public double[] evaluate( Solution soln, Move proposedMove )
{
if( proposedMove == null )
return evaluateAbsolutely( soln );
else
return evaluateIncrementally( soln, proposedMove );
}
Then you can deal with the two pieces of logic separately.
If you cannot incrementally evaluate your solution but are
forced to modify the Solution object first, make sure
your Move object has a way to undo its operation:
Always Return the Solution to its Original State:
private double[] evaluateIncrementally( Solution soln, Move proposedMove )
{
proposedMove.operateOn( soln );
double[] val = evaluateAbsolutely( soln );
proposedMove.undoOperation( soln );
return val;
}
It is a common mistake to try to reuse arrays of doubles
and mess it up (it's possible, but you have to be careful).
When you're first writing your code and aren't fine tuning
it to this level, it's easiest to evaluate your objective
function values with scalars and then return a fresh array:
Avoid Reusing Arrays:
public double[] evaluate( Solution soln, Move proposedMove )
{
double val1 = ...;
double val2 = ...;
return new double[]{ val1, val2 };
}
The array of returned values will later be compared
lexicographically in the classic "goal-programming" style.
If instead you want some goals to overpower higher goals,
use the style of weighting the levels with appropriate values.
In this trivial example, suppose your ObjectiveFunction
returns an array of three doubles
in an assignment problem where the first value in the array (Goal #1)
is the number of customers in a day who wait longer than 10 minutes
on hold waiting for one of the available Tech Support personnel
(objective value #2: minimize hiring), and the third value is
the total cost of the operation.
Solution Pair A |
|
Soln 1 | Soln 2 |
Goal #1 | 33 | 6 |
Goal #2 | 7 | 12 |
Goal #3 | 24000 | 33000 |
|
|
Winner |
|
Solution Pair B |
|
Soln 1 | Soln 2 |
Goal #1 | 17 | 17 |
Goal #2 | 8 | 9 |
Goal #3 | 26000 | 26000 |
|
Winner |
|
|
We see in the tables above that with the lexicographical comparison
of ObjectiveFunction values, that Goal #1 is absolutely more important
than the rest of the goals which is why, in Solution Pair A, Soln 2
beats Soln 1 despite Soln 1's lower cost and number of people hired.
In Solution Pair B we see that there's a tie on Goal #1, so OpenTS
compares the next goal and declares Soln 1 the winner for hiring
fewer people. If this goal had been a tie, OpenTS would have moved
on to the next goal.
Although all numbers are stored and calculated as doubles,
they are cast to floats before being compared.
This helps avoid the problems where two "equal" values
stored as doubles differ by 0.000000000001 or some
ridiculously small value.
When OpenTS considers whether or not some solution X is better than
some other solution Y, the answer is No if the two objective function
values result in ties for all of the goals.