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.Collection;
026import java.util.ConcurrentModificationException;
027import java.util.HashSet;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Set;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.ExecutionException;
033import java.util.concurrent.Future;
034import java.util.concurrent.LinkedBlockingQueue;
035import java.util.concurrent.ThreadPoolExecutor;
036import java.util.concurrent.TimeUnit;
037import java.util.stream.Collectors;
038
039import com.google.common.collect.Sets;
040
041import conexp.fx.core.collections.Collections3;
042import conexp.fx.core.context.Concept;
043import conexp.fx.core.context.Context;
044import conexp.fx.core.context.Implication;
045import conexp.fx.core.math.SetClosureOperator;
046
047public final class NextClosures1C {
048
049  public static final class ResultC<G, M> {
050
051    public final Set<Concept<G, M>>  concepts     = Collections3.newConcurrentHashSet();
052    public final Map<Set<M>, Set<M>> implications = new ConcurrentHashMap<Set<M>, Set<M>>();
053    final Map<Set<M>, Integer>       candidates   = new ConcurrentHashMap<Set<M>, Integer>();
054    private final Set<Set<M>>        processed    = Collections3.newConcurrentHashSet();
055    int                              cardinality  = 0;
056    private final SetClosureOperator<M> clop;
057
058    public ResultC(SetClosureOperator<M> clop) {
059      this.clop = clop;
060      candidates.put(new HashSet<M>(), 0);
061    }
062
063    private final boolean isClosed(final Set<M> candidate) {
064//      if (!clop.isClosed(candidate))
065//        return false;
066      for (Entry<Set<M>, Set<M>> implication : implications.entrySet())
067        if (candidate.size() > implication.getKey().size() && candidate.containsAll(implication.getKey())
068            && !candidate.containsAll(implication.getValue()))
069          return false;
070      return true;
071    }
072
073    final Set<M> fastClosure(final Set<M> candidate, final int c) {
074      final Set<M> closure = new HashSet<M>(candidate);
075      boolean changed = false;
076      for (Entry<Set<M>, Set<M>> implication : implications.entrySet())
077        if (implication.getKey().size() >= c && closure.size() > implication.getKey().size()
078            && closure.containsAll(implication.getKey()) && !closure.containsAll(implication.getValue())) {
079          closure.addAll(implication.getValue());
080          changed = true;
081        }
082//      changed |= !clop.close(closure);
083      while (changed) {
084        changed = false;
085        for (Entry<Set<M>, Set<M>> implication : implications.entrySet())
086          if (closure.size() > implication.getKey().size() && closure.containsAll(implication.getKey())
087              && !closure.containsAll(implication.getValue())) {
088            closure.addAll(implication.getValue());
089            changed = true;
090          }
091//        changed |= !clop.close(closure);
092      }
093      return closure;
094    }
095
096    private final Set<M> closure(final Set<M> candidate) {
097      final Set<M> closure = new HashSet<M>(candidate);
098      boolean changed = true;
099      while (changed) {
100        changed = false;
101        for (Entry<Set<M>, Set<M>> implication : implications.entrySet())
102          if (closure.size() > implication.getKey().size() && closure.containsAll(implication.getKey())
103              && !closure.containsAll(implication.getValue())) {
104            closure.addAll(implication.getValue());
105            changed = true;
106          }
107//        changed |= !clop.close(closure);
108      }
109      return closure;
110    }
111
112    final boolean addToProcessed(final Set<M> s) {
113      try {
114        return processed.add(s);
115      } catch (ConcurrentModificationException e) {
116        return addToProcessed(s);
117      }
118    }
119
120  }
121
122  public static final <G, M> ResultC<G, M> compute(
123      final Context<G, M> cxt,
124      final SetClosureOperator<M> clop,
125      final boolean verbose,
126      final ThreadPoolExecutor tpe) {
127    if (verbose)
128      System.out
129          .println("NextClosures running on " + tpe.getCorePoolSize() + " - " + tpe.getMaximumPoolSize() + " cores...");
130    final ResultC<G, M> result = new ResultC<G, M>(clop);
131    final SetClosureOperator<M> sup = SetClosureOperator.supremum(SetClosureOperator.fromContext(cxt), clop);
132    final int maxCardinality = cxt.colHeads().size();
133    for (; result.cardinality <= maxCardinality; result.cardinality++) {
134      if (verbose) {
135        final int p = (int) ((100f * (float) result.cardinality) / ((float) maxCardinality));
136        System.out.println("current cardinality: " + result.cardinality + "/" + maxCardinality + " (" + p + "%)");
137      }
138//      final Collection<Set<M>> candidatesN =
139//          new HashSet<Set<M>>(Collections2.filter(result.candidates.keySet(), new Predicate<Set<M>>() {
140//
141//            @Override
142//            public final boolean apply(final Set<M> candidate) {
143//              return candidate.size() == result.cardinality;
144//            }
145//          }));
146//      if (verbose)
147//        System.out.println("     " + candidatesN.size() + " candidates will be processed...");
148      final Set<Future<?>> futures = Collections3.newConcurrentHashSet();
149//      for (final Set<M> candidate : candidatesN) {
150      result.candidates.keySet().parallelStream().filter(c -> c.size() == result.cardinality).forEach(candidate -> {
151        futures.add(tpe.submit(() -> {
152          final Set<M> closure = SetClosureOperator
153              .fromImplications(
154                  result.implications
155                      .entrySet()
156                      .stream()
157                      .map(e -> new Implication<G, M>(e.getKey(), e.getValue()))
158                      .collect(Collectors.toSet()),
159                  true,
160                  true)
161              .closure(candidate);
162//                result.fastClosure(candidate, result.candidates.get(candidate));
163          if (closure.size() == candidate.size()) {
164            final Set<M> candidateII = sup.closure(candidate);
165            if (result.addToProcessed(candidateII)) {
166              for (M m : Sets.difference(cxt.colHeads(), candidateII)) {
167                final Set<M> candidateM = new HashSet<M>(candidateII);
168                candidateM.add(m);
169                result.candidates.put(candidateM, 0);
170              }
171            }
172            result.concepts.add(new Concept<G, M>(cxt.colAnd(candidateII), Sets.newHashSet(candidateII)));
173            if (candidateII.size() != candidate.size()) {
174//                result.concepts.add(new Concept<G, M>(cxt.colAnd(candidate), candidate));
175//              } else {
176              candidateII.removeAll(candidate);
177              result.implications.put(candidate, candidateII);
178            }
179          } else {
180            result.candidates.put(closure, result.cardinality);
181          }
182          result.candidates.remove(candidate);
183        }));
184      });
185      for (Future<?> future : futures)
186        try {
187          future.get();
188        } catch (InterruptedException | ExecutionException e) {
189          e.printStackTrace();
190        }
191//      result.candidates.keySet().removeAll(candidatesN);
192    }
193    if (verbose) {
194      System.out.println(result.concepts.size() + " concepts found");
195      System.out.println(result.implications.size() + " implications found");
196    }
197    return result;
198  }
199
200  public static final <G, M> ResultC<G, M>
201      compute(final Context<G, M> cxt, final SetClosureOperator<M> clop, final boolean verbose, final int cores) {
202    if (cores > Runtime.getRuntime().availableProcessors())
203      throw new IllegalArgumentException(
204          "Requested pool size is too large. VM has only " + Runtime.getRuntime().availableProcessors()
205              + " available cpus, thus a thread pool with " + cores + " cores cannot be used here.");
206    final ThreadPoolExecutor tpe =
207        new ThreadPoolExecutor(cores, cores, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
208    tpe.prestartAllCoreThreads();
209    final ResultC<G, M> result = compute(cxt, clop, verbose, tpe);
210    tpe.purge();
211    tpe.shutdown();
212    return result;
213  }
214
215  public static final <G, M> ResultC<G, M>
216      compute(final Context<G, M> cxt, final SetClosureOperator<M> clop, final boolean verbose) {
217    final int maxc = Runtime.getRuntime().availableProcessors();
218    final int cores = maxc < 9 ? maxc : (maxc * 3) / 4;
219    return compute(cxt, clop, verbose, cores);
220  }
221
222  public static final <G, M> ResultC<G, M> computeWithBackgroundImplications(
223      final Context<G, M> cxt,
224      final Set<Implication<G, M>> backgroundImplications,
225      final boolean verbose) {
226    return compute(cxt, SetClosureOperator.fromImplications(backgroundImplications, false, true), verbose);
227  }
228
229  public static final <G, M> ResultC<G, M>
230      computeIceberg(final Context<G, M> cxt, final int minSupp, final boolean verbose) {
231    return compute(cxt, SetClosureOperator.byMinimalSupport(minSupp, cxt), verbose);
232  }
233
234  public static final <G, M> ResultC<G, M>
235      computeByMaxCard(final Context<G, M> cxt, final int maxCard, final boolean verbose) {
236    return compute(cxt, SetClosureOperator.byMaximalCardinality(maxCard, cxt.colHeads()), verbose);
237  }
238
239  public static final <G, M> ResultC<G, M>
240      computeBelow(final Context<G, M> cxt, final Collection<M> elements, final boolean verbose) {
241    return compute(cxt, SetClosureOperator.isSubsetOf(elements, cxt.colHeads()), verbose);
242  }
243
244  public static final <G, M> ResultC<G, M>
245      computeAbove(final Context<G, M> cxt, final Collection<M> elements, final boolean verbose) {
246    return compute(cxt, SetClosureOperator.containsAllFrom(elements, cxt.colHeads()), verbose);
247  }
248
249}