001package conexp.fx.core.algorithm.nextclosures;
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.Collections;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.Set;
030import java.util.concurrent.ConcurrentHashMap;
031import java.util.concurrent.ExecutionException;
032import java.util.concurrent.ExecutorService;
033import java.util.concurrent.Executors;
034import java.util.concurrent.Future;
035import java.util.function.Consumer;
036import java.util.function.Supplier;
037import java.util.stream.Collectors;
038import java.util.stream.Stream;
039
040import conexp.fx.core.collections.BitSetFX;
041import conexp.fx.core.collections.Collections3;
042import conexp.fx.core.collections.Pair;
043import conexp.fx.core.context.Concept;
044import conexp.fx.core.context.Context;
045import conexp.fx.core.context.Implication;
046import conexp.fx.core.context.MatrixContext;
047import conexp.fx.core.math.SetClosureOperator;
048import conexp.fx.core.math.Math3;
049import conexp.fx.core.util.Meter;
050import conexp.fx.gui.ConExpFX;
051import conexp.fx.gui.dataset.FCADataset;
052import conexp.fx.gui.task.TimeTask;
053import conexp.fx.gui.util.Platform2;
054
055public class NextClosures2Bit {
056
057  public static final <G, M> Pair<Set<Concept<G, M>>, Set<Implication<G, M>>> bitCompute(
058      final Context<G, M> cxt,
059      final ExecutorService executor,
060      final Consumer<Concept<G, M>> conceptConsumer,
061      final Consumer<Implication<G, M>> implicationConsumer,
062      final Consumer<String> updateStatus,
063      final Consumer<Double> updateProgress,
064      final Supplier<Boolean> isCancelled) {
065    final MatrixContext<G, M> mcxt = cxt instanceof MatrixContext ? (MatrixContext<G, M>) cxt : cxt.clone();
066    final int max = mcxt.colHeads().size();
067    final NextClosuresState<Integer, Integer, BitSetFX> state = NextClosuresState.withBitSets(max);
068//    final Function<BitSetFX, BitSetFX> cl = bitClosure(b.implications);
069    for (; state.cardinality <= max; state.cardinality++) {
070      if (isCancelled.get())
071        break;
072      final double q = ((double) state.cardinality) / ((double) max);
073      final int p = (int) (100d * q);
074      updateStatus.accept("current cardinality: " + state.cardinality + "/" + max + " (" + p + "%)");
075      updateProgress.accept(q);
076      final Set<Future<?>> fs = Collections3.newConcurrentHashSet();
077      final Set<BitSetFX> cc = state.getActualCandidates();
078      cc.forEach(c -> fs.add(executor.submit(() -> {
079        final BitSetFX d = SetClosureOperator
080            .implicativeClosure(state.implications, state.getFirstPremiseSize(c), true, true, true, BitSetFX::new, c);
081        if (c.geq(d)) {
082//        if (c.containsAll(d)) {
083          final BitSetFX c1 = mcxt._colAnd(c);
084          final BitSetFX c2 = mcxt._rowAnd(c1);
085          if (state.isNewIntent(c2)) {
086            state.concepts.add(new Concept<Integer, Integer>(c1, new BitSetFX(c2)));
087            state.addNewCandidates(c2);
088          }
089          if (!c.geq(c2)) {
090//          if (!c.containsAll(c2)) {
091            c2.removeAll(c);
092            state.implications.add(new Implication<Integer, Integer>(c, c2));
093          }
094        } else
095          state.addCandidate(d);
096//        state.candidates.remove(c);
097      })));
098      for (Future<?> f : fs)
099        try {
100          f.get();
101        } catch (InterruptedException | ExecutionException __) {}
102      state.candidates.keySet().removeAll(cc);
103    }
104    updateStatus.accept(state.concepts.size() + " concepts, and " + state.implications.size() + " implications found");
105    final NextClosuresState<G, M, Set<M>> r = NextClosuresState.withHashSets(cxt.colHeads());
106    state.concepts
107        .parallelStream()
108        .map(
109            c -> new Concept<G, M>(
110                mcxt.rowHeads().getAll(c.getExtent(), true),
111                mcxt.colHeads().getAll(c.getIntent(), true)))
112        .forEach(conceptConsumer.andThen(r.concepts::add));
113    state.implications
114        .parallelStream()
115        .map(
116            i -> new Implication<G, M>(
117                mcxt.colHeads().getAll(i.getPremise(), true),
118                mcxt.colHeads().getAll(i.getConclusion(), true)))
119        .forEach(implicationConsumer.andThen(r.implications::add));
120    return r.getResultAndDispose();
121  }
122
123  public static final <G, M> Pair<Set<Concept<G, M>>, Set<Implication<G, M>>> bitCompute(final Context<G, M> cxt) {
124    return bitCompute(cxt, Executors.newWorkStealingPool(), __ -> {}, __ -> {}, __ -> {}, __ -> {}, () -> false);
125  }
126
127  public static final <G, M> Pair<Set<Concept<G, M>>, Set<Implication<G, M>>> bitCleanedCompute(
128      final Context<G, M> cxt,
129      final ExecutorService executor,
130      final Consumer<Concept<G, M>> conceptConsumer,
131      final Consumer<Implication<G, M>> implicationConsumer,
132      final Consumer<String> updateStatus,
133      final Consumer<Double> updateProgress,
134      final Supplier<Boolean> isCancelled) {
135    final MatrixContext<G, M> mcxt = cxt.toMatrixContext();
136    System.out.println("starting context cleaning...");
137    final Meter<Long> nsw = Meter.newNanoStopWatch();
138    mcxt.clean();
139    System.out.println("context cleaning took " + Math3.formatNanos(nsw.measure()));
140    System.out.println("cloning cleaned context...");
141    final Meter<Long> nsw2 = Meter.newNanoStopWatch();
142    final MatrixContext<Set<G>, Set<M>> ccxt = mcxt.cleaned.clone();
143    System.out.println("context cloning took " + Math3.formatNanos(nsw2.measure()));
144
145    final Meter<Long> nsw3 = Meter.newNanoStopWatch();
146    final Pair<Set<Concept<Set<G>, Set<M>>>, Set<Implication<Set<G>, Set<M>>>> r =
147        bitCompute(ccxt, executor, __ -> {}, __ -> {}, updateStatus, updateProgress, isCancelled);
148    System.out.println("bitcleaned: " + nsw3.measureAndFormat());
149    final Meter<Long> nsw4 = Meter.newNanoStopWatch();
150    final NextClosuresState<G, M, Set<M>> x = NextClosuresState.withHashSets(cxt.colHeads());
151    r
152        .first()
153        .parallelStream()
154        .map(
155            c -> new Concept<G, M>(
156                c.extent().parallelStream().flatMap(Set::parallelStream).collect(Collectors.toSet()),
157                c.intent().parallelStream().flatMap(Set::parallelStream).collect(Collectors.toSet())))
158        .forEach(conceptConsumer.andThen(x.concepts::add));
159    final Map<Set<M>, M> f = new ConcurrentHashMap<>();
160    ccxt.colHeads().parallelStream().map(c -> new HashSet<M>(c)).forEach(c -> f.put(c, c.iterator().next()));
161    r
162        .second()
163        .parallelStream()
164        .map(
165            i -> new Implication<G, M>(
166                i.getPremise().parallelStream().map(f::get).collect(Collectors.toSet()),
167                i.getConclusion().parallelStream().map(f::get).collect(Collectors.toSet())))
168        .forEach(implicationConsumer.andThen(x.implications::add));
169    f
170        .keySet()
171        .parallelStream()
172        .flatMap(
173            c -> c.size() > 1 ? c.parallelStream().map(m -> new Implication<G, M>(Collections.singleton(m), c))
174                : Stream.empty())
175        .forEach(implicationConsumer.andThen(x.implications::add));
176    System.out.println("transform: " + nsw3.measureAndFormat());
177    return x.getResultAndDispose();
178  }
179
180  public static final <G, M> Pair<Set<Concept<G, M>>, Set<Implication<G, M>>> bitReducedCompute(
181      final Context<G, M> cxt,
182      final ExecutorService executor,
183      final Consumer<Concept<G, M>> conceptConsumer,
184      final Consumer<Implication<G, M>> implicationConsumer,
185      final Consumer<String> updateStatus,
186      final Consumer<Double> updateProgress,
187      final Supplier<Boolean> isCancelled) {
188    final MatrixContext<G, M> mcxt = cxt.toMatrixContext();
189//    final long s = System.nanoTime();
190//    System.out.print(".");
191    mcxt.reduce();
192//    System.out.print(".");
193    final MatrixContext<Set<G>, Set<M>> rcxt = mcxt.reduced.clone();
194    final MatrixContext<Set<G>, Set<M>> ccxt = mcxt.cleaned.clone();
195//    System.out.println("reducing took " + Math3.formatNanos(System.nanoTime() - s));
196    final Pair<Set<Concept<Set<G>, Set<M>>>, Set<Implication<Set<G>, Set<M>>>> r =
197        bitCompute(rcxt, executor, __ -> {}, __ -> {}, updateStatus, updateProgress, isCancelled);
198    final NextClosuresState<G, M, Set<M>> x = NextClosuresState.withHashSets(cxt.colHeads());
199    final Map<Set<M>, Set<Set<M>>> irr = new ConcurrentHashMap<>();
200    ccxt
201        .colHeads()
202        .parallelStream()
203        .filter(atts -> !rcxt.colHeads().contains(atts))
204        .forEach(atts -> irr.put(atts, new HashSet<Set<M>>(ccxt.attributeQuasiOrder().col(atts))));
205    final Map<Set<G>, Set<Set<G>>> jrr = new ConcurrentHashMap<>();
206    ccxt
207        .rowHeads()
208        .parallelStream()
209        .filter(objs -> !rcxt.rowHeads().contains(objs))
210        .forEach(objs -> jrr.put(objs, new HashSet<Set<G>>(ccxt.objectQuasiOrder().col(objs))));
211    final Map<Set<M>, M> f = new ConcurrentHashMap<>();
212    ccxt.colHeads().parallelStream().map(c -> new HashSet<M>(c)).forEach(c -> f.put(c, c.iterator().next()));
213    r
214        .first()
215        .parallelStream()
216        .map(
217            c -> new Concept<G, M>(
218                c.extent().parallelStream().flatMap(Set::parallelStream).collect(Collectors.toSet()),
219                c.intent().parallelStream().flatMap(Set::parallelStream).collect(Collectors.toSet())))
220        .forEach(conceptConsumer.andThen(x.concepts::add));
221    for (Concept<G, M> c : x.concepts) {
222      for (Entry<Set<M>, Set<Set<M>>> e : irr.entrySet())
223        if (c.intent().containsAll(e.getValue().parallelStream().map(f::get).collect(Collectors.toSet())))
224          c.intent().addAll(e.getKey());
225      for (Entry<Set<G>, Set<Set<G>>> e : jrr.entrySet())
226        if (c.extent().containsAll(e.getValue().parallelStream().map(f::get).collect(Collectors.toSet())))
227          c.extent().addAll(e.getKey());
228    }
229    r
230        .second()
231        .parallelStream()
232        .map(
233            i -> new Implication<G, M>(
234                i.getPremise().parallelStream().map(f::get).collect(Collectors.toSet()),
235                i.getConclusion().parallelStream().map(f::get).collect(Collectors.toSet())))
236        .forEach(implicationConsumer.andThen(x.implications::add));
237    f
238        .keySet()
239        .parallelStream()
240        .flatMap(
241            c -> c.size() > 1 ? c.parallelStream().map(m -> new Implication<G, M>(Collections.singleton(m), c))
242                : Stream.empty())
243        .forEach(implicationConsumer.andThen(x.implications::add));
244    irr
245        .keySet()
246        .parallelStream()
247        .flatMap(
248            ir -> Stream
249                .<Implication<G, M>> of(
250                    new Implication<G, M>(
251                        Collections.singleton(f.get(ir)),
252                        irr.get(ir).parallelStream().map(f::get).collect(Collectors.toSet())),
253                    new Implication<G, M>(
254                        irr.get(ir).parallelStream().map(f::get).collect(Collectors.toSet()),
255                        Collections.singleton(f.get(ir)))))
256        .forEach(implicationConsumer.andThen(x.implications::add));
257    return x.getResultAndDispose();
258  }
259
260  public static final <G, M> TimeTask<?> createTask(FCADataset<G, M> dataset) {
261    return new TimeTask<Void>(dataset, "NextClosures") {
262
263      @Override
264      protected Void call() throws Exception {
265        updateProgress(0d, 1d);
266        if (isCancelled())
267          return null;
268        bitCompute(
269            dataset.context.getSelection(),
270            ConExpFX.instance.executor.tpe,
271            concept -> Platform2.runOnFXThread(() -> dataset.concepts.add(concept)),
272            implication -> Platform2.runOnFXThread(() -> dataset.implications.add(implication)),
273            // dataset.concepts::add,
274            // dataset.implications::add,
275            status -> updateMessage(status),
276            progress -> updateProgress(progress, 1d),
277            () -> isCancelled());
278        updateProgress(1d, 1d);
279        return null;
280      }
281    };
282  }
283
284}