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}