1 package net.bmahe.genetics4j.samples.clustering;
2
3 import static net.bmahe.genetics4j.core.termination.Terminations.or;
4
5 import java.io.IOException;
6 import java.util.ArrayList;
7 import java.util.List;
8 import java.util.Objects;
9 import java.util.Optional;
10 import java.util.Random;
11
12 import org.apache.commons.cli.CommandLine;
13 import org.apache.commons.cli.CommandLineParser;
14 import org.apache.commons.cli.DefaultParser;
15 import org.apache.commons.cli.Options;
16 import org.apache.commons.cli.ParseException;
17 import org.apache.commons.lang3.Validate;
18 import org.apache.commons.lang3.time.DurationFormatUtils;
19 import org.apache.commons.math3.ml.clustering.CentroidCluster;
20 import org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer;
21 import org.apache.logging.log4j.LogManager;
22 import org.apache.logging.log4j.Logger;
23
24 import net.bmahe.genetics4j.core.EASystemFactory;
25 import net.bmahe.genetics4j.core.Fitness;
26 import net.bmahe.genetics4j.core.Genotype;
27 import net.bmahe.genetics4j.core.chromosomes.DoubleChromosome;
28 import net.bmahe.genetics4j.core.evolutionlisteners.EvolutionListeners;
29 import net.bmahe.genetics4j.core.spec.EAConfiguration;
30 import net.bmahe.genetics4j.core.spec.EAExecutionContexts;
31 import net.bmahe.genetics4j.core.spec.chromosome.DoubleChromosomeSpec;
32 import net.bmahe.genetics4j.core.spec.combination.MultiCombinations;
33 import net.bmahe.genetics4j.core.spec.combination.MultiPointArithmetic;
34 import net.bmahe.genetics4j.core.spec.combination.MultiPointCrossover;
35 import net.bmahe.genetics4j.core.spec.mutation.CreepMutation;
36 import net.bmahe.genetics4j.core.spec.mutation.MultiMutations;
37 import net.bmahe.genetics4j.core.spec.mutation.RandomMutation;
38 import net.bmahe.genetics4j.core.spec.selection.Tournament;
39 import net.bmahe.genetics4j.core.termination.Termination;
40 import net.bmahe.genetics4j.core.termination.Terminations;
41 import net.bmahe.genetics4j.extras.evolutionlisteners.CSVEvolutionListener;
42 import net.bmahe.genetics4j.extras.evolutionlisteners.ColumnExtractor;
43 import net.bmahe.genetics4j.samples.CLIUtils;
44
45 public class Clustering {
46 public static final Logger logger = LogManager.getLogger(Clustering.class);
47
48 public static final int DEFAULT_NUM_CLUSTERS = 6;
49 public static final int DEFAULT_NUMBER_TOURNAMENTS = 2;
50 public static final int DEFAULT_POPULATION_SIZE = 120;
51 public static final double DEFAULT_RANDOM_MUTATION_RATE = 0.15D;
52 public static final double DEFAULT_CREEP_MUTATION_RATE = 0.20D;
53 public static final double DEFAULT_CREEP_MUTATION_MEAN = 0.0D;
54 public static final double DEFAULT_CREEP_MUTATION_STDDEV = 5;
55 public static final int DEFAULT_COMBINATION_ARITHMETIC = 3;
56 public static final int DEFAULT_COMBINATION_CROSSOVER_ARITHMETIC = 3;
57
58 public static final String PARAM_NUM_CLUSTERS = "n";
59 public static final String LONG_PARAM_NUM_CLUSTERS = "num-clusters";
60
61 public static final String PARAM_NUMBER_TOURNAMENTS = "t";
62 public static final String LONG_PARAM_NUMBER_TOURNAMENTS = "num-tournaments";
63
64 public static final String PARAM_POPULATION_SIZE = "p";
65 public static final String LONG_PARAM_POPULATION_SIZE = "population-size";
66
67 public static final String PARAM_SOURCE_CLUSTERS_CSV = "c";
68 public static final String LONG_PARAM_SOURCE_CUSTERS_CSV = "source-clusters";
69
70 public static final String PARAM_SOURCE_DATA_CSV = "s";
71 public static final String LONG_PARAM_SOURCE_DATA_CSV = "source-data";
72
73 public static final String PARAM_FIXED_TERMINATION = "f";
74 public static final String LONG_PARAM_FIXED_TERMINATION = "fixed-termination";
75
76 public static final String PARAM_RANDOM_MUTATION_RATE = "r";
77 public static final String LONG_PARAM_RANDOM_MUTATION_RATE = "random-mutation-rate";
78
79 public static final String PARAM_CREEP_MUTATION_RATE = "m";
80 public static final String LONG_PARAM_CREEP_MUTATION_RATE = "creep-mutation-rate";
81
82 public static final String PARAM_CREEP_MUTATION_MEAN = "a";
83 public static final String LONG_PARAM_CREEP_MUTATION_MEAN = "creep-mutation-mean";
84
85 public static final String PARAM_CREEP_MUTATION_STD_DEV = "d";
86 public static final String LONG_PARAM_CREEP_MUTATION_STD_DEV = "creep-mutation-std-dev";
87
88 public static final String PARAM_COMBINATION_ARITHMETIC = "b";
89 public static final String LONG_PARAM_COMBINATION_ARITHMETIC = "combination-arithmetic";
90
91 public static final String PARAM_COMBINATION_CROSSOVER = "e";
92 public static final String LONG_PARAM_COMBINATION_CROSSOVER = "combination-crossover";
93
94 public static final String PARAM_OUTPUT_CSV = "o";
95 public static final String LONG_PARAM_OUTPUT_CSV = "output";
96
97 public static final String PARAM_OUTPUT_WITH_SSE_CSV = "g";
98 public static final String LONG_PARAM_OUTPUT_WITH_SSE_CSV = "output-sse";
99
100 public static final String PARAM_BASE_DIR_OUTPUT = "i";
101 public static final String LONG_PARAM_BASE_DIR_OUTPUT = "base-dir";
102
103 public static void cliError(final Options options, final String errorMessage) {
104 CLIUtils.cliHelpAndExit(logger, Clustering.class, options, errorMessage);
105 }
106
107 private static double computeDistance(final double[][] array, final int i, final int j) {
108 final double xDiff = array[j][0] - array[i][0];
109 final double yDiff = array[j][1] - array[i][1];
110 return Math.sqrt((xDiff * xDiff) + (yDiff * yDiff));
111 }
112
113 private static double[][] computeAllDistances(final double[][] array) {
114
115 final double[][] distances = new double[array.length][array.length];
116
117 for (int i = 0; i < array.length; i++) {
118 distances[i][i] = 0.0;
119 }
120
121 for (int i = 0; i < array.length; i++) {
122 for (int j = 0; j < i; j++) {
123 final double distance = computeDistance(array, i, j);
124 distances[i][j] = distance;
125 distances[j][i] = distance;
126 }
127 }
128
129 return distances;
130 }
131
132
133 public static double[][] generateClusters(final Random random, final int numClusters, final double minX,
134 final double maxX, final double minY, final double maxY) {
135 Objects.requireNonNull(random);
136 Validate.isTrue(numClusters > 0);
137 Validate.isTrue(minX <= maxX);
138 Validate.isTrue(minY <= maxY);
139
140 logger.info("Generating {} clusters", numClusters);
141
142 final double[][] clusters = new double[numClusters][2];
143 for (int i = 0; i < numClusters; i++) {
144 clusters[i][0] = minX + random.nextDouble() * (maxX - minX);
145 clusters[i][1] = minY + random.nextDouble() * (maxY - minY);
146 }
147
148 return clusters;
149 }
150
151
152
153 public static double[][] generateDataPoints(final Random random, final double[][] clusters, final int numDataPoints,
154 final double radius) {
155 Objects.requireNonNull(random);
156 Objects.requireNonNull(clusters);
157 Validate.isTrue(clusters.length > 0);
158
159 final int numClusters = clusters.length;
160 final double[][] data = new double[numDataPoints][3];
161 for (int i = 0; i < numDataPoints; i++) {
162 final int clusterIndex = i % numClusters;
163
164 data[i][0] = random.nextGaussian() * radius + clusters[clusterIndex][0];
165 data[i][1] = random.nextGaussian() * radius + clusters[clusterIndex][1];
166 data[i][2] = clusterIndex;
167 }
168
169 return data;
170 }
171
172
173 public static void doGA(final int k, final double min, final double max, final int numberTournaments,
174 final int combinationArithmetic, final int combinationCrossover, final double randomMutationRate,
175 final double creepMutationRate, final double creepMutationMean, final double creepMutationStdDev,
176 final Fitness<Double> fitnessFunction, final Termination<Double> terminations, final int populationSize,
177 final String outputCSV, final double[][] data, final double[][] distances, final String baseDir,
178 final String filenameSuffix) throws IOException {
179
180
181 final var eaConfigurationBuilder = new EAConfiguration.Builder<Double>();
182 eaConfigurationBuilder.chromosomeSpecs(DoubleChromosomeSpec.of(k * 2, min, max))
183 .parentSelectionPolicy(Tournament.of(numberTournaments))
184 .combinationPolicy(
185 MultiCombinations.of(
186 MultiPointArithmetic.of(combinationArithmetic, 0.5),
187 MultiPointCrossover.of(combinationCrossover)))
188 .mutationPolicies(
189 MultiMutations.of(
190 RandomMutation.of(randomMutationRate),
191 CreepMutation.ofNormal(creepMutationRate, creepMutationMean, creepMutationStdDev)))
192 .fitness(fitnessFunction)
193 .postEvaluationProcessor(FitnessSharingUtils.clusterDistance)
194 .termination(terminations);
195 final var eaConfiguration = eaConfigurationBuilder.build();
196
197
198 final var eaExecutionContext = EAExecutionContexts.<Double>forScalarFitness()
199 .populationSize(populationSize)
200 .addEvolutionListeners(
201 EvolutionListeners.ofLogTopN(logger, 3),
202 new CSVEvolutionListener.Builder<Double, Double>().filename(outputCSV)
203 .columnExtractors(
204 List.of(
205 ColumnExtractor.of("generation", evolutionStep -> evolutionStep.generation()),
206 ColumnExtractor.of("fitness", evolutionStep -> evolutionStep.fitness()),
207 ColumnExtractor
208 .of("combination_arithmetic", evolutionStep -> combinationArithmetic),
209 ColumnExtractor.of("combination_crossover", evolutionStep -> combinationCrossover),
210 ColumnExtractor.of("random_mutation_rate", evolutionStep -> randomMutationRate),
211 ColumnExtractor.of("creep_mutation_mean", evolutionStep -> creepMutationMean),
212 ColumnExtractor.of("creep_mutation_stddev", evolutionStep -> creepMutationStdDev),
213 ColumnExtractor.of("creep_mutation_rate", evolutionStep -> creepMutationRate)))
214 .build())
215 .build();
216
217 final var eaSystem = EASystemFactory.from(eaConfiguration, eaExecutionContext);
218
219 final var evolutionResult = eaSystem.evolve();
220 logger.info("Best genotype: {}", evolutionResult.bestGenotype());
221 logger.info(" with fitness: {}", evolutionResult.bestFitness());
222 logger.info(" at generation: {}", evolutionResult.generation());
223
224 final Genotype bestGenotype = evolutionResult.bestGenotype();
225 final double[][] bestPhenotype = PhenotypeUtils.toPhenotype(bestGenotype);
226 logger.info("Best phenotype:");
227 for (int i = 0; i < k; i++) {
228 logger.info("\tx: {} - y: {}", bestPhenotype[i][0], bestPhenotype[i][1]);
229 }
230 final int[] bestClusterMembership = FitnessUtils.assignDataToClusters(data, distances, bestPhenotype);
231 IOUtils
232 .persistDataPoints(data, bestClusterMembership, baseDir + "clustering-result-ga" + filenameSuffix + ".csv");
233 IOUtils.persistClusters(bestPhenotype, baseDir + "clustering-result-clusters-ga" + filenameSuffix + ".csv");
234
235 }
236
237 public static List<CentroidCluster<LocationWrapper>> apacheCommonsMathCluster(final double[][] clusters,
238 final double[][] data) {
239
240 logger.info("Initializing kmeans from Apache Commons Math");
241
242 final long startTs = System.currentTimeMillis();
243
244 final int numClusters = clusters.length;
245 final int numDataPoints = data.length;
246
247 final List<LocationWrapper> clusterInput = new ArrayList<>(numDataPoints);
248 for (int i = 0; i < numDataPoints; i++) {
249 clusterInput.add(new LocationWrapper(data[i]));
250 }
251
252 logger.info("Running kmeans");
253
254 final KMeansPlusPlusClusterer<LocationWrapper> clusterer = new KMeansPlusPlusClusterer<>(numClusters, 10_000);
255 final List<CentroidCluster<LocationWrapper>> clusterResults = clusterer.cluster(clusterInput);
256
257 final long durationMs = System.currentTimeMillis() - startTs;
258 logger.info("Computation time: {}", DurationFormatUtils.formatDurationHMS(durationMs));
259
260 return clusterResults;
261 }
262
263 public static void main(String[] args) throws IOException {
264 logger.info("Starting");
265
266 final Random random = new Random();
267
268 final double min = -100;
269 final double max = 100;
270 final double minX = min;
271 final double maxX = max;
272 final double minY = min;
273 final double maxY = max;
274
275 final double radius = 8;
276
277 final int numDataPoints = 1_000;
278
279
280
281
282
283 final CommandLineParser parser = new DefaultParser();
284
285 final Options options = new Options();
286 options.addOption(PARAM_NUM_CLUSTERS, LONG_PARAM_NUM_CLUSTERS, true, "number of clusters");
287 options.addOption(PARAM_NUMBER_TOURNAMENTS, LONG_PARAM_NUMBER_TOURNAMENTS, true, "number of tournaments");
288 options.addOption(PARAM_SOURCE_CLUSTERS_CSV, LONG_PARAM_SOURCE_CUSTERS_CSV, true, "source csv file for clusters");
289 options.addOption(PARAM_SOURCE_DATA_CSV, LONG_PARAM_SOURCE_DATA_CSV, true, "source csv file for data");
290 options.addOption(PARAM_OUTPUT_CSV, LONG_PARAM_OUTPUT_CSV, true, "output csv");
291 options.addOption(PARAM_OUTPUT_WITH_SSE_CSV, LONG_PARAM_OUTPUT_WITH_SSE_CSV, true, "output with sse csv");
292 options
293 .addOption(PARAM_COMBINATION_ARITHMETIC, LONG_PARAM_COMBINATION_ARITHMETIC, true, "combination arithmetic");
294 options.addOption(PARAM_COMBINATION_CROSSOVER, LONG_PARAM_COMBINATION_CROSSOVER, true, "combination crossover");
295 options.addOption(PARAM_POPULATION_SIZE, LONG_PARAM_POPULATION_SIZE, true, "population size");
296 options.addOption(PARAM_BASE_DIR_OUTPUT, LONG_PARAM_BASE_DIR_OUTPUT, true, "base directory");
297 options.addOption(
298 PARAM_CREEP_MUTATION_STD_DEV,
299 LONG_PARAM_CREEP_MUTATION_STD_DEV,
300 true,
301 "creep mutation std dev. Default: " + DEFAULT_CREEP_MUTATION_STDDEV);
302 options.addOption(
303 PARAM_CREEP_MUTATION_MEAN,
304 LONG_PARAM_CREEP_MUTATION_MEAN,
305 true,
306 "creep mutation mean. Default: " + DEFAULT_CREEP_MUTATION_MEAN);
307 options.addOption(
308 PARAM_CREEP_MUTATION_RATE,
309 LONG_PARAM_CREEP_MUTATION_RATE,
310 true,
311 "creep mutation rate. Default: " + DEFAULT_CREEP_MUTATION_RATE);
312 options.addOption(
313 PARAM_RANDOM_MUTATION_RATE,
314 LONG_PARAM_RANDOM_MUTATION_RATE,
315 true,
316 "random mutation rate. Default: " + DEFAULT_RANDOM_MUTATION_RATE);
317 options.addOption(
318 PARAM_FIXED_TERMINATION,
319 LONG_PARAM_FIXED_TERMINATION,
320 true,
321 "Fix the termination to the specified number of generations");
322
323 Optional<String> paramSourceClustersCSV = Optional.empty();
324 Optional<String> paramSourceDataCSV = Optional.empty();
325 Optional<Integer> paramNumClusters = Optional.empty();
326 Optional<String> paramOutputCSV = Optional.empty();
327 Optional<String> paramOutputWithSSECSV = Optional.empty();
328 Optional<Long> paramFixedTermination = Optional.empty();
329
330 int numberTournaments = DEFAULT_NUMBER_TOURNAMENTS;
331 int populationSize = DEFAULT_POPULATION_SIZE;
332 final double randomMutationRate;
333 final double creepMutationRate;
334 final double creepMutationMean;
335 final double creepMutationStdDev;
336 final int combinationArithmetic;
337 final int combinationCrossover;
338 final String baseDir;
339 try {
340 final CommandLine line = parser.parse(options, args);
341
342 if (line.hasOption(PARAM_NUMBER_TOURNAMENTS)) {
343 numberTournaments = Integer.parseInt(line.getOptionValue(PARAM_NUMBER_TOURNAMENTS).strip());
344 }
345
346 baseDir = Optional.ofNullable(line.getOptionValue(PARAM_BASE_DIR_OUTPUT)).orElse("");
347
348 combinationArithmetic = Optional.ofNullable(line.getOptionValue(PARAM_COMBINATION_ARITHMETIC))
349 .map(String::strip)
350 .map(Integer::parseInt)
351 .orElse(DEFAULT_COMBINATION_ARITHMETIC);
352
353 combinationCrossover = Optional.ofNullable(line.getOptionValue(PARAM_COMBINATION_CROSSOVER))
354 .map(String::strip)
355 .map(Integer::parseInt)
356 .orElse(DEFAULT_COMBINATION_CROSSOVER_ARITHMETIC);
357
358 paramNumClusters = Optional.ofNullable(line.getOptionValue(PARAM_NUM_CLUSTERS))
359 .map(String::strip)
360 .map(Integer::parseInt);
361 populationSize = Optional.ofNullable(line.getOptionValue(PARAM_POPULATION_SIZE))
362 .map(String::strip)
363 .map(Integer::parseInt)
364 .orElse(DEFAULT_POPULATION_SIZE);
365
366 paramSourceClustersCSV = Optional.ofNullable(line.getOptionValue(PARAM_SOURCE_CLUSTERS_CSV))
367 .map(String::strip);
368
369 paramSourceDataCSV = Optional.ofNullable(line.getOptionValue(PARAM_SOURCE_DATA_CSV)).map(String::strip);
370
371 paramOutputCSV = Optional.ofNullable(line.getOptionValue(PARAM_OUTPUT_CSV)).map(String::strip);
372 paramOutputWithSSECSV = Optional.ofNullable(line.getOptionValue(PARAM_OUTPUT_WITH_SSE_CSV)).map(String::strip);
373
374 paramFixedTermination = Optional.ofNullable(line.getOptionValue(PARAM_FIXED_TERMINATION))
375 .map(String::strip)
376 .map(Long::parseLong);
377
378 randomMutationRate = Optional.ofNullable(line.getOptionValue(PARAM_RANDOM_MUTATION_RATE))
379 .map(String::strip)
380 .map(Double::parseDouble)
381 .orElse(DEFAULT_RANDOM_MUTATION_RATE);
382
383 creepMutationRate = Optional.ofNullable(line.getOptionValue(PARAM_CREEP_MUTATION_RATE))
384 .map(String::strip)
385 .map(Double::parseDouble)
386 .orElse(DEFAULT_CREEP_MUTATION_RATE);
387
388 creepMutationMean = Optional.ofNullable(line.getOptionValue(PARAM_CREEP_MUTATION_MEAN))
389 .map(String::strip)
390 .map(Double::parseDouble)
391 .orElse(DEFAULT_CREEP_MUTATION_MEAN);
392
393 creepMutationStdDev = Optional.ofNullable(line.getOptionValue(PARAM_CREEP_MUTATION_STD_DEV))
394 .map(String::strip)
395 .map(Double::parseDouble)
396 .orElse(DEFAULT_CREEP_MUTATION_STDDEV);
397
398 logger.info("Unrecognized args:");
399 boolean hasError = false;
400 for (final String extraArg : line.getArgList()) {
401 logger.info("\t[{}]", extraArg);
402 if (!extraArg.isBlank()) {
403 hasError = true;
404 }
405 }
406
407 if (hasError) {
408 throw new RuntimeException();
409 }
410 } catch (ParseException exp) {
411 cliError(options, "Unexpected exception:" + exp.getMessage());
412
413
414 throw new RuntimeException();
415
416 }
417
418 final int numClusters = paramNumClusters.orElse(DEFAULT_NUM_CLUSTERS);
419
420 logger.info("Preparing {} clusters", numClusters);
421 final double[][] clusters = paramSourceClustersCSV.map(IOUtils::loadClusters)
422 .orElseGet(() -> generateClusters(random, numClusters, minX, maxX, minY, maxY));
423 logger.info("Found {} clusters", clusters.length);
424
425 logger.info("Preparing data points");
426 final double[][] data = paramSourceDataCSV.map(sourceDataCSVFileName -> {
427 try {
428 logger.info("Loading data points");
429 return IOUtils.loadDataPoints(sourceDataCSVFileName);
430 } catch (IOException e) {
431 throw new RuntimeException("Could not load " + sourceDataCSVFileName, e);
432 }
433 }).orElseGet(() -> generateDataPoints(random, clusters, numDataPoints, radius));
434 final double[][] distances = computeAllDistances(data);
435
436 final String originalClustersFilename = "originalClusters.csv";
437 if (paramSourceClustersCSV.isPresent()) {
438 logger.info("Not persisting clusters since it was provided");
439 } else {
440 logger.info("Saving clusters to CSV: {}", originalClustersFilename);
441 IOUtils.persistClusters(clusters, originalClustersFilename);
442 }
443
444 final String originalDataFilename = "originalData.csv";
445 if (paramSourceDataCSV.isPresent()) {
446 logger.info("Not persisting data since it was provided");
447 } else {
448 logger.info("Saving data to CSV: {}", originalDataFilename);
449 IOUtils.persistDataPoints(data, originalDataFilename);
450 }
451
452 logger.info("Clustering data with Apache Commons Math");
453
454 final List<CentroidCluster<LocationWrapper>> clusterResults = apacheCommonsMathCluster(clusters, data);
455
456 logger.info("Definition of genetic problem");
457
458 final int k = numClusters;
459 final var fitnessFunction = FitnessUtils.computeFitness(numDataPoints, data, distances, k);
460
461 final var terminations = paramFixedTermination.map(Terminations::<Double>ofMaxGeneration)
462 .orElseGet(() -> or(Terminations.<Double>ofMaxGeneration(500), Terminations.<Double>ofStableFitness(50)));
463
464 logger.info("Terminations: {}", paramFixedTermination);
465 logger.info(
466 "Parameters: random_mutation_rate: {} - creep_mutation_rate: {} - creep_mutation_stddev: {} ",
467 randomMutationRate,
468 creepMutationRate,
469 creepMutationStdDev);
470 logger.info("Combinations: arithmetic {} ; crossover {}", combinationArithmetic, combinationCrossover);
471
472 logger.info("Running GA with Silhouette score");
473 doGA(
474 k,
475 min,
476 max,
477 numberTournaments,
478 combinationArithmetic,
479 combinationCrossover,
480 randomMutationRate,
481 creepMutationRate,
482 creepMutationMean,
483 creepMutationStdDev,
484 fitnessFunction,
485 terminations,
486 populationSize,
487 paramOutputCSV.orElse("output.csv"),
488 data,
489 distances,
490 baseDir,
491 "");
492
493 logger.info("Running GA with Silhouette score + SSE");
494 final var fitnessFunctionWithSumSquareErrors = FitnessUtils
495 .computeFitnessWithSSE(numDataPoints, data, distances, k);
496 doGA(
497 k,
498 min,
499 max,
500 numberTournaments,
501 combinationArithmetic,
502 combinationCrossover,
503 randomMutationRate,
504 creepMutationRate,
505 creepMutationMean,
506 creepMutationStdDev,
507 fitnessFunctionWithSumSquareErrors,
508 terminations,
509 populationSize,
510 paramOutputWithSSECSV.orElse("output-with-sse.csv"),
511 data,
512 distances,
513 baseDir,
514 "-with-sse");
515
516 logger.info("Original clusters:");
517 final double[] originalMeans = new double[numClusters * 2];
518 for (int i = 0; i < numClusters; i++) {
519 logger.info("\tx: {} - y: {}", clusters[i][0], clusters[i][1]);
520 originalMeans[i * 2] = clusters[i][0];
521 originalMeans[i * 2 + 1] = clusters[i][1];
522 }
523 final var originalFitness = fitnessFunction
524 .compute(new Genotype(new DoubleChromosome(k * 2, -100.0d, 100.0d, originalMeans)));
525 logger.info("Original fitness: {}", originalFitness);
526 final int[] originalClusterMembership = FitnessUtils.assignDataToClusters(data, distances, clusters);
527 IOUtils.persistDataPoints(data, originalClusterMembership, baseDir + "clustering-result-original.csv");
528 IOUtils.persistClusters(clusters, baseDir + "clustering-result-clusters-original.csv");
529
530 logger.info("kmeans output:");
531
532 final double[] kmeansClusters = new double[numClusters * 2];
533 for (int i = 0; i < clusterResults.size(); i++) {
534 final CentroidCluster<LocationWrapper> centroidCluster = clusterResults.get(i);
535 logger.info("\t{}", centroidCluster.getCenter());
536 kmeansClusters[i * 2] = centroidCluster.getCenter().getPoint()[0];
537 kmeansClusters[i * 2 + 1] = centroidCluster.getCenter().getPoint()[1];
538 }
539 final var kmeansGenotype = new Genotype(new DoubleChromosome(k * 2, -100.0d, 100.0d, kmeansClusters));
540 final var kmeansFitness = fitnessFunction.compute(kmeansGenotype);
541 logger.info("kmeans fitness: {}", kmeansFitness);
542
543 final int[] kmeansClusterMembership = FitnessUtils
544 .assignDataToClusters(data, distances, PhenotypeUtils.toPhenotype(kmeansGenotype));
545 IOUtils.persistDataPoints(data, kmeansClusterMembership, baseDir + "clustering-result-kmeans.csv");
546 IOUtils.persistClusters(
547 PhenotypeUtils.toPhenotype(kmeansGenotype),
548 baseDir + "clustering-result-clusters-kmeans.csv");
549
550 logger.info("Done");
551 }
552 }