1 package net.bmahe.genetics4j.moo;
2
3 import java.util.Collection;
4 import java.util.Comparator;
5 import java.util.List;
6 import java.util.Objects;
7 import java.util.stream.Collectors;
8 import java.util.stream.IntStream;
9
10 import org.apache.commons.lang3.Validate;
11
12 /**
13 * Represents a multi-objective fitness vector for multi-objective optimization (MOO).
14 *
15 * <p>A FitnessVector encapsulates multiple fitness values, each representing the quality of a solution in a different
16 * objective dimension. This is essential for multi-objective evolutionary algorithms like NSGA-II and SPEA-II where
17 * solutions are evaluated against multiple, often conflicting objectives.
18 *
19 * <p>The fitness vector provides:
20 * <ul>
21 * <li>Storage for multiple objective values</li>
22 * <li>Custom comparators for each objective (minimization/maximization)</li>
23 * <li>Pareto dominance comparison logic</li>
24 * <li>Type-safe access to individual objective values</li>
25 * </ul>
26 *
27 * <p>Pareto dominance comparison rules:
28 * <ul>
29 * <li>Vector A dominates B if A is better in all objectives</li>
30 * <li>Vector A weakly dominates B if A is better or equal in all objectives and better in at least one</li>
31 * <li>Vectors are non-dominated if neither dominates the other</li>
32 * </ul>
33 *
34 * @param <T> the type of the fitness values, must be comparable
35 * @see net.bmahe.genetics4j.moo.nsga2
36 * @see net.bmahe.genetics4j.moo.spea2
37 * @see ParetoUtils
38 */
39 public class FitnessVector<T extends Comparable<T>> implements Comparable<FitnessVector<T>> {
40
41 private final List<T> fitnesses;
42 private final List<Comparator<T>> comparators;
43
44 /**
45 * Creates a fitness vector with the specified values and comparators.
46 *
47 * @param _vector the fitness values for each objective
48 * @param _comparators the comparators defining optimization direction for each objective
49 * @throws IllegalArgumentException if vectors are null, empty, or have different sizes
50 */
51 public FitnessVector(final Collection<T> _vector, final Collection<Comparator<T>> _comparators) {
52 Objects.requireNonNull(_vector);
53 Validate.isTrue(_vector.isEmpty() == false, "Fitness vector must not be empty");
54 Objects.requireNonNull(_comparators);
55 Validate.isTrue(_vector.size() == _comparators.size());
56
57 fitnesses = List.copyOf(_vector);
58 comparators = List.copyOf(_comparators);
59 }
60
61 /**
62 * Creates a fitness vector with natural ordering for all objectives.
63 *
64 * <p>All objectives will use natural ordering (ascending), which means smaller values are considered better
65 * (minimization).
66 *
67 * @param _vector the fitness values for each objective
68 * @throws IllegalArgumentException if vector is null or empty
69 */
70 public FitnessVector(final Collection<T> _vector) {
71 this(_vector,
72 IntStream.range(0, _vector.size())
73 .boxed()
74 .map(i -> Comparator.<T>naturalOrder())
75 .collect(Collectors.toList()));
76 }
77
78 /**
79 * Creates a fitness vector from variable arguments with natural ordering.
80 *
81 * @param _vector the fitness values for each objective
82 * @throws IllegalArgumentException if vector is empty
83 */
84 public FitnessVector(T... _vector) {
85 this(List.of(_vector));
86 }
87
88 /**
89 * Returns the number of objectives in this fitness vector.
90 *
91 * @return the number of objectives
92 */
93 public int dimensions() {
94 return fitnesses.size();
95 }
96
97 /**
98 * Returns the fitness value for the specified objective.
99 *
100 * @param index the index of the objective (0-based)
101 * @return the fitness value for the specified objective
102 * @throws IllegalArgumentException if index is out of bounds
103 */
104 public T get(final int index) {
105 Validate.exclusiveBetween(-1, fitnesses.size(), index);
106
107 return fitnesses.get(index);
108 }
109
110 /**
111 * Returns the comparator for the specified objective.
112 *
113 * @param index the index of the objective (0-based)
114 * @return the comparator defining optimization direction for the objective
115 * @throws IllegalArgumentException if index is out of bounds
116 */
117 public Comparator<T> getComparator(final int index) {
118 Validate.exclusiveBetween(-1, fitnesses.size(), index);
119
120 return comparators.get(index);
121 }
122
123 @Override
124 public int hashCode() {
125 final int prime = 31;
126 int result = 1;
127 result = prime * result + (comparators == null ? 0 : comparators.hashCode());
128 return prime * result + (fitnesses == null ? 0 : fitnesses.hashCode());
129 }
130
131 @Override
132 public boolean equals(Object obj) {
133 if (this == obj) {
134 return true;
135 }
136 if (obj == null) {
137 return false;
138 }
139 if (getClass() != obj.getClass()) {
140 return false;
141 }
142 FitnessVector other = (FitnessVector) obj;
143 if (comparators == null) {
144 if (other.comparators != null) {
145 return false;
146 }
147 } else if (!comparators.equals(other.comparators)) {
148 return false;
149 }
150 if (fitnesses == null) {
151 if (other.fitnesses != null) {
152 return false;
153 }
154 } else if (!fitnesses.equals(other.fitnesses)) {
155 return false;
156 }
157 return true;
158 }
159
160 @Override
161 public String toString() {
162 return "FitnessVector [fitnesses=" + fitnesses + "]";
163 }
164
165 @Override
166 public int compareTo(final FitnessVector<T> o) {
167 return compare(this, o);
168 }
169
170 /**
171 * Compares two fitness vectors using Pareto dominance.
172 *
173 * <p>This method implements Pareto dominance comparison:
174 * <ul>
175 * <li>Returns positive value if fv1 dominates fv2 (fv1 is better in all objectives)</li>
176 * <li>Returns negative value if fv2 dominates fv1 (fv2 is better in all objectives)</li>
177 * <li>Returns 0 if vectors are non-dominated (neither dominates the other)</li>
178 * </ul>
179 *
180 * @param <U> the type of the fitness values
181 * @param fv1 the first fitness vector to compare
182 * @param fv2 the second fitness vector to compare
183 * @return positive if fv1 dominates fv2, negative if fv2 dominates fv1, 0 if non-dominated
184 * @throws IllegalArgumentException if vectors are null or have different dimensions
185 */
186 public static <U extends Comparable<U>> int compare(final FitnessVector<U> fv1, final FitnessVector<U> fv2) {
187 Objects.requireNonNull(fv1);
188 Objects.requireNonNull(fv2);
189
190 if (fv1.dimensions() != fv2.dimensions()) {
191 throw new IllegalArgumentException("Can't compare FitnessVector with different dimensions");
192 }
193
194 int greater = 0;
195 int lesser = 0;
196
197 for (int i = 0; i < fv1.dimensions(); i++) {
198 final U d1 = fv1.get(i);
199 final U d2 = fv2.get(i);
200
201 final int compared = fv1.comparators.get(i).compare(d1, d2);
202
203 if (compared < 0) {
204 lesser++;
205 } else if (compared > 0) {
206 greater++;
207 }
208 }
209
210 if (lesser == 0 && greater > 0) {
211 return 1;
212 }
213 if (greater == 0 && lesser > 0) {
214 return -1;
215 }
216 return 0;
217 }
218 }