001package conexp.fx.core.algorithm.lattice;
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.Collection;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032import java.util.concurrent.AbstractExecutorService;
033import java.util.stream.Collectors;
034
035import com.google.common.base.Predicate;
036import com.google.common.base.Predicates;
037import com.google.common.collect.Collections2;
038import com.google.common.collect.Iterables;
039import com.google.common.collect.Iterators;
040import com.google.common.collect.Sets;
041
042import conexp.fx.core.collections.Collections3;
043import conexp.fx.core.collections.Pair;
044import conexp.fx.core.context.Concept;
045import conexp.fx.core.context.MatrixContext;
046import conexp.fx.core.layout.AdditiveConceptLayout;
047import conexp.fx.core.layout.ConceptMovement;
048import conexp.fx.core.layout.LayoutEvolution;
049import conexp.fx.core.layout.QualityMeasure;
050import conexp.fx.gui.task.TimeTask;
051import conexp.fx.gui.util.Platform2;
052import javafx.collections.ObservableSet;
053import javafx.geometry.Point3D;
054
055public final class IFox1<G, M> {
056
057  private enum Type {
058    EQUIVALENT,
059    REDUCIBLE,
060    IRREDUCIBLE;
061  }
062
063  private static final class Attribute<M> {
064
065    private final Type   type;
066    private final Set<M> equivalents;
067
068    @SafeVarargs
069    private Attribute(final Type type, final Collection<M>... equivalents) {
070      this.type = type;
071      if (equivalents.length == 0)
072        this.equivalents = Collections.<M> emptySet();
073      else
074        this.equivalents = new HashSet<M>(equivalents[0]);
075    }
076  }
077
078  private static final <G, M> Attribute<M> attribute(final MatrixContext<G, M> context, final M m) {
079    final int _m = context.selection.colHeads().indexOf(m);
080    for (Set<Integer> _n : context.selection._attributes)
081      if (_n.contains(_m))
082        if (_n.size() > 1)
083          return new Attribute<M>(
084              Type.EQUIVALENT,
085              Collections2.transform(
086                  Sets.difference(_n, Collections.singleton(_m)),
087                  context.selection.colHeads().indexGuava().inverse()));
088        else if (context.selection._irreducibleAttributes.contains(_n))
089          return new Attribute<M>(Type.IRREDUCIBLE);
090        else
091          return new Attribute<M>(Type.REDUCIBLE);
092    return null;
093  }
094
095  private static final <G, M> void adjust(
096      final AdditiveConceptLayout<G, M> layout,
097      final M m,
098      final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance,
099      final AbstractExecutorService tpe) {
100    LayoutEvolution<G, M>.Value v = new LayoutEvolution<G, M>(
101        layout,
102        layout.lattice.attributeConcepts.get(m),
103        ConceptMovement.LABEL_SEED,
104        2d,
105        2d,
106        1,
107        1,
108        1,
109        conflictDistance,
110        tpe).calculate();
111    layout.updateSeeds(v.seedsG, v.seedsM);
112//    v =
113//        new LayoutEvolution<G, M>(
114//            layout,
115//            v.hint,
116//            ConceptMovement.INTENT_CHAIN_SEEDS,
117//            4d,
118//            4d,
119//            2,
120//            2,
121//            1,
122//            conflictDistance,
123//            tpe).calculate();
124//    layout.updateSeeds(v.seeds);
125  }
126
127  public static final <G, M> TimeTask<Void> select(
128      final String id,
129      final AdditiveConceptLayout<G, M> layout,
130      final M m,
131      final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance,
132      final AbstractExecutorService tpe) {
133    return new TimeTask<Void>(id + " - iFox Select") {
134
135      private MatrixContext<G, M> I;
136      private MatrixContext<G, M> IuJ;
137      private Set<G>              mJ;
138      private Attribute<M>        attribute;
139
140      protected Void call() {
141        updateProgress(0d, 1d);
142        if (isCancelled())
143          return null;
144        updateMessage("Calculating Incremental Update...");
145        I = layout.lattice.context.selection;
146        mJ = new HashSet<G>(layout.lattice.context.col(m));
147        layout.lattice.context.selectAttribute(m);
148        IuJ = layout.lattice.context.selection;
149        attribute = attribute(layout.lattice.context.selection, m);
150        switch (attribute.type) {
151        case EQUIVALENT:
152          equivalent();
153          break;
154        case REDUCIBLE:
155          reducible();
156          break;
157        case IRREDUCIBLE:
158          irreducible();
159          break;
160        }
161        layout.invalidate();
162        layout.lattice.pushAllChangedEvent();
163        updateProgress(1d, 1d);
164        return null;
165      }
166
167      private void equivalent() {
168        reducible();
169      }
170
171      private void reducible() {
172        // modify only label and intents
173        for (final Concept<G, M> c : layout.lattice.rowHeads())
174          if (mJ.containsAll(c.extent())) {
175            Platform2.runOnFXThread(new Runnable() {
176
177              public void run() {
178                c.intent().add(m);
179              }
180            });
181            if (mJ.size() == c.extent().size())
182              synchronized (layout.lattice.attributeConcepts) {
183                layout.lattice.attributeConcepts.put(m, c);
184              }
185          }
186      }
187
188      private void irreducible() {
189        // full update
190        updateMessage("Determining old, varying and generating concepts...");
191        updateProgress(0.1d, 1d);
192//        final Set<Concept<G, M>> olds = new HashSet<Concept<G, M>>();
193//        final Set<Concept<G, M>> vars = new HashSet<Concept<G, M>>();
194//        final Set<Concept<G, M>> gens = new HashSet<Concept<G, M>>();
195//        final Predicate<Concept<G, M>> varPredicate = new Predicate<Concept<G, M>>() {
196//
197//          public final boolean apply(final Concept<G, M> c) {
198//            return mJ.containsAll(c.extent());
199//          }
200//        };
201//        final Predicate<Concept<G, M>> genPredicate = new Predicate<Concept<G, M>>() {
202//
203//          public final boolean apply(final Concept<G, M> c) {
204//            return I.rowAnd(Sets.intersection(c.extent(), mJ)).equals(c.intent());
205//          }
206//        };
207//        vars.addAll(Collections2.filter(layout.lattice.rowHeads(), varPredicate));
208//        olds.addAll(Sets.difference(layout.lattice.rowHeads(), vars));
209//        gens.addAll(Collections2.filter(olds, genPredicate));
210        final Set<Concept<G, M>> vars = layout.lattice
211            .rowHeads()
212            .parallelStream()
213            .filter(c -> mJ.containsAll(c.extent()))
214            .collect(Collectors.toSet());
215        final Set<Concept<G, M>> olds =
216            layout.lattice.rowHeads().parallelStream().filter(c -> !vars.contains(c)).collect(Collectors.toSet());
217        final Set<Concept<G, M>> gens =
218            olds.parallelStream().filter(c -> I.rowAnd(Sets.intersection(c.extent(), mJ)).equals(c.intent())).collect(
219                Collectors.toSet());
220        final Map<Concept<G, M>, Concept<G, M>> news = new HashMap<Concept<G, M>, Concept<G, M>>(gens.size());
221        updateMessage("Updating varying concepts...");
222        updateProgress(0.2d, 1d);
223        for (final Concept<G, M> v : vars) {
224          Platform2.runOnFXThread(new Runnable() {
225
226            public void run() {
227              v.intent().add(m);
228            }
229          });
230          for (Concept<G, M> g : gens)
231            layout.lattice.remove(v, g);
232        }
233        updateMessage("Creating new concepts...");
234        updateProgress(0.3d, 1d);
235        double i = 0d;
236        for (final Concept<G, M> g : gens) {
237          updateProgress(0.3d + 0.25d * (i++ / (double) gens.size()), 1d);
238          final HashSet<G> gol = new HashSet<G>(layout.lattice.objectLabels(g));
239          final Concept<G, M> n = g.clone();
240          n.extent().retainAll(mJ);
241          n.intent().add(m);
242          news.put(n, g);
243          synchronized (layout.generators) {
244            layout.generators.put(n, g);
245          }
246          layout.lattice.rowHeads().add(n);
247          layout.lattice.addFast(n, g);
248          if (g.extent().containsAll(mJ))
249            synchronized (layout.lattice.attributeConcepts) {
250              layout.lattice.attributeConcepts.put(m, n);
251            }
252          for (G ol : Sets.intersection(gol, mJ))
253            synchronized (layout.lattice.objectConcepts) {
254              layout.lattice.objectConcepts.put(ol, n);
255            }
256          for (final Concept<G, M> v : vars)
257            if (v.smaller(g) && Sets.filter(Sets.union(gens, vars), new Predicate<Concept<G, M>>() {
258
259              public final boolean apply(final Concept<G, M> c) {
260                return v.smaller(c) && c.smaller(g);
261              }
262            }).isEmpty())
263              layout.lattice.addFast(v, n);
264          // layout.invalidate();
265        }
266        updateMessage("Creating new concepts neighborhood...");
267        updateProgress(0.55d, 1d);
268        i = 0d;
269        for (final Entry<Concept<G, M>, Concept<G, M>> n1 : news.entrySet())
270          for (final Entry<Concept<G, M>, Concept<G, M>> n2 : news.entrySet()) {
271            updateProgress(0.55d + 0.25d * (i++ / Math.pow(news.size(), 2)), 1d);
272            if (n1.getValue().smaller(n2.getValue()) && Sets.filter(gens, new Predicate<Concept<G, M>>() {
273
274              public final boolean apply(final Concept<G, M> c) {
275                return n1.getValue().smaller(c) && c.smaller(n2.getValue());
276              }
277            }).isEmpty())
278              layout.lattice.addFast(n1.getKey(), n2.getKey());
279          }
280        // layout.invalidate();
281        updateMessage("Updating seeds...");
282        updateProgress(0.8d, 1d);
283        synchronized (layout.seedsM) {
284          updateMessage("Determining new Reducibles...");
285          for (M n : layout.seedsM.keySet()) {
286            final int _n = IuJ.colHeads().indexOf(n);
287            final Set<Integer> eq = Iterables.find(IuJ._attributes, new Predicate<Collection<Integer>>() {
288
289              public final boolean apply(final Collection<Integer> c) {
290                return c.contains(_n);
291              }
292            });
293            if (!IuJ._irreducibleAttributes.contains(eq)) {
294              updateMessage("Backup seed: " + n);
295              final Point3D oldSeed = layout.seedsM.remove(n);
296              layout.seedHistoryM.put(n, oldSeed);
297            }
298          }
299          // final HashSet<M> currentIrreducibles = new HashSet<M>(layout.seeds.keySet());
300          // for (M s : currentIrreducibles)
301          // if (layout.lattice.row(layout.lattice.attributeConcepts.get(s)).size() != 1)
302          if (layout.seedHistoryM.containsKey(m) && !layout.seedsM.values().contains(layout.seedHistoryM.get(m))) {
303            updateMessage("Restoring seed: " + m);
304            layout.seedsM.put(m, layout.seedHistoryM.get(m));
305          } else {
306            updateMessage("Computing random seed: " + m);
307            layout.seedsM.put(m, new Point3D(2d * Math.random() - 1d, 0.5d + Math.random(), 0));
308          }
309          updateProgress(0.9d, 1d);
310          updateMessage("Adjusting Layout...");
311//          adjust(layout, m, conflictDistance, tpe);
312        }
313      }
314    };
315  }
316
317  public static final <G, M> TimeTask<Void> ignore(
318      final String id,
319      final AdditiveConceptLayout<G, M> layout,
320      final M m,
321      final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance,
322      final AbstractExecutorService tpe) {
323    return new TimeTask<Void>(id + " - iFox Ignore") {
324
325      private MatrixContext<G, M> I;
326      private Attribute<M>        attribute;
327
328      protected Void call() {
329        updateProgress(0d, 1d);
330        if (isCancelled())
331          return null;
332        updateMessage("Calculating Incremental Update...");
333        attribute = attribute(layout.lattice.context.selection, m);
334        layout.lattice.context.deselectAttribute(m);
335        I = layout.lattice.context.selection;
336        switch (attribute.type) {
337        case EQUIVALENT:
338          equivalent();
339          break;
340        case REDUCIBLE:
341          reducible();
342          break;
343        case IRREDUCIBLE:
344          irreducible();
345          break;
346        }
347        layout.invalidate();
348        layout.lattice.pushAllChangedEvent();
349        updateProgress(1d, 1d);
350        return null;
351      }
352
353      private void equivalent() {
354        // modify only label and intents
355        synchronized (layout.lattice.attributeConcepts) {
356          layout.lattice.attributeConcepts.remove(m);
357        }
358        for (Concept<G, M> c : layout.lattice.rowHeads())
359          Platform2.runOnFXThread(new Runnable() {
360
361            public void run() {
362              c.intent().remove(m);
363            }
364          });
365        synchronized (layout.seedsM) {
366          final Point3D seed = layout.seedsM.remove(m);
367          if (seed != null) {
368            layout.seedHistoryM.put(m, seed);
369            final M n = attribute.equivalents.iterator().next();
370            layout.seedsM.put(n, seed);
371          }
372        }
373      }
374
375      private void reducible() {
376        // modify only label and intents
377        synchronized (layout.lattice.attributeConcepts) {
378          layout.lattice.attributeConcepts.remove(m);
379        }
380        for (Concept<G, M> c : layout.lattice.rowHeads())
381          Platform2.runOnFXThread(new Runnable() {
382
383            public void run() {
384              c.intent().remove(m);
385            }
386          });
387      }
388
389      private void irreducible() {
390        // full update
391        updateMessage("Determining old, varying and generating concepts...");
392        updateProgress(0.1d, 1d);
393        final Set<Concept<G, M>> olds = new HashSet<Concept<G, M>>();
394        final Set<Concept<G, M>> vars = new HashSet<Concept<G, M>>();
395        final Set<Concept<G, M>> _news = new HashSet<Concept<G, M>>();
396        final Map<Concept<G, M>, Concept<G, M>> news = new HashMap<Concept<G, M>, Concept<G, M>>();
397        final Set<Concept<G, M>> oldNonGens = new HashSet<Concept<G, M>>();
398        final Predicate<Concept<G, M>> oldPredicate = new Predicate<Concept<G, M>>() {
399
400          public final boolean apply(final Concept<G, M> c) {
401            return !c.intent().contains(m);
402          }
403        };
404        final Predicate<Concept<G, M>> varPredicate = new Predicate<Concept<G, M>>() {
405
406          public final boolean apply(final Concept<G, M> c) {
407            return !olds.contains(c)
408                && c.extent().equals(I.colAnd(Sets.difference(c.intent(), Collections.singleton(m))));
409          }
410        };
411        olds.addAll(Collections2.filter(layout.lattice.rowHeads(), oldPredicate));
412        vars.addAll(Collections2.filter(layout.lattice.rowHeads(), varPredicate));
413        _news.addAll(Sets.difference(layout.lattice.rowHeads(), Sets.union(olds, vars)));
414        updateMessage("Computing generating concepts...");
415        updateProgress(0.2d, 1d);
416        synchronized (layout.lattice.attributeConcepts) {
417          layout.lattice.attributeConcepts.remove(m);
418        }
419        for (Concept<G, M> n : _news) {
420          final ObservableSet<M> intent = n.intent();
421          final Predicate<Concept<G, M>> genPredicate = new Predicate<Concept<G, M>>() {
422
423            public final boolean apply(final Concept<G, M> c) {
424              return c.intent().equals(Sets.difference(intent, Collections.singleton(m)));
425            }
426          };
427          final Concept<G, M> g = Iterators.find(olds.iterator(), genPredicate);
428          synchronized (layout.lattice.objectConcepts) {
429            final Set<G> nol = new HashSet<G>(layout.lattice.objectLabels(n));
430            for (G ol : nol)
431              layout.lattice.objectConcepts.put(ol, g);
432          }
433          news.put(n, g);
434          synchronized (layout.generators) {
435            layout.generators.put(n, g);
436          }
437        }
438        oldNonGens.addAll(Collections3.difference(olds, news.values()));
439        // layout.invalidate();
440        updateMessage("Updating varying concepts...");
441        updateProgress(0.3d, 1d);
442        for (Concept<G, M> v : vars)
443          Platform2.runOnFXThread(new Runnable() {
444
445            public void run() {
446              v.intent().remove(m);
447            }
448          });
449        for (final Entry<Concept<G, M>, Concept<G, M>> n : news.entrySet())
450          for (final Concept<G, M> v : vars) {
451            final Predicate<Concept<G, M>> vnIntervalPredicate = new Predicate<Concept<G, M>>() {
452
453              public final boolean apply(final Concept<G, M> c) {
454                return v.smaller(c) && c.smaller(n.getValue());
455              }
456            };
457            if (layout.lattice.contains(v, n.getKey()) && Sets.filter(oldNonGens, vnIntervalPredicate).isEmpty())
458              layout.lattice.addFast(v, n.getValue());
459          }
460        // layout.invalidate();
461        updateMessage("Removing concepts...");
462        // layout.lattice.rowHeads().removeAll(news.keySet());
463        for (Concept<G, M> y : news.keySet()) {
464          for (Concept<G, M> z : layout.lattice.row(y))
465            layout.lattice.remove(y, z);
466          for (Concept<G, M> z : layout.lattice.col(y))
467            layout.lattice.remove(z, y);
468          layout.lattice.rowHeads().remove(y);
469        }
470        updateMessage("Updating seeds...");
471        updateProgress(0.6d, 1d);
472        synchronized (layout.seedsM) {
473          updateMessage("Backup seed: " + m);
474          final Point3D oldSeed = layout.seedsM.remove(m);
475          layout.seedHistoryM.put(m, oldSeed);
476          Set<Integer> _seeds =
477              new HashSet<Integer>(Collections2.transform(layout.seedsM.keySet(), I.colHeads().indexGuava()));
478          for (Set<Integer> eq : I._irreducibleAttributes)
479            if (!Iterables.any(_seeds, Predicates.in(eq))) {
480              // introduce new seed
481
482              // interate over all possible attributes in the equivalence class!
483//              final M n = Collections2.transform(eq, I.colHeads().index().inverse()).iterator().next();
484              for (M n : Collections2.transform(eq, I.colHeads().indexGuava().inverse())) {
485                // if (true) {
486                // read seed vector from current layout
487                final Concept<G, M> nc = I.attributeConcept(n);
488                if (layout.getPosition(nc) != null) {
489                  final Point3D np = layout.getOrAddPosition(nc).getValue().add(oldSeed);
490                  final Point3D nnp =
491                      layout.getOrAddPosition(Iterables.getOnlyElement(layout.lattice.row(nc))).getValue();
492                  final Point3D seed = np.subtract(nnp);
493                  layout.seedsM.put(n, seed);
494                  updateProgress(0.8d, 1d);
495                  updateMessage("Adjusting Layout...");
496//                adjust(layout, n, conflictDistance, tpe);
497                  // }
498                  // if (layout.seedHistory.containsKey(n)) {
499                  // updateMessage("Restoring seed: " + n);
500                  // final Point3D seedBackup = layout.seedHistory.get(n);
501                  // layout.seeds.put(n, seedBackup);
502                  // } else {
503                  // updateMessage("Computing random seed: " + n);
504                  // layout.seeds.put(n, new Point3D(2d * Math.random() - 1d, 0.5d + Math.random(), 0));
505                  // adjust(layout, n);
506                  // }
507                  break;
508                }
509              }
510            }
511          // final HashSet<M> currentReducibles =
512          // new HashSet<M>(Sets.difference(I.colHeads(), layout.seeds.keySet()));
513          // for (M s : currentReducibles)
514          // if (Sets.difference(layout.lattice.row(layout.lattice.attributeConcepts.get(s)), news.keySet()).size() ==
515          // 1)
516        }
517        updateProgress(0.9d, 1d);
518      }
519    };
520  }
521}