Free cookie consent management tool by TermsFeed Policy Generator

source: branches/OKBJavaConnector/ECJClient/src/ec/multiobjective/MultiObjectiveFitness.java @ 6703

Last change on this file since 6703 was 6152, checked in by bfarka, 14 years ago

added ecj and custom statistics to communicate with the okb services #1441

File size: 19.7 KB
Line 
1/*
2  Copyright 2006 by Sean Luke
3  Licensed under the Academic Free License version 3.0
4  See the file "LICENSE" for more information
5*/
6
7package ec.multiobjective;
8
9import java.io.*;
10import ec.util.DecodeReturn;
11import ec.util.Parameter;
12import ec.util.Code;
13import ec.Fitness;
14import ec.EvolutionState;
15import java.util.*;
16import ec.*;
17
18/*
19 * MultiObjectiveFitness.java
20 *
21 * Created: Tue Aug 10 20:27:38 1999
22 * By: Sean Luke
23 */
24
25/**
26 * MultiObjectiveFitness is a subclass of Fitness which implements basic
27 * multi-objective mechanisms suitable for being used with a variety of
28 * multi-objective selection mechanisms, including ones using pareto-optimality.
29 *
30 * <p>
31 * The object contains two items: an array of floating point values representing
32 * the various multiple fitnesses, and a flag (maximize) indicating whether
33 * higher is considered better. By default, isIdealFitness() always returns
34 * false; you might want to override that, though it'd be unusual -- what is the
35 * ideal fitness from the perspective of a pareto front?
36 *
37 * <p>
38 * The object also contains maximum and minimum fitness values suggested for the
39 * problem, on a per-objective basis. By default the maximum values are all 1.0
40 * and the minimum values are all 0.0, but you can change these. Note that
41 * maximum does not mean "best" unless maximize is true.
42 *
43 * <p>The class also contains utility methods or computing pareto dominance,
44 * Pareto Fronts and Pareto Front Ranks, and distance in multiobjective space.
45 * The default comparison operators use Pareto Dominance, though this is often
46 * overridden by subclasses.
47 *
48 * <p>The fitness() method returns the maximum of the fitness values, which is
49 * clearly nonsensical: you should not be using this method.
50 *
51 * <p>Subclasses of this class may add certain auxiliary fitness measures which
52 * are printed out by MultiObjectiveStatistics along with the multiple objectives.
53 * To have these values printed out, override the getAuxiliaryFitnessNames()
54 * and getAuxiliaryFitnessValues() methods.
55 *
56 * <p>
57 * <b>Parameters</b><br>
58 * <table>
59 * <tr>
60 * <td valign=top><i>base</i>.<tt>num-objectives</tt><br>
61 * (else)<tt>multi.num-objectives</tt><br>
62 * <font size=-1>int &gt;= 1</font></td>
63 * <td valign=top>(the number of fitnesses in the objectives array)</td>
64 * </tr>
65 *
66 * <tr>
67 * <td valign=top><i>base</i>.<tt>maximize</tt><br>
68 * <font size=-1> bool = <tt>true</tt> (default) or <tt>false</tt></font></td>
69 * <td valign=top>(are higher values considered "better"?)
70 * </table>
71 *
72 * <tr>
73 * <td valign=top><i>base</i>.<tt>max</tt><br>
74 * <font size=-1> float (<tt>1.0</tt> default)</font></td>
75 * <td valign=top>(maximum fitness value for all objectives)</table>
76 *
77 * <tr>
78 * <td valign=top><i>base</i>.<tt>max</tt>.<i>i</i><br>
79 * <font size=-1> float (<tt>1.0</tt> default)</font></td>
80 * <td valign=top>(maximum fitness value for objective <i>i</i>. Overrides the
81 * all-objective maximum fitness.)</table>
82 *
83 * <tr>
84 * <td valign=top><i>base</i>.<tt>min</tt><br>
85 * <font size=-1> float (<tt>0.0</tt> (default)</font></td>
86 * <td valign=top>(minimum fitness value for all objectives)</table>
87 *
88 * <tr>
89 * <td valign=top><i>base</i>.<tt>min</tt>.<i>i</i><br>
90 * <font size=-1> float = <tt>0.0</tt> (default)</font></td>
91 * <td valign=top>(minimum fitness value for objective <i>i</i>. Overrides the
92 * all-objective minimum fitness.)</table>
93 *
94 * <p>
95 * <b>Default Base</b><br>
96 * multi.fitness
97 *
98 * @author Sean Luke
99 * @version 1.1
100 */
101
102public class MultiObjectiveFitness extends Fitness
103    {
104    public static final String MULTI_FITNESS_POSTAMBLE = "[";
105    public static final String FITNESS_POSTAMBLE = "]";
106
107    /** parameter for size of objectives */
108    public static final String P_NUMOBJECTIVES = "num-objectives";
109
110    /** parameter for max fitness values */
111    public static final String P_MAXOBJECTIVES = "max";
112
113    /** parameter for min fitness values */
114    public static final String P_MINOBJECTIVES = "min";
115
116    /** Is higher better? */
117    public static final String P_MAXIMIZE = "maximize";
118
119    /** Desired maximum fitness values. By default these are 1.0. Shared. */
120    public float[] maxObjective;
121
122    /** Desired minimum fitness values. By default these are 0.0. Shared. */
123    public float[] minObjective;
124
125    /** The various fitnesses. */
126    protected float[] objectives; // values range from 0 (worst) to 1 INCLUSIVE
127    protected boolean maximize = true;
128
129    /** Returns auxilliary fitness value names to be printed by the statistics object.
130        By default, an empty array is returned, but various algorithms may override this to provide additional columns.
131    */
132    public String[] getAuxilliaryFitnessNames() { return new String[] { }; }
133
134    /** Returns auxilliary fitness values to be printed by the statistics object.
135        By default, an empty array is returned, but various algorithms may override this to provide additional columns.
136    */
137    public double[] getAuxilliaryFitnessValues() { return new double[] { }; }
138
139    public boolean isMaximizing()
140        {
141        return maximize;
142        }
143
144
145    public int getNumObjectives() { return objectives.length; }
146       
147    /**
148     * Returns the objectives as an array. Note that this is the *actual array*.
149     * Though you could set values in this array, you should NOT do this --
150     * rather, set them using setObjectives().
151     */
152    public float[] getObjectives()
153        {
154        return objectives;
155        }
156
157    public float getObjective(int i)
158        {
159        return objectives[i];
160        }
161
162    public void setObjectives(final EvolutionState state, float[] newObjectives)
163        {
164        if (newObjectives == null)
165            {
166            state.output.fatal("Null objective array provided to MultiObjectiveFitness.");
167            }
168        if (newObjectives.length != objectives.length)
169            {
170            state.output.fatal("New objective array length does not match current length.");
171            }
172        for (int i = 0; i < newObjectives.length; i++)
173            {
174            float _f = newObjectives[i];
175            if (_f == Float.POSITIVE_INFINITY || _f == Float.NEGATIVE_INFINITY || Float.isNaN(_f))
176                {
177                state.output.warning("Bad objective #" + i + ": " + _f + ", setting to worst value for that objective.");
178                if (maximize)
179                    newObjectives[i] = minObjective[i];
180                else
181                    newObjectives[i] = maxObjective[i];
182                }
183            }
184        objectives = newObjectives;
185        }
186
187    public Parameter defaultBase()
188        {
189        return MultiObjectiveDefaults.base().push(P_FITNESS);
190        }
191
192    public Object clone()
193        {
194        MultiObjectiveFitness f = (MultiObjectiveFitness) (super.clone());
195        f.objectives = (float[]) (objectives.clone()); // cloning an array
196
197        // note that we do NOT clone max and min fitness -- they're shared
198        return f;
199        }
200
201    /**
202     * Returns the Max() of objectives, which adheres to Fitness.java's protocol
203     * for this method. Though you should not rely on a selection or statistics
204     * method which requires this.
205     */
206    public float fitness()
207        {
208        float fit = objectives[0];
209        for (int x = 1; x < objectives.length; x++)
210            if (fit < objectives[x])
211                fit = objectives[x];
212        return fit;
213        }
214
215    /**
216     * Sets up. This must be called at least once in the prototype before
217     * instantiating any fitnesses that will actually be used in evolution.
218     */
219
220    public void setup(EvolutionState state, Parameter base)
221        {
222        super.setup(state, base); // unnecessary really
223
224        Parameter def = defaultBase();
225        int numFitnesses;
226
227        numFitnesses = state.parameters.getInt(base.push(P_NUMOBJECTIVES), def.push(P_NUMOBJECTIVES), 0);
228        if (numFitnesses <= 0)
229            state.output.fatal("The number of objectives must be an integer >= 1.", base.push(P_NUMOBJECTIVES), def.push(P_NUMOBJECTIVES));
230
231        maximize = state.parameters.getBoolean(base.push(P_MAXIMIZE), def.push(P_MAXIMIZE), true);
232
233        objectives = new float[numFitnesses];
234        maxObjective = new float[numFitnesses];
235        minObjective = new float[numFitnesses];
236
237        for (int i = 0; i < numFitnesses; i++)
238            {
239            // load default globals
240            minObjective[i] = state.parameters.getFloatWithDefault(base.push(P_MINOBJECTIVES), def.push(P_MINOBJECTIVES), 0.0f);
241            maxObjective[i] = state.parameters.getFloatWithDefault(base.push(P_MAXOBJECTIVES), def.push(P_MAXOBJECTIVES), 1.0f);
242
243            // load specifics if any
244            minObjective[i] = state.parameters.getFloatWithDefault(base.push(P_MINOBJECTIVES).push("" + i), def.push(P_MINOBJECTIVES).push("" + i), minObjective[i]);
245            maxObjective[i] = state.parameters.getFloatWithDefault(base.push(P_MAXOBJECTIVES).push("" + i), def.push(P_MAXOBJECTIVES).push("" + i), maxObjective[i]);
246           
247            // test for validity
248            if (minObjective[i] >= maxObjective[i])
249                state.output.error("For objective " + i + "the min fitness must be strictly less than the max fitness.");
250            }
251        state.output.exitIfErrors();
252        }
253
254    /**
255     * Returns true if this fitness is the "ideal" fitness. Default always
256     * returns false. You may want to override this.
257     */
258    public boolean isIdealFitness()
259        {
260        return false;
261        }
262
263    /**
264     * Returns true if I'm equivalent in fitness (neither better nor worse) to
265     * _fitness. The rule I'm using is this: If one of us is better in one or
266     * more criteria, and we are equal in the others, then equivalentTo is
267     * false. If each of us is better in one or more criteria each, or we are
268     * equal in all criteria, then equivalentTo is true.   Multiobjective optimization algorithms may
269     * choose to override this to do something else.
270     */
271
272    public boolean equivalentTo(Fitness _fitness)
273        {
274        MultiObjectiveFitness other = (MultiObjectiveFitness) _fitness;
275        boolean abeatsb = false;
276        boolean bbeatsa = false;
277
278        if (maximize != other.maximize)
279            throw new RuntimeException(
280                "Attempt made to compare two multiobjective fitnesses; but one expects higher values to be better and the other expectes lower values to be better.");
281        if (objectives.length != other.objectives.length)
282            throw new RuntimeException("Attempt made to compare two multiobjective fitnesses; but they have different numbers of objectives.");
283        if (maximize)
284            {
285            for (int x = 0; x < objectives.length; x++)
286                {
287                if (objectives[x] > other.objectives[x])
288                    abeatsb = true;
289                if (objectives[x] < other.objectives[x])
290                    bbeatsa = true;
291                if (abeatsb && bbeatsa)
292                    return true;
293                }
294            }
295        else
296            // lower is better
297            {
298            for (int x = 0; x < objectives.length; x++)
299                {
300                if (objectives[x] < other.objectives[x])
301                    abeatsb = true;
302                if (objectives[x] > other.objectives[x])
303                    bbeatsa = true;
304                if (abeatsb && bbeatsa)
305                    return true;
306                }
307            }
308        if (abeatsb || bbeatsa)
309            return false;
310        return true;
311        }
312
313    /**
314     * Returns true if I'm better than _fitness. The DEFAULT rule I'm using is this: if
315     * I am better in one or more criteria, and we are equal in the others, then
316     * betterThan is true, else it is false. Multiobjective optimization algorithms may
317     * choose to override this to do something else.
318     */
319
320    public boolean betterThan(Fitness fitness)
321        {
322        return paretoDominates((MultiObjectiveFitness)fitness);
323        }
324
325    /**
326     * Returns true if I'm better than _fitness. The rule I'm using is this: if
327     * I am better in one or more criteria, and we are equal in the others, then
328     * betterThan is true, else it is false.
329     */
330
331    public boolean paretoDominates(MultiObjectiveFitness other)
332        {
333        boolean abeatsb = false;
334        if (maximize != other.maximize)
335            throw new RuntimeException(
336                "Attempt made to compare two multiobjective fitnesses; but one expects higher values to be better and the other expectes lower values to be better.");
337        if (objectives.length != other.objectives.length)
338            throw new RuntimeException("Attempt made to compare two multiobjective fitnesses; but they have different numbers of objectives.");
339        if (maximize)
340            {
341            for (int x = 0; x < objectives.length; x++)
342                {
343                if (objectives[x] > other.objectives[x])
344                    abeatsb = true;
345                else if (objectives[x] < other.objectives[x])
346                    return false;
347                }
348            }
349        else
350            {
351            for (int x = 0; x < objectives.length; x++)
352                {
353                if (objectives[x] < other.objectives[x])
354                    abeatsb = true;
355                else if (objectives[x] > other.objectives[x])
356                    return false;
357                }
358            }
359        return abeatsb;
360        }
361
362    // Remove an individual from the ArrayList, shifting the topmost
363    // individual in his place
364    static void yank(int val, ArrayList list)
365        {
366        int size = list.size();
367        list.set(val, list.get(size - 1));
368        list.remove(size - 1);
369        }
370
371    /**
372     * Divides an array of Individuals into the Pareto front and the "nonFront" (everyone else).
373     * The Pareto front is returned.  You may provide ArrayLists for the front and a nonFront.
374     * If you provide null for the front, an ArrayList will be created for you.  If you provide
375     * null for the nonFront, non-front individuals will not be added to it.  This algorithm is
376     * O(n^2).
377     */
378    public static ArrayList partitionIntoParetoFront(Individual[] inds, ArrayList front, ArrayList nonFront)
379        {
380        if (front == null)
381            front = new ArrayList();
382                       
383        // put the first guy in the front
384        front.add(inds[0]);
385               
386        // iterate over all the remaining individuals
387        for (int i = 1; i < inds.length; i++)
388            {
389            Individual ind = (Individual) (inds[i]);
390
391            boolean noOneWasBetter = true;
392            int frontSize = front.size();
393                       
394            // iterate over the entire front
395            for (int j = 0; j < frontSize; j++)
396                {
397                Individual frontmember = (Individual) (front.get(j));
398                               
399                // if the front member is better than the individual, dump the individual and go to the next one
400                if (((MultiObjectiveFitness) (frontmember.fitness)).paretoDominates((MultiObjectiveFitness) (ind.fitness)))
401                    {
402                    if (nonFront != null) nonFront.add(ind);
403                    noOneWasBetter = false;
404                    break;  // failed.  He's not in the front
405                    }
406                // if the individual was better than the front member, dump the front member.  But look over the
407                // other front members (don't break) because others might be dominated by the individual as well.
408                else if (((MultiObjectiveFitness) (ind.fitness)).paretoDominates((MultiObjectiveFitness) (frontmember.fitness)))
409                    {
410                    yank(j, front);
411                    // a front member is dominated by the new individual.  Replace him
412                    frontSize--; // member got removed
413                    j--;  // because there's another guy we now need to consider in his place
414                    if (nonFront != null) nonFront.add(frontmember);
415                    }
416                }
417            if (noOneWasBetter)
418                front.add(ind);
419            }
420        return front;
421        }
422
423
424    /** Divides inds into pareto front ranks (each an ArrayList), and returns them, in order,
425        stored in an ArrayList. */
426    public static ArrayList partitionIntoRanks(Individual[] inds)
427        {
428        Individual[] dummy = new Individual[0];
429        ArrayList frontsByRank = new ArrayList();
430
431        while(inds.length > 0)
432            {
433            ArrayList front = new ArrayList();
434            ArrayList nonFront = new ArrayList();
435            MultiObjectiveFitness.partitionIntoParetoFront(inds, front, nonFront);
436                       
437            // build inds out of remainder
438            inds = (Individual[]) nonFront.toArray(dummy);
439            frontsByRank.add(front);
440            }
441        return frontsByRank;
442        }
443
444
445    /**
446     * Returns the sum of the squared difference between two Fitnesses in Objective space.
447     */
448    public double sumSquaredObjectiveDistance(MultiObjectiveFitness other)
449        {
450        double s = 0;
451        for (int i = 0; i < objectives.length; i++)
452            {
453            double a = (objectives[i] - other.objectives[i]);
454            s += a * a;
455            }
456        return s;
457        }
458
459
460    /**
461     * Returns the Manhattan difference between two Fitnesses in Objective space.
462     */
463    public double manhattanObjectiveDistance(MultiObjectiveFitness other)
464        {
465        double s = 0;
466        for (int i = 0; i < objectives.length; i++)
467            {
468            s += Math.abs(objectives[i] - other.objectives[i]);
469            }
470        return s;
471        }
472
473
474    public String fitnessToString()
475        {
476        String s = FITNESS_PREAMBLE + MULTI_FITNESS_POSTAMBLE;
477        for (int x = 0; x < objectives.length; x++)
478            {
479            if (x > 0)
480                s = s + " ";
481            s = s + Code.encode(objectives[x]);
482            }
483        s = s + " ";
484        s = s + Code.encode(maximize);
485        return s + FITNESS_POSTAMBLE;
486        }
487
488
489    public String fitnessToStringForHumans()
490        {
491        String s = FITNESS_PREAMBLE + MULTI_FITNESS_POSTAMBLE;
492        for (int x = 0; x < objectives.length; x++)
493            {
494            if (x > 0)
495                s = s + " ";
496            s = s + objectives[x];
497            }
498        s = s + " ";
499        s = s + (maximize ? "max" : "min");
500        return s + FITNESS_POSTAMBLE;
501        }
502
503    public void readFitness(final EvolutionState state, final LineNumberReader reader) throws IOException
504        {
505        DecodeReturn d = Code.checkPreamble(FITNESS_PREAMBLE + MULTI_FITNESS_POSTAMBLE, state, reader);
506        for (int x = 0; x < objectives.length; x++)
507            {
508            Code.decode(d);
509            if (d.type != DecodeReturn.T_FLOAT)
510                state.output.fatal("Reading Line " + d.lineNumber + ": " + "Bad Fitness (objectives value #" + x + ").");
511            objectives[x] = (float) d.d;
512            }
513        Code.decode(d);
514        if (d.type != DecodeReturn.T_BOOLEAN)
515            state.output.fatal("Reading Line " + d.lineNumber + ": " + "Information missing about whether higher is better");
516        maximize = (boolean) (d.l != 0);
517        }
518
519    public void writeFitness(final EvolutionState state, final DataOutput dataOutput) throws IOException
520        {
521        dataOutput.writeInt(objectives.length);
522        for (int x = 0; x < objectives.length; x++)
523            dataOutput.writeFloat(objectives[x]);
524        dataOutput.writeBoolean(maximize);
525        }
526
527    public void readFitness(final EvolutionState state, final DataInput dataInput) throws IOException
528        {
529        int len = dataInput.readInt();
530        if (objectives == null || objectives.length != len)
531            objectives = new float[len];
532        for (int x = 0; x < objectives.length; x++)
533            objectives[x] = dataInput.readFloat();
534        maximize = dataInput.readBoolean();
535        }
536    }
Note: See TracBrowser for help on using the repository browser.