001/*
002 * @author Francesco.Kriegel@gmx.de
003 */
004package conexp.fx.core.context;
005
006/*
007 * #%L
008 * Concept Explorer FX
009 * %%
010 * Copyright (C) 2010 - 2023 Francesco Kriegel
011 * %%
012 * This program is free software: you can redistribute it and/or modify
013 * it under the terms of the GNU General Public License as
014 * published by the Free Software Foundation, either version 3 of the
015 * License, or (at your option) any later version.
016 * 
017 * This program is distributed in the hope that it will be useful,
018 * but WITHOUT ANY WARRANTY; without even the implied warranty of
019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
020 * GNU General Public License for more details.
021 * 
022 * You should have received a copy of the GNU General Public
023 * License along with this program.  If not, see
024 * <http://www.gnu.org/licenses/gpl-3.0.html>.
025 * #L%
026 */
027
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashSet;
031import java.util.Set;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.stream.Collectors;
034import java.util.stream.Stream;
035
036import com.google.common.base.Predicates;
037import com.google.common.collect.Maps;
038import com.google.common.collect.Sets;
039
040import conexp.fx.core.collections.relation.MatrixRelation;
041import conexp.fx.core.collections.relation.RelationEvent;
042import conexp.fx.core.collections.relation.RelationEventHandler;
043import javafx.collections.FXCollections;
044import javafx.collections.ObservableMap;
045
046public final class ConceptLattice<G, M> extends MatrixRelation<Concept<G, M>, Concept<G, M>> {
047
048  public final MatrixContext<G, M>                     context;
049  public final ObservableMap<G, Concept<G, M>>         objectConcepts    =
050                                                                             FXCollections
051                                                                                 .observableMap(new ConcurrentHashMap<G, Concept<G, M>>());
052  public final ObservableMap<M, Concept<G, M>>         attributeConcepts =
053                                                                             FXCollections
054                                                                                 .observableMap(new ConcurrentHashMap<M, Concept<G, M>>());
055  private MatrixRelation<Concept<G, M>, Concept<G, M>> order;
056
057  public ConceptLattice(final MatrixContext<G, M> context) {
058    super(true);
059    this.context = context;
060    order = order();
061    addEventHandler(new RelationEventHandler<Concept<G, M>, Concept<G, M>>() {
062
063      @Override
064      public final void handle(final RelationEvent<Concept<G, M>, Concept<G, M>> event) {
065        order = order();
066      }
067    }, RelationEvent.ALL_CHANGED);
068  }
069
070  public final Set<Concept<G, M>> lowerNeighbors(final Concept<G, M> concept) {
071    return col(concept);
072  }
073
074  public final Set<Concept<G, M>> upperNeighbors(final Concept<G, M> concept) {
075    return row(concept);
076  }
077
078  // lower
079  public final Set<Concept<G, M>> ideal(final Concept<G, M> concept) {
080    return order.col(concept);
081  }
082
083  // upper
084  public final Set<Concept<G, M>> filter(final Concept<G, M> concept) {
085    return order.row(concept);
086  }
087
088  public final Set<Concept<G, M>> interval(final Concept<G, M> lower, final Concept<G, M> upper) {
089    return Sets.intersection(filter(lower), ideal(upper));
090  }
091
092  public final Set<Concept<G, M>> complement(final Set<Concept<G, M>> concepts) {
093    return Sets.difference(rowHeads(), concepts);
094  }
095
096  @SafeVarargs
097  public final Concept<G, M> supremum(final Concept<G, M>... concepts) {
098    final Set<M> intent = new HashSet<M>(context.colHeads());
099    for (Concept<G, M> concept : concepts)
100      intent.retainAll(concept.intent());
101    final Set<G> extent = new HashSet<G>(context.colAnd(intent));
102    return new Concept<G, M>(extent, intent);
103  }
104
105  public final Concept<G, M> supremum(final Iterable<Concept<G, M>> concepts) {
106    final Set<M> intent = new HashSet<M>(context.colHeads());
107    for (Concept<G, M> concept : concepts)
108      intent.retainAll(concept.intent());
109    final Set<G> extent = new HashSet<G>(context.colAnd(intent));
110    return new Concept<G, M>(extent, intent);
111  }
112
113  @SafeVarargs
114  public final Concept<G, M> infimum(final Concept<G, M>... concepts) {
115    final Set<G> extent = new HashSet<G>(context.rowHeads());
116    for (Concept<G, M> concept : concepts)
117      extent.retainAll(concept.extent());
118    final Set<M> intent = new HashSet<M>(context.rowAnd(extent));
119    return new Concept<G, M>(extent, intent);
120  }
121
122  public final Concept<G, M> infimum(final Iterable<Concept<G, M>> concepts) {
123    final Set<G> extent = new HashSet<G>(context.rowHeads());
124    for (Concept<G, M> concept : concepts)
125      extent.retainAll(concept.extent());
126    final Set<M> intent = new HashSet<M>(context.rowAnd(extent));
127    return new Concept<G, M>(extent, intent);
128  }
129
130  public final Set<G> objectLabels(final Concept<G, M> concept) {
131    return Maps.filterValues(objectConcepts, Predicates.equalTo(concept)).keySet();
132  }
133
134  public final Set<M> attributeLabels(final Concept<G, M> concept) {
135    return Maps.filterValues(attributeConcepts, Predicates.equalTo(concept)).keySet();
136  }
137
138  /**
139   * The method computes the luxenburger base for partial implications, whose confidence is at least minConfidence. If
140   * sorted is set to true, then the partial implications are sorted according to descending confidence, and a List is
141   * returned. Otherwise an unsorted Set is returned.
142   * 
143   * @param minConfidence
144   * @param sorted
145   * @return a collection of implications
146   */
147  public final Collection<Implication<G, M>> luxenburgerBase(final double minConfidence, final boolean sorted) {
148    if (minConfidence < 0d || minConfidence > 1d)
149      throw new IllegalArgumentException("Confidence must be in range [0,1].");
150    Stream<Implication<G, M>> s = new LuxenburgerBaseExtractor().get().parallelStream();
151    if (minConfidence > 0d)
152      s = s.filter(i -> i.getConfidence() >= minConfidence);
153    if (sorted)
154      return s.sorted((i, j) -> (int) Math.signum(j.getConfidence() - i.getConfidence())).collect(Collectors.toList());
155    return s.collect(Collectors.toSet());
156  }
157
158//  public final List<Implication<G, M>> luxenburgerBase() {
159//    return new LuxenburgerBaseExtractor()
160//        .get()
161//        .parallelStream()
162//        .sorted((i, j) -> (int) Math.signum(j.getConfidence() - i.getConfidence()))
163//        .collect(Collectors.toList());
164//  }
165//
166//  public final List<Implication<G, M>> luxenburgerBase(final double confidence) {
167//    if (confidence < 0d || confidence > 1d)
168//      throw new IllegalArgumentException("Confidence must be in range [0,1].");
169//    return new LuxenburgerBaseExtractor()
170//        .get()
171//        .parallelStream()
172//        .filter(i -> i.getConfidence() >= confidence)
173//        .sorted((i, j) -> (int) Math.signum(j.getConfidence() - i.getConfidence()))
174//        .collect(Collectors.toList());
175//  }
176
177  private final class LuxenburgerBaseExtractor {
178
179    private final Set<Implication<G, M>> set = Collections
180                                                 .newSetFromMap(new ConcurrentHashMap<Implication<G, M>, Boolean>());
181
182    private final Set<Implication<G, M>> get() {
183      recursion(context.selection.topConcept());
184      return set;
185    }
186
187    private final void recursion(final Concept<G, M> concept) {
188      col(concept).parallelStream().forEach(
189          lowerNeighbor -> {
190            set.add(new Implication<G, M>(concept.getIntent(), Sets.newHashSet(Sets.difference(
191                lowerNeighbor.getIntent(),
192                concept.getIntent())), lowerNeighbor.getExtent(), ((double) lowerNeighbor.getExtent().size())
193                / ((double) concept.getExtent().size())));
194            recursion(lowerNeighbor);
195          });
196    }
197
198  }
199
200}