001package conexp.fx.core.layout; 002 003import java.util.HashMap; 004 005/* 006 * #%L 007 * Concept Explorer FX 008 * %% 009 * Copyright (C) 2010 - 2023 Francesco Kriegel 010 * %% 011 * This program is free software: you can redistribute it and/or modify 012 * it under the terms of the GNU General Public License as 013 * published by the Free Software Foundation, either version 3 of the 014 * License, or (at your option) any later version. 015 * 016 * This program is distributed in the hope that it will be useful, 017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 019 * GNU General Public License for more details. 020 * 021 * You should have received a copy of the GNU General Public 022 * License along with this program. If not, see 023 * <http://www.gnu.org/licenses/gpl-3.0.html>. 024 * #L% 025 */ 026 027import java.util.HashSet; 028import java.util.Map; 029import java.util.Random; 030import java.util.Set; 031import java.util.concurrent.ExecutionException; 032import java.util.concurrent.Future; 033 034import org.ujmp.core.util.RandomSimple; 035 036import com.google.common.collect.Collections2; 037import com.google.common.collect.Iterables; 038 039import conexp.fx.core.collections.BitSetFX; 040import conexp.fx.core.collections.Collections3; 041import conexp.fx.core.context.Concept; 042import conexp.fx.core.context.MatrixContext; 043import conexp.fx.core.layout.AdditiveConceptLayout.Type; 044import conexp.fx.gui.ConExpFX; 045import conexp.fx.gui.dataset.FCADataset; 046import conexp.fx.gui.task.TimeTask; 047import javafx.beans.property.DoubleProperty; 048import javafx.beans.property.SimpleDoubleProperty; 049import javafx.geometry.Point3D; 050 051public final class GeneticLayouter<G, M> { 052 053 public static final <G, M> TimeTask<Void> initialSeeds(final FCADataset<G, M> dataset) { 054 return new TimeTask<Void>(dataset, "Initial Seeds and Labels") { 055 056 protected final Void call() { 057 updateProgress(0d, 1d); 058 if (isCancelled()) 059 return null; 060 final Random rng = new RandomSimple(); 061 updateMessage("Computing Infimum Irreducibles..."); 062 final Set<G> supremumIrreducibles = dataset.layout.lattice.context.selection.supremumIrreducibles(); 063 final Set<M> infimumIrreducibles = dataset.layout.lattice.context.selection.infimumIrreducibles(); 064 updateProgress(0.2d, 1d); 065 updateProgress(0.3d, 1d); 066 updateMessage("Generating Layered Random Seeds..."); 067 final Map<G, Point3D> randomSeedsG = new HashMap<G, Point3D>(); 068 final Map<M, Point3D> randomSeedsM = new HashMap<M, Point3D>(); 069 for (G g : supremumIrreducibles) 070 randomSeedsG.put(g, new Point3D(2d * rng.nextDouble() - 1d, 1, 0)); 071 for (M m : infimumIrreducibles) 072 randomSeedsM.put(m, new Point3D(2d * rng.nextDouble() - 1d, 1, 0)); 073 updateProgress(0.4d, 1d); 074 dataset.layout.updateSeeds(randomSeedsG, randomSeedsM); 075 updateProgress(0.5d, 1d); 076 updateMessage("Computing Attribute Labels..."); 077 for (Concept<G, M> c : dataset.layout.lattice.rowHeads()) { 078 final Set<M> attributeLabels = 079 new HashSet<M>(dataset.layout.lattice.context.selection.attributeLabels(c.extent(), c.intent())); 080 synchronized (dataset.layout.lattice.attributeConcepts) { 081 for (M m : attributeLabels) 082 dataset.layout.lattice.attributeConcepts.put(m, c); 083 } 084 } 085 updateProgress(0.75d, 1d); 086 updateMessage("Computing Object Labels..."); 087 for (Concept<G, M> c : dataset.layout.lattice.rowHeads()) { 088 final Set<G> objectLabels = 089 new HashSet<G>(dataset.layout.lattice.context.selection.objectLabels(c.extent(), c.intent())); 090 synchronized (dataset.layout.lattice.objectConcepts) { 091 for (G g : objectLabels) 092 dataset.layout.lattice.objectConcepts.put(g, c); 093 } 094 } 095 updateProgress(1d, 1d); 096 return null; 097 } 098 }; 099 } 100 101 public static final <G, M> TimeTask<Void> seeds( 102 final FCADataset<G, M> dataset, 103 final boolean includingLayout, 104 final int generationCount, 105 final int populationSize) { 106 return new TimeTask<Void>(dataset, "Genetic Layouter") { 107 108 private final DoubleProperty evolutionaryProgressProperty = new SimpleDoubleProperty(0d); 109 private final Set<AdditiveConceptLayout<G, M>> layouts = 110 new HashSet<AdditiveConceptLayout<G, M>>(populationSize); 111 private double currentQuality = -1d; 112 private AdditiveConceptLayout<G, M> currentBest; 113 private ChainDecomposer<Set<Integer>> chainDecomposerG; 114 private ChainDecomposer<Set<Integer>> chainDecomposerM; 115 private Random rng = new RandomSimple(); 116 117 @Override 118 protected final void updateProgress(double workDone, double max) { 119 super.updateProgress(workDone, max); 120 evolutionaryProgressProperty.set(workDone / max); 121 }; 122 123 @Override 124 protected final Void call() { 125 updateProgress(0d, 1d); 126 if (isCancelled()) 127 return null; 128 updateProgress(0.1d, 1d); 129 updateMessage("Decomposing Concept Lattice..."); 130 final MatrixContext<Set<Integer>, Set<Integer>> cxt = dataset.layout.lattice.context.selection._reduced.clone(); 131 chainDecomposerG = new ChainDecomposer<Set<Integer>>(cxt.objectQuasiOrder().neighborhood()); 132 chainDecomposerM = new ChainDecomposer<Set<Integer>>(cxt.attributeQuasiOrder().neighborhood()); 133 if (includingLayout) { 134 layouts.add(dataset.layout.clone()); 135 currentBest = dataset.layout; 136 } 137 updateProgress(0.2d, 1d); 138 updateMessage("Setting up Evolution Engine..."); 139 initPopulation(); 140 updateProgress(0.3d, 1d); 141 updateMessage("Evolving Layout..."); 142 evolvePopulation(); 143 updateProgress(1d, 1d); 144 return null; 145 } 146 147 private final void initPopulation() { 148 final Set<Future<?>> futures = new HashSet<Future<?>>(); 149 for (int i = includingLayout ? 1 : 0; i < populationSize; i++) 150 futures.add(ConExpFX.instance.executor.tpe.submit(generate())); 151 for (Future<?> f : futures) 152 try { 153 f.get(); 154 } catch (InterruptedException | ExecutionException e) { 155 e.printStackTrace(); 156 } 157 } 158 159 private final Runnable generate() { 160 return new Runnable() { 161 162 @Override 163 public void run() { 164 if (dataset.conceptGraph.polar()) {} else { 165 final AdditiveConceptLayout<G, M> candidate = 166 new AdditiveConceptLayout<G, M>(dataset.layout.lattice, null, null, Type.HYBRID); 167 final Set<Set<Set<Integer>>> chainsG = chainDecomposerG.randomChainDecomposition(); 168 final Set<Set<Set<Integer>>> chainsM = chainDecomposerM.randomChainDecomposition(); 169 final int v = chainsG.size(); 170 if (v == 1) { 171 final Point3D seed = new Point3D(0d, 1d, 0d); 172 for (G g : Collections2 173 .transform(Iterables.getOnlyElement(chainsG), candidate.lattice.context.selection._firstObject)) 174 candidate.seedsG.put(g, seed); 175 } else { 176 final int vv = v * v; 177 final BitSetFX usedX = new BitSetFX(); 178 for (Set<Set<Integer>> chain : chainsG) { 179 int _x; 180 for (_x = rng.nextInt(vv + 1); usedX.contains(_x) || usedX.contains(_x + 1) 181 || (_x != 0 && usedX.contains(_x - 1)); _x = rng.nextInt(vv + 1)); 182 usedX.add(_x); 183 final int __x = _x - vv / 2; 184 final Point3D seed = new Point3D( 185 (double) __x, 186 (double) (rng.nextInt(Math.abs(__x + 1) + 1) + 1), 187 dataset.conceptGraph.threeDimensions() ? (double) (rng.nextInt(Math.abs(__x + 1) + 1) - __x / 2) 188 : 0d); 189 for (G g : Collections2.transform(chain, candidate.lattice.context.selection._firstObject)) 190 candidate.seedsG.put(g, seed); 191 } 192 } 193 final int w = chainsM.size(); 194 if (w == 1) { 195 final Point3D seed = new Point3D(0d, 1d, 0d); 196 for (M m : Collections2 197 .transform(Iterables.getOnlyElement(chainsM), candidate.lattice.context.selection._firstAttribute)) 198 candidate.seedsM.put(m, seed); 199 } else { 200 final int ww = w * w; 201 final BitSetFX usedX = new BitSetFX(); 202 for (Set<Set<Integer>> chain : chainsM) { 203 int _x; 204 for (_x = rng.nextInt(ww + 1); usedX.contains(_x) || usedX.contains(_x + 1) 205 || (_x != 0 && usedX.contains(_x - 1)); _x = rng.nextInt(ww + 1)); 206 usedX.add(_x); 207 final int __x = _x - ww / 2; 208 final Point3D seed = new Point3D( 209 (double) __x, 210 (double) (rng.nextInt(Math.abs(__x + 1) + 1) + 1), 211 dataset.conceptGraph.threeDimensions() ? (double) (rng.nextInt(Math.abs(__x + 1) + 1) - __x / 2) 212 : 0d); 213 for (M m : Collections2.transform(chain, candidate.lattice.context.selection._firstAttribute)) 214 candidate.seedsM.put(m, seed); 215 } 216 } 217 synchronized (layouts) { 218 layouts.add(candidate); 219 } 220 final double result = dataset.conflictDistance.apply(candidate).second(); 221 if (result > currentQuality || currentBest == null) { 222 currentQuality = result; 223 currentBest = candidate; 224 } 225 } 226 } 227 }; 228 } 229 230 private final AdditiveConceptLayout<G, M> 231 crossover(final AdditiveConceptLayout<G, M> l1, final AdditiveConceptLayout<G, M> l2) { 232 final AdditiveConceptLayout<G, M> l = l1.clone(); 233 return null; 234 } 235 236 private final void evolvePopulation() { 237 if (dataset.conceptGraph.polar()) {} else { 238 for (int i = 0; i < generationCount; i++) { 239// if (currentBest != dataset.layout) 240 dataset.layout.updateSeeds(currentBest.seedsG, currentBest.seedsM); 241 updateProgress(0.3d + 0.7d * ((double) i) / (double) generationCount, 1d); 242 updateMessage("Evolving Seeds: " + i + " of " + generationCount + " Generations..."); 243 evolveGeneration(); 244 } 245// try { 246// if (currentBest != dataset.layout) 247 dataset.layout.updateSeeds(currentBest.seedsG, currentBest.seedsM); 248// } catch (Exception e) { 249// System.err.println(currentBest); 250// System.err.println(dataset.layout); 251// e.printStackTrace(); 252// } 253 } 254 } 255 256 private final void evolveGeneration() { 257 if (dataset.conceptGraph.polar()) {} else { 258 currentQuality = -1d; 259 for (AdditiveConceptLayout<G, M> candidate : layouts) { 260 LayoutEvolution<G, M>.Value v = new LayoutEvolution<G, M>( 261 candidate, 262 dataset.conflictDistance.apply(candidate).first(), 263 ConceptMovement.INTENT_CHAIN_SEEDS, 264 4d, 265 4d, 266 2, 267 2, 268 1, 269 dataset.conflictDistance, 270 ConExpFX.instance.executor.tpe).calculate(); 271 if (!candidate.updateSeeds(v.seedsG, v.seedsM)) { 272// if (rng.nextBoolean()) { 273// final Concept<G, M> c = candidate.seedsM 274// .keySet() 275// .parallelStream() 276// .map(candidate.lattice.attributeConcepts::get) 277// .filter(cn -> cn != null) 278// .findAny() 279// .get(); 280//// candidate.lattice.attributeConcepts.get(Collections3.random(candidate.seeds.keySet(), rng)); 281// v = new LayoutEvolution<G, M>( 282// candidate, 283// c, 284// // TODO 285// // ConceptMovement.LABEL_CHAIN_SEEDS, 286// ConceptMovement.INTENT_CHAIN_SEEDS, 287// 4d, 288// 4d, 289// 2, 290// 2, 291// 1, 292// dataset.conflictDistance, 293// ConExpFX.instance.executor.tpe).calculate(); 294// } else 295 v = new LayoutEvolution<G, M>( 296 candidate, 297 Collections3.random(candidate.lattice.rowHeads(), rng), 298 ConceptMovement.INTENT_CHAIN_SEEDS, 299 4d, 300 4d, 301 2, 302 2, 303 1, 304 dataset.conflictDistance, 305 ConExpFX.instance.executor.tpe).calculate(); 306 candidate.updateSeeds(v.seedsG, v.seedsM); 307 } 308 if (v.result > currentQuality) { 309 currentQuality = v.result; 310 currentBest = candidate; 311 } 312 } 313 } 314 } 315 }; 316 } 317}