001package conexp.fx.core.layout; 002 003/* 004 * #%L 005 * Concept Explorer FX 006 * %% 007 * Copyright (C) 2010 - 2023 Francesco Kriegel 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.NoSuchElementException; 031import java.util.Set; 032import java.util.concurrent.ExecutionException; 033import java.util.concurrent.ExecutorService; 034import java.util.concurrent.Future; 035 036import com.google.common.collect.Iterables; 037import com.google.common.collect.Iterators; 038 039import conexp.fx.core.collections.Pair; 040import conexp.fx.core.context.Concept; 041import conexp.fx.gui.task.TimeTask; 042import javafx.beans.property.DoubleProperty; 043import javafx.beans.property.SimpleDoubleProperty; 044import javafx.beans.value.ChangeListener; 045import javafx.beans.value.ObservableValue; 046import javafx.collections.FXCollections; 047import javafx.collections.ObservableSet; 048import javafx.geometry.BoundingBox; 049import javafx.geometry.Point3D; 050import javafx.geometry.Rectangle2D; 051 052public final class LayoutEvolution<G, M> { 053 054 public static final <G, M> TimeTask<Void> calculate(final LayoutEvolution<G, M> qualityChart) { 055 return new TimeTask<Void>("Quality Measure Chart") { 056 057 @Override 058 protected final Void call() { 059 updateProgress(0d, 1d); 060 if (isCancelled()) 061 return null; 062 updateMessage("Simulating Concept Movement..."); 063 qualityChart.progressProperty.addListener(new ChangeListener<Number>() { 064 065 @Override 066 public final void changed( 067 final ObservableValue<? extends Number> observable, 068 final Number oldValue, 069 final Number newValue) { 070 updateProgress(newValue.doubleValue(), 1d); 071 } 072 }); 073 qualityChart.calculate(); 074 updateProgress(1d, 1d); 075 return null; 076 } 077 }; 078 } 079 080 public final class Value implements Comparable<Value> { 081 082 public final Point3D delta; 083 public final Rectangle2D rectangle; 084 public final double result; 085 public final Concept<G, M> hint; 086 public final Map<G, Point3D> seedsG; 087 public final Map<M, Point3D> seedsM; 088 089 private Value( 090 final Point3D delta, 091 final Rectangle2D rectangle, 092 final Pair<Concept<G, M>, Double> result, 093 final Map<G, Point3D> seedsG, 094 final Map<M, Point3D> seedsM) { 095 this(delta, rectangle, result.second(), result.first(), seedsG, seedsM); 096 } 097 098 private Value( 099 final Point3D delta, 100 final Rectangle2D rectangle, 101 final double result, 102 final Concept<G, M> hint, 103 final Map<G, Point3D> seedsG, 104 final Map<M, Point3D> seedsM) { 105 super(); 106 this.delta = delta; 107 this.rectangle = rectangle; 108 this.result = result; 109 this.hint = hint; 110 this.seedsG = seedsG; 111 this.seedsM = seedsM; 112 } 113 114 @Override 115 public final int compareTo(final Value other) { 116 return (int) Math.signum(other.result - this.result); 117 } 118 } 119 120 private final ExecutorService tpe; 121 private final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance; 122 private final AdditiveConceptLayout<G, M> layout; 123 private final Concept<G, M> concept; 124 private final ConceptMovement movement; 125 private double minY; 126 private final int toSide; 127 private final int zoomInto; 128 private final int steps; 129 public final DoubleProperty progressProperty = new SimpleDoubleProperty(0d); 130 public Value best; 131 public final ObservableSet<Value> values = 132 FXCollections.observableSet(new HashSet<Value>()); 133 private final List<Value> lastValues; 134 private final Set<Value> nextValues = new HashSet<Value>(); 135 private final double numPerDim; 136 137 public LayoutEvolution( 138 final AdditiveConceptLayout<G, M> layout, 139 final Concept<G, M> concept, 140 final ConceptMovement movement, 141 final double widthFactor, 142 final double heightFactor, 143 final int toSide, 144 final int steps, 145 final int zoomInto, 146 final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance, 147 final ExecutorService tpe) { 148 super(); 149 this.layout = layout; 150 this.concept = concept; 151 this.movement = movement; 152 this.toSide = toSide; 153 this.numPerDim = 1d + 2d * toSide; 154 this.zoomInto = zoomInto; 155 this.steps = steps; 156 this.conflictDistance = conflictDistance; 157 this.tpe = tpe; 158 this.lastValues = new ArrayList<Value>((int) Math.pow(1d + 2d * toSide, 2d)); 159 final BoundingBox layoutBounds = layout.getCurrentBoundingBox(false, false); 160 final double width = widthFactor * layoutBounds.getWidth(); 161 final double height = heightFactor * layoutBounds.getHeight(); 162 final Point3D origin = layout.getPosition(concept).getValue(); 163 final Rectangle2D rectangle = 164 new Rectangle2D(origin.getX() - width / 2d, origin.getY() - height / 2d, width, height); 165 final Value firstValue = new Value( 166 Point3D.ZERO, 167 rectangle, 168 conflictDistance.apply(layout), 169 // layout.clone()._seedsG, 170 // layout.clone()._seedsM); 171 layout.clone().seedsG, 172 layout.clone().seedsM); 173 if (concept.equals(layout.lattice.context.selection.topConcept())) { 174 best = firstValue; 175 return; 176 } 177 switch (movement) { 178 case LABEL_SEED: 179 try { 180 minY = layout.getPosition(Iterators.getOnlyElement(layout.lattice.row(concept).iterator())).getValue().getY() 181 - origin.getY(); 182 } catch (IllegalArgumentException | NoSuchElementException e) { 183 best = firstValue; 184 return; 185 } 186 break; 187 case INTENT_SEEDS: 188 case INTENT_CHAIN_SEEDS: 189 default: 190 minY = -origin.getY(); 191 break; 192 } 193 values.add(firstValue); 194 nextValues.add(firstValue); 195 } 196 197 public final Value calculate() { 198 if (!nextValues.isEmpty()) 199 for (int step = 0; step < steps; step++) 200 zoomIn(); 201 return best; 202 } 203 204 private final void zoomIn() { 205 final Set<Future<?>> futures = new HashSet<Future<?>>(); 206 for (Value nextValue : nextValues) { 207 values.remove(nextValue); 208 final double tileWidth = nextValue.rectangle.getWidth() / numPerDim; 209 final double tileHeight = nextValue.rectangle.getHeight() / numPerDim; 210 final Value value = new Value( 211 nextValue.delta, 212 new Rectangle2D( 213 nextValue.rectangle.getMinX() + toSide * tileWidth, 214 nextValue.rectangle.getMinY() + toSide * tileHeight, 215 tileWidth, 216 tileHeight), 217 nextValue.result, 218 nextValue.hint, 219 nextValue.seedsG, 220 nextValue.seedsM); 221 values.add(value); 222 lastValues.add(value); 223 for (double i = -toSide; i <= toSide; i++) 224 for (double j = -toSide; j <= toSide; j++) 225 if (i != 0d || j != 0d) 226 futures.add(tpe.submit(create(nextValue, i, j, tileWidth, tileHeight))); 227 } 228 for (Future<?> f : futures) 229 try { 230 f.get(); 231 } catch (InterruptedException | ExecutionException e) { 232 e.printStackTrace(); 233 } 234 nextValues.clear(); 235 Collections.sort(lastValues); 236 for (Value nextValue : Iterables.limit(lastValues, zoomInto)) 237 nextValues.add(nextValue); 238 lastValues.clear(); 239 } 240 241 private final Runnable 242 create(final Value nextValue, final double i, final double j, final double tileWidth, final double tileHeight) { 243 return new Runnable() { 244 245 @Override 246 public void run() { 247 final Point3D delta = nextValue.delta.add(i * tileWidth, j * tileHeight, 0); 248 if (delta.getY() < minY) 249 return; 250 final AdditiveConceptLayout<G, M> movedLayout = layout.clone(); 251 movedLayout.move(concept, movement, delta); 252 final Value value = new Value( 253 delta, 254 new Rectangle2D( 255 nextValue.rectangle.getMinX() + (i + toSide) * tileWidth, 256 nextValue.rectangle.getMinY() + (j + toSide) * tileHeight, 257 tileWidth, 258 tileHeight), 259 conflictDistance.apply(movedLayout), 260 // movedLayout._seedsG, 261 // movedLayout._seedsM); 262 movedLayout.seedsG, 263 movedLayout.seedsM); 264 synchronized (values) { 265 values.add(value); 266 } 267 synchronized (lastValues) { 268 lastValues.add(value); 269 } 270// synchronized (best) { 271 if (best == null || value.result >= best.result) 272 best = value; 273// } 274 } 275 }; 276 } 277}