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}