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.HashSet;
026import java.util.Set;
027import java.util.concurrent.ExecutionException;
028import java.util.concurrent.ExecutorService;
029import java.util.concurrent.Executors;
030import java.util.concurrent.Future;
031import java.util.function.Consumer;
032import java.util.function.Supplier;
033
034import conexp.fx.core.collections.Collections3;
035import conexp.fx.core.collections.Pair;
036import conexp.fx.core.context.Concept;
037import conexp.fx.core.context.Context;
038import conexp.fx.core.context.Implication;
039import conexp.fx.core.math.SetClosureOperator;
040import conexp.fx.gui.ConExpFX;
041import conexp.fx.gui.dataset.FCADataset;
042import conexp.fx.gui.task.TimeTask;
043import conexp.fx.gui.util.Platform2;
044
045public final class NextClosures2C {
046
047  public static final <G, M> Pair<Set<Concept<G, M>>, Set<Implication<G, M>>> compute(
048      final Context<G, M> cxt,
049      final ExecutorService executor,
050      final Consumer<Concept<G, M>> conceptConsumer,
051      final Consumer<Implication<G, M>> implicationConsumer,
052      final Consumer<String> updateStatus,
053      final Consumer<Double> updateProgress,
054      final Supplier<Boolean> isCancelled,
055      final SetClosureOperator<M> constraint) {
056    final NextClosuresState<G, M, Set<M>> result = NextClosuresState.withHashSets(cxt.colHeads());
057    result.candidates.clear();
058    final HashSet<M> firstCandidate = new HashSet<M>();
059    constraint.close(firstCandidate);
060    final int firstCardinality = firstCandidate.size();
061    result.cardinality = firstCardinality;
062    result.candidates.put(firstCandidate, firstCardinality);
063//    final ClosureOperator<M> clop = ClosureOperator.fromImplications(result.implications, true, true);
064    final SetClosureOperator<M> sup = SetClosureOperator.supremum(SetClosureOperator.fromContext(cxt), constraint);
065    final int maxCardinality = cxt.colHeads().size();
066    for (; result.cardinality <= maxCardinality; result.cardinality++) {
067      try {
068        if (isCancelled.get())
069          break;
070      } catch (Exception __) {}
071      final double q = ((double) result.cardinality) / ((double) maxCardinality);
072      final int p = (int) (100d * q);
073      updateStatus.accept("current cardinality: " + result.cardinality + "/" + maxCardinality + " (" + p + "%)");
074      updateProgress.accept(q);
075      final Set<Future<?>> futures = Collections3.newConcurrentHashSet();
076      result.candidates.keySet().parallelStream().filter(c -> c.size() == result.cardinality).forEach(candidate -> {
077        futures.add(executor.submit(() -> {
078          final Set<M> closure =
079              SetClosureOperator
080                  .supremum(
081                      SetClosureOperator
082                          .fromImplications(result.implications, result.candidates.get(candidate), true, true),
083                      constraint)
084                  .closure(candidate);
085          if (closure.size() == candidate.size()) {
086            final Set<M> candidateII = sup.closure(candidate);
087            final Set<G> candidateI = cxt.colAnd(candidate);
088            if (result.isNewIntent(candidateII)) {
089              final Concept<G, M> concept = new Concept<G, M>(candidateI, new HashSet<M>(candidateII));
090              result.concepts.add(concept);
091              conceptConsumer.accept(concept);
092              result.addNewCandidates(candidateII);
093            }
094            if (candidateII.size() != candidate.size()) {
095              candidateII.removeAll(candidate);
096              final Implication<G, M> implication = new Implication<G, M>(candidate, candidateII, candidateI);
097              result.implications.add(implication);
098              implicationConsumer.accept(implication);
099            }
100          } else {
101            result.candidates.put(closure, result.cardinality);
102          }
103          result.candidates.remove(candidate);
104        }));
105      });
106      for (Future<?> future : futures)
107        try {
108          future.get();
109        } catch (InterruptedException | ExecutionException e) {
110          e.printStackTrace();
111        }
112    }
113    updateStatus
114        .accept(result.concepts.size() + " concepts, and " + result.implications.size() + " implications found");
115    return result.getResultAndDispose();
116  }
117
118  public static final <G, M> Pair<Set<Concept<G, M>>, Set<Implication<G, M>>>
119      compute(final Context<G, M> cxt, final ExecutorService executor, final SetClosureOperator<M> constraint) {
120    return compute(
121        cxt,
122        executor,
123        __ -> {},
124        __ -> {},
125        System.out::println,
126        System.out::println,
127        () -> false,
128        constraint);
129  }
130
131  public static final <G, M> Pair<Set<Concept<G, M>>, Set<Implication<G, M>>>
132      compute(final Context<G, M> cxt, final int cores, final SetClosureOperator<M> constraint) {
133    if (cores > Runtime.getRuntime().availableProcessors())
134      throw new IllegalArgumentException(
135          "Requested pool size is too large. VM has only " + Runtime.getRuntime().availableProcessors()
136              + " available cpus, thus a thread pool with " + cores + " cores cannot be used here.");
137//    final ThreadPoolExecutor tpe =
138//        new ThreadPoolExecutor(cores, cores, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
139//    tpe.prestartAllCoreThreads();
140    final ExecutorService tpe = Executors.newWorkStealingPool(cores);
141    final Pair<Set<Concept<G, M>>, Set<Implication<G, M>>> result = compute(cxt, tpe, constraint);
142//    tpe.purge();
143    tpe.shutdown();
144    return result;
145  }
146
147  public static final <G, M> Pair<Set<Concept<G, M>>, Set<Implication<G, M>>>
148      compute(final Context<G, M> cxt, final SetClosureOperator<M> constraint) {
149    return compute(cxt, Runtime.getRuntime().availableProcessors() - 1, constraint);
150  }
151
152  public static final <G, M> TimeTask<?> createTask(FCADataset<G, M> dataset, final SetClosureOperator<M> constraint) {
153    return new TimeTask<Void>(dataset, "NextClosures") {
154
155      @Override
156      protected Void call() throws Exception {
157        updateProgress(0d, 1d);
158        if (isCancelled())
159          return null;
160        compute(
161            dataset.context.getSelection(),
162            ConExpFX.instance.executor.tpe,
163            concept -> Platform2.runOnFXThread(() -> dataset.concepts.add(concept)),
164            implication -> Platform2.runOnFXThread(() -> dataset.implications.add(implication)),
165            // dataset.concepts::add,
166            // dataset.implications::add,
167            status -> updateMessage(status),
168            progress -> updateProgress(progress, 1d),
169            () -> isCancelled(),
170            constraint);
171        updateProgress(1d, 1d);
172        return null;
173      }
174    };
175  }
176
177}