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.concurrent.ExecutorService;
034import java.util.function.Predicate;
035import java.util.stream.Collectors;
036import java.util.stream.Stream;
037
038import com.google.common.base.Predicates;
039import com.google.common.collect.Collections2;
040import com.google.common.collect.Iterables;
041import com.google.common.collect.Sets;
042
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 javafx.geometry.Point3D;
052
053public final class IFox2<G, M> {
054
055  private enum Type {
056    EQUIVALENT,
057    REDUCIBLE,
058    IRREDUCIBLE;
059  }
060
061  private static final class Attribute<M> {
062
063    private final Type   type;
064    private final Set<M> equivalents;
065
066    @SafeVarargs
067    private Attribute(final Type type, final Collection<M>... equivalents) {
068      this.type = type;
069      if (equivalents.length == 0)
070        this.equivalents = Collections.<M> emptySet();
071      else
072        this.equivalents = new HashSet<M>(equivalents[0]);
073    }
074  }
075
076  private static final <G, M> Attribute<M> attribute(final MatrixContext<G, M> context, final M m) {
077    final int _m = context.selection.colHeads().indexOf(m);
078    for (Set<Integer> _n : context.selection._attributes)
079      if (_n.contains(_m))
080        if (_n.size() > 1)
081          return new Attribute<M>(
082              Type.EQUIVALENT,
083              Collections2.transform(
084                  Sets.difference(_n, Collections.singleton(_m)),
085                  context.selection.colHeads().indexGuava().inverse()));
086        else if (context.selection._irreducibleAttributes.contains(_n))
087          return new Attribute<M>(Type.IRREDUCIBLE);
088        else
089          return new Attribute<M>(Type.REDUCIBLE);
090    return null;
091  }
092
093  private static final <G, M> void adjust(
094      final AdditiveConceptLayout<G, M> layout,
095      final M m,
096      final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance,
097      final AbstractExecutorService tpe) {
098    LayoutEvolution<G, M>.Value v = new LayoutEvolution<G, M>(
099        layout,
100        layout.lattice.attributeConcepts.get(m),
101        // TODO
102        // ConceptMovement.LABEL_SEED,
103        ConceptMovement.INTENT_CHAIN_SEEDS,
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 ExecutorService 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            c.intent().add(m);
176//            Platform2.runOnFXThread(() -> c.intent().add(m));
177            if (mJ.size() == c.extent().size())
178              synchronized (layout.lattice.attributeConcepts) {
179                layout.lattice.attributeConcepts.put(m, c);
180              }
181          }
182      }
183
184      private void irreducible() {
185        // full update
186        updateMessage("Determining old, varying and generating concepts...");
187        updateProgress(0.1d, 1d);
188        final Set<Concept<G, M>> vars =
189            layout.lattice.rowHeads().stream().filter(c -> mJ.containsAll(c.extent())).collect(Collectors.toSet());
190        final Set<Concept<G, M>> olds =
191            layout.lattice.rowHeads().stream().filter(c -> !vars.contains(c)).collect(Collectors.toSet());
192        final Set<Concept<G, M>> gens =
193            olds.stream().filter(c -> I.rowAnd(Sets.intersection(c.extent(), mJ)).equals(c.intent())).collect(
194                Collectors.toSet());
195        final Map<Concept<G, M>, Concept<G, M>> news = new HashMap<Concept<G, M>, Concept<G, M>>(gens.size());
196        updateMessage("Updating varying concepts...");
197        updateProgress(0.2d, 1d);
198        for (final Concept<G, M> v : vars) {
199          v.intent().add(m);
200//          Platform2.runOnFXThread(() -> v.intent().add(m));
201          for (final Concept<G, M> g : gens)
202            layout.lattice.remove(v, g);
203        }
204        updateMessage("Creating new concepts...");
205        updateProgress(0.3d, 1d);
206        double i = 0d;
207        for (final Concept<G, M> g : gens) {
208          updateProgress(0.3d + 0.25d * (i++ / (double) gens.size()), 1d);
209          final HashSet<G> gol = new HashSet<G>(layout.lattice.objectLabels(g));
210          final Concept<G, M> n = g.clone();
211          n.extent().retainAll(mJ);
212          n.intent().add(m);
213          news.put(n, g);
214          synchronized (layout.generators) {
215            layout.generators.put(n, g);
216          }
217          layout.lattice.rowHeads().add(n);
218          layout.lattice.addFast(n, g);
219          if (g.extent().containsAll(mJ))
220            synchronized (layout.lattice.attributeConcepts) {
221              layout.lattice.attributeConcepts.put(m, n);
222            }
223          for (G ol : Sets.intersection(gol, mJ))
224            synchronized (layout.lattice.objectConcepts) {
225              layout.lattice.objectConcepts.put(ol, n);
226            }
227          for (final Concept<G, M> v : vars)
228            if (v.smaller(g)
229                && Stream.concat(gens.stream(), vars.stream()).noneMatch(c -> v.smaller(c) && c.smaller(g)))
230              layout.lattice.addFast(v, n);
231          // layout.invalidate();
232        }
233        updateMessage("Creating new concepts neighborhood...");
234        updateProgress(0.55d, 1d);
235        i = 0d;
236        for (final Entry<Concept<G, M>, Concept<G, M>> n1 : news.entrySet())
237          for (final Entry<Concept<G, M>, Concept<G, M>> n2 : news.entrySet()) {
238            updateProgress(0.55d + 0.25d * (i++ / Math.pow(news.size(), 2)), 1d);
239            if (n1.getValue().smaller(n2.getValue())
240                && gens.stream().noneMatch(c -> n1.getValue().smaller(c) && c.smaller(n2.getValue())))
241              layout.lattice.addFast(n1.getKey(), n2.getKey());
242          }
243        // layout.invalidate();
244        updateMessage("Updating seeds...");
245        updateProgress(0.8d, 1d);
246        synchronized (layout.seedsM) {
247          updateMessage("Determining new Reducibles...");
248          for (M n : layout.seedsM.keySet()) {
249            final int _n = IuJ.colHeads().indexOf(n);
250            final Set<Integer> eq = IuJ._attributes.stream().filter(c -> c.contains(_n)).findAny().get();
251            if (!IuJ._irreducibleAttributes.contains(eq)) {
252              updateMessage("Backup seed: " + n);
253              final Point3D oldSeed = layout.seedsM.remove(n);
254              layout.seedHistoryM.put(n, oldSeed);
255            }
256          }
257          // final HashSet<M> currentIrreducibles = new HashSet<M>(layout.seeds.keySet());
258          // for (M s : currentIrreducibles)
259          // if (layout.lattice.row(layout.lattice.attributeConcepts.get(s)).size() != 1)
260          if (layout.seedHistoryM.containsKey(m) && !layout.seedsM.values().contains(layout.seedHistoryM.get(m))) {
261            updateMessage("Restoring seed: " + m);
262            layout.seedsM.put(m, layout.seedHistoryM.get(m));
263          } else {
264            updateMessage("Computing random seed: " + m);
265            layout.seedsM.put(m, new Point3D(1d + Math.random(), 0.5d + Math.random(), 0));
266          }
267        }
268        synchronized (layout.seedsG) {
269          IuJ._irreducibleObjects
270              .stream()
271              .filter(
272                  eq -> eq
273                      .stream()
274                      .map(IuJ.rowHeads()::get)
275                      .filter(((Predicate<G>) layout.seedsG::containsKey).negate())
276                      .findAny()
277                      .isPresent())
278              .map(IuJ._firstObject::apply)
279              .forEach(g -> layout.seedsG.put(g, new Point3D(1d + Math.random(), 0.5d + Math.random(), 0)));
280          layout.seedsG.keySet().removeIf(
281              g -> !IuJ._irreducibleObjects
282                  .contains(IuJ._objects.stream().filter(c -> c.contains(IuJ.rowHeads().indexOf(g))).findAny().get()));
283        }
284        updateProgress(0.9d, 1d);
285        updateMessage("Adjusting Layout...");
286//    adjust(layout, m, conflictDistance, tpe);
287      }
288    };
289  }
290
291  public static final <G, M> TimeTask<Void> ignore(
292      final String id,
293      final AdditiveConceptLayout<G, M> layout,
294      final M m,
295      final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance,
296      final ExecutorService tpe) {
297    return new TimeTask<Void>(id + " - iFox Ignore") {
298
299      private MatrixContext<G, M> I;
300      private Attribute<M>        attribute;
301
302      protected Void call() {
303        updateProgress(0d, 1d);
304        if (isCancelled())
305          return null;
306        updateMessage("Calculating Incremental Update...");
307        attribute = attribute(layout.lattice.context.selection, m);
308        layout.lattice.context.deselectAttribute(m);
309        I = layout.lattice.context.selection;
310        switch (attribute.type) {
311        case EQUIVALENT:
312          equivalent();
313          break;
314        case REDUCIBLE:
315          reducible();
316          break;
317        case IRREDUCIBLE:
318          irreducible();
319          break;
320        }
321        layout.lattice.pushAllChangedEvent();
322        layout.invalidate();
323        updateProgress(1d, 1d);
324        return null;
325      }
326
327      private void equivalent() {
328        // modify only label and intents
329        synchronized (layout.lattice.attributeConcepts) {
330          layout.lattice.attributeConcepts.remove(m);
331        }
332        for (Concept<G, M> c : layout.lattice.rowHeads())
333          c.intent().remove(m);
334//          Platform2.runOnFXThread(() -> c.intent().remove(m));
335        synchronized (layout.seedsM) {
336          final Point3D seed = layout.seedsM.remove(m);
337          if (seed != null) {
338            layout.seedHistoryM.put(m, seed);
339            final M n = attribute.equivalents.iterator().next();
340            layout.seedsM.put(n, seed);
341          }
342        }
343      }
344
345      private void reducible() {
346        // modify only label and intents
347        synchronized (layout.lattice.attributeConcepts) {
348          layout.lattice.attributeConcepts.remove(m);
349        }
350        for (Concept<G, M> c : layout.lattice.rowHeads())
351          c.intent().remove(m);
352//          Platform2.runOnFXThread(() -> c.intent().remove(m));
353      }
354
355      private void irreducible() {
356        // full update
357        updateMessage("Determining old, varying and generating concepts...");
358        updateProgress(0.1d, 1d);
359        final Set<Concept<G, M>> olds =
360            layout.lattice.rowHeads().stream().filter(c -> !c.intent().contains(m)).collect(Collectors.toSet());
361        final Set<Concept<G, M>> vars = layout.lattice
362            .rowHeads()
363            .stream()
364            .filter(
365                c -> !olds.contains(c)
366                    && c.extent().equals(I.colAnd(Sets.difference(c.intent(), Collections.singleton(m)))))
367            .collect(Collectors.toSet());
368        final Set<Concept<G, M>> _news = layout.lattice
369            .rowHeads()
370            .stream()
371            .filter(c -> !olds.contains(c) && !vars.contains(c))
372            .collect(Collectors.toSet());
373        updateMessage("Computing generating concepts...");
374        updateProgress(0.2d, 1d);
375        synchronized (layout.lattice.attributeConcepts) {
376          layout.lattice.attributeConcepts.remove(m);
377        }
378        final Map<Concept<G, M>, Concept<G, M>> news = new HashMap<Concept<G, M>, Concept<G, M>>();
379        for (Concept<G, M> n : _news) {
380          final Concept<G, M> g = olds
381              .stream()
382              // .filter(c -> c.intent().containsAll(n.intent()) && c.intent().size() == n.intent().size() - 1)
383              .filter(c -> c.intent().equals(Sets.difference(n.intent(), Collections.singleton(m))))
384              .findAny()
385              .get();
386          synchronized (layout.lattice.objectConcepts) {
387            final Set<G> nol = new HashSet<G>(layout.lattice.objectLabels(n));
388            for (G ol : nol)
389              layout.lattice.objectConcepts.put(ol, g);
390          }
391          news.put(n, g);
392          synchronized (layout.generators) {
393            layout.generators.put(n, g);
394          }
395        }
396        final Set<Concept<G, M>> oldNonGens =
397            olds.stream().filter(c -> !news.values().contains(c)).collect(Collectors.toSet());
398        // layout.invalidate();
399        updateMessage("Updating varying concepts...");
400        updateProgress(0.3d, 1d);
401        for (Concept<G, M> v : vars)
402          v.intent().remove(m);
403//          Platform2.runOnFXThread(() -> v.intent().remove(m));
404        for (final Entry<Concept<G, M>, Concept<G, M>> n : news.entrySet())
405          for (final Concept<G, M> v : vars)
406            if (layout.lattice.contains(v, n.getKey())
407                && oldNonGens.stream().noneMatch(c -> v.smaller(c) && c.smaller(n.getValue())))
408              layout.lattice.addFast(v, n.getValue());
409        // layout.invalidate();
410        updateMessage("Removing concepts...");
411        // layout.lattice.rowHeads().removeAll(news.keySet());
412        for (Concept<G, M> y : news.keySet()) {
413          for (Concept<G, M> z : layout.lattice.row(y))
414            layout.lattice.remove(y, z);
415          for (Concept<G, M> z : layout.lattice.col(y))
416            layout.lattice.remove(z, y);
417          layout.lattice.rowHeads().remove(y);
418        }
419        updateMessage("Updating seeds...");
420        updateProgress(0.6d, 1d);
421        synchronized (layout.seedsM) {
422          updateMessage("Backup seed: " + m);
423          // TODO: seed is sometimes not available in the map. check for a solution!
424          final Point3D oldSeed = layout.seedsM.remove(m);
425          layout.seedHistoryM.put(m, oldSeed);
426          Set<Integer> _seeds =
427              new HashSet<Integer>(Collections2.transform(layout.seedsM.keySet(), I.colHeads().indexGuava()));
428          for (Set<Integer> eq : I._irreducibleAttributes)
429            if (!Iterables.any(_seeds, Predicates.in(eq))) {
430              // introduce new seed
431              // interate over all possible attributes in the equivalence class!
432//              final M n = Collections2.transform(eq, I.colHeads().index().inverse()).iterator().next();
433              for (M n : Collections2.transform(eq, I.colHeads().indexGuava().inverse())) {
434                // if (true) {
435                // read seed vector from current layout
436                final Concept<G, M> nc = I.attributeConcept(n);
437                if (layout.getPosition(nc) != null) {
438                  final Point3D np = layout.getPosition(nc).getValue();// .add(oldSeed);
439                  final Point3D nnp = layout.getPosition(Iterables.getOnlyElement(layout.lattice.row(nc))).getValue();
440                  final Point3D seed = np.subtract(nnp);
441                  layout.seedsM.put(n, seed);
442                  updateProgress(0.8d, 1d);
443                  updateMessage("Adjusting Layout...");
444//                adjust(layout, n, conflictDistance, tpe);
445                  // }
446                  // if (layout.seedHistory.containsKey(n)) {
447                  // updateMessage("Restoring seed: " + n);
448                  // final Point3D seedBackup = layout.seedHistory.get(n);
449                  // layout.seeds.put(n, seedBackup);
450                  // } else {
451                  // updateMessage("Computing random seed: " + n);
452                  // layout.seeds.put(n, new Point3D(2d * Math.random() - 1d, 0.5d + Math.random(), 0));
453                  // adjust(layout, n);
454                  // }
455                  break;
456                }
457              }
458            }
459          // final HashSet<M> currentReducibles =
460          // new HashSet<M>(Sets.difference(I.colHeads(), layout.seeds.keySet()));
461          // for (M s : currentReducibles)
462          // if (Sets.difference(layout.lattice.row(layout.lattice.attributeConcepts.get(s)), news.keySet()).size() ==
463          // 1)
464        }
465        synchronized (layout.seedsG) {
466          I._irreducibleObjects
467              .stream()
468              .filter(
469                  eq -> eq
470                      .stream()
471                      .map(I.rowHeads()::get)
472                      .filter(((Predicate<G>) layout.seedsG::containsKey).negate())
473                      .findAny()
474                      .isPresent())
475              .map(I._firstObject::apply)
476              .forEach(g -> layout.seedsG.put(g, new Point3D(1d + Math.random(), 0.5d + Math.random(), 0)));
477          layout.seedsG.keySet().removeIf(
478              g -> !I._irreducibleObjects
479                  .contains(I._objects.stream().filter(c -> c.contains(I.rowHeads().indexOf(g))).findAny().get()));
480        }
481        updateProgress(0.9d, 1d);
482      }
483    };
484  }
485}