001package conexp.fx.core.algorithm.lattice; 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.Collections; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Set; 032import java.util.concurrent.AbstractExecutorService; 033import java.util.concurrent.ExecutorService; 034import java.util.function.Predicate; 035import java.util.stream.Collectors; 036import java.util.stream.Stream; 037 038import com.google.common.base.Predicates; 039import com.google.common.collect.Collections2; 040import com.google.common.collect.Iterables; 041import com.google.common.collect.Sets; 042 043import conexp.fx.core.collections.Pair; 044import conexp.fx.core.context.Concept; 045import conexp.fx.core.context.MatrixContext; 046import conexp.fx.core.layout.AdditiveConceptLayout; 047import conexp.fx.core.layout.ConceptMovement; 048import conexp.fx.core.layout.LayoutEvolution; 049import conexp.fx.core.layout.QualityMeasure; 050import conexp.fx.gui.task.TimeTask; 051import javafx.geometry.Point3D; 052 053public final class IFox2<G, M> { 054 055 private enum Type { 056 EQUIVALENT, 057 REDUCIBLE, 058 IRREDUCIBLE; 059 } 060 061 private static final class Attribute<M> { 062 063 private final Type type; 064 private final Set<M> equivalents; 065 066 @SafeVarargs 067 private Attribute(final Type type, final Collection<M>... equivalents) { 068 this.type = type; 069 if (equivalents.length == 0) 070 this.equivalents = Collections.<M> emptySet(); 071 else 072 this.equivalents = new HashSet<M>(equivalents[0]); 073 } 074 } 075 076 private static final <G, M> Attribute<M> attribute(final MatrixContext<G, M> context, final M m) { 077 final int _m = context.selection.colHeads().indexOf(m); 078 for (Set<Integer> _n : context.selection._attributes) 079 if (_n.contains(_m)) 080 if (_n.size() > 1) 081 return new Attribute<M>( 082 Type.EQUIVALENT, 083 Collections2.transform( 084 Sets.difference(_n, Collections.singleton(_m)), 085 context.selection.colHeads().indexGuava().inverse())); 086 else if (context.selection._irreducibleAttributes.contains(_n)) 087 return new Attribute<M>(Type.IRREDUCIBLE); 088 else 089 return new Attribute<M>(Type.REDUCIBLE); 090 return null; 091 } 092 093 private static final <G, M> void adjust( 094 final AdditiveConceptLayout<G, M> layout, 095 final M m, 096 final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance, 097 final AbstractExecutorService tpe) { 098 LayoutEvolution<G, M>.Value v = new LayoutEvolution<G, M>( 099 layout, 100 layout.lattice.attributeConcepts.get(m), 101 // TODO 102 // ConceptMovement.LABEL_SEED, 103 ConceptMovement.INTENT_CHAIN_SEEDS, 104 2d, 105 2d, 106 1, 107 1, 108 1, 109 conflictDistance, 110 tpe).calculate(); 111 layout.updateSeeds(v.seedsG, v.seedsM); 112// v = 113// new LayoutEvolution<G, M>( 114// layout, 115// v.hint, 116// ConceptMovement.INTENT_CHAIN_SEEDS, 117// 4d, 118// 4d, 119// 2, 120// 2, 121// 1, 122// conflictDistance, 123// tpe).calculate(); 124// layout.updateSeeds(v.seeds); 125 } 126 127 public static final <G, M> TimeTask<Void> select( 128 final String id, 129 final AdditiveConceptLayout<G, M> layout, 130 final M m, 131 final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance, 132 final ExecutorService tpe) { 133 return new TimeTask<Void>(id + " - iFox Select") { 134 135 private MatrixContext<G, M> I; 136 private MatrixContext<G, M> IuJ; 137 private Set<G> mJ; 138 private Attribute<M> attribute; 139 140 protected Void call() { 141 updateProgress(0d, 1d); 142 if (isCancelled()) 143 return null; 144 updateMessage("Calculating Incremental Update..."); 145 I = layout.lattice.context.selection; 146 mJ = new HashSet<G>(layout.lattice.context.col(m)); 147 layout.lattice.context.selectAttribute(m); 148 IuJ = layout.lattice.context.selection; 149 attribute = attribute(layout.lattice.context.selection, m); 150 switch (attribute.type) { 151 case EQUIVALENT: 152 equivalent(); 153 break; 154 case REDUCIBLE: 155 reducible(); 156 break; 157 case IRREDUCIBLE: 158 irreducible(); 159 break; 160 } 161 layout.invalidate(); 162 layout.lattice.pushAllChangedEvent(); 163 updateProgress(1d, 1d); 164 return null; 165 } 166 167 private void equivalent() { 168 reducible(); 169 } 170 171 private void reducible() { 172 // modify only label and intents 173 for (final Concept<G, M> c : layout.lattice.rowHeads()) 174 if (mJ.containsAll(c.extent())) { 175 c.intent().add(m); 176// Platform2.runOnFXThread(() -> c.intent().add(m)); 177 if (mJ.size() == c.extent().size()) 178 synchronized (layout.lattice.attributeConcepts) { 179 layout.lattice.attributeConcepts.put(m, c); 180 } 181 } 182 } 183 184 private void irreducible() { 185 // full update 186 updateMessage("Determining old, varying and generating concepts..."); 187 updateProgress(0.1d, 1d); 188 final Set<Concept<G, M>> vars = 189 layout.lattice.rowHeads().stream().filter(c -> mJ.containsAll(c.extent())).collect(Collectors.toSet()); 190 final Set<Concept<G, M>> olds = 191 layout.lattice.rowHeads().stream().filter(c -> !vars.contains(c)).collect(Collectors.toSet()); 192 final Set<Concept<G, M>> gens = 193 olds.stream().filter(c -> I.rowAnd(Sets.intersection(c.extent(), mJ)).equals(c.intent())).collect( 194 Collectors.toSet()); 195 final Map<Concept<G, M>, Concept<G, M>> news = new HashMap<Concept<G, M>, Concept<G, M>>(gens.size()); 196 updateMessage("Updating varying concepts..."); 197 updateProgress(0.2d, 1d); 198 for (final Concept<G, M> v : vars) { 199 v.intent().add(m); 200// Platform2.runOnFXThread(() -> v.intent().add(m)); 201 for (final Concept<G, M> g : gens) 202 layout.lattice.remove(v, g); 203 } 204 updateMessage("Creating new concepts..."); 205 updateProgress(0.3d, 1d); 206 double i = 0d; 207 for (final Concept<G, M> g : gens) { 208 updateProgress(0.3d + 0.25d * (i++ / (double) gens.size()), 1d); 209 final HashSet<G> gol = new HashSet<G>(layout.lattice.objectLabels(g)); 210 final Concept<G, M> n = g.clone(); 211 n.extent().retainAll(mJ); 212 n.intent().add(m); 213 news.put(n, g); 214 synchronized (layout.generators) { 215 layout.generators.put(n, g); 216 } 217 layout.lattice.rowHeads().add(n); 218 layout.lattice.addFast(n, g); 219 if (g.extent().containsAll(mJ)) 220 synchronized (layout.lattice.attributeConcepts) { 221 layout.lattice.attributeConcepts.put(m, n); 222 } 223 for (G ol : Sets.intersection(gol, mJ)) 224 synchronized (layout.lattice.objectConcepts) { 225 layout.lattice.objectConcepts.put(ol, n); 226 } 227 for (final Concept<G, M> v : vars) 228 if (v.smaller(g) 229 && Stream.concat(gens.stream(), vars.stream()).noneMatch(c -> v.smaller(c) && c.smaller(g))) 230 layout.lattice.addFast(v, n); 231 // layout.invalidate(); 232 } 233 updateMessage("Creating new concepts neighborhood..."); 234 updateProgress(0.55d, 1d); 235 i = 0d; 236 for (final Entry<Concept<G, M>, Concept<G, M>> n1 : news.entrySet()) 237 for (final Entry<Concept<G, M>, Concept<G, M>> n2 : news.entrySet()) { 238 updateProgress(0.55d + 0.25d * (i++ / Math.pow(news.size(), 2)), 1d); 239 if (n1.getValue().smaller(n2.getValue()) 240 && gens.stream().noneMatch(c -> n1.getValue().smaller(c) && c.smaller(n2.getValue()))) 241 layout.lattice.addFast(n1.getKey(), n2.getKey()); 242 } 243 // layout.invalidate(); 244 updateMessage("Updating seeds..."); 245 updateProgress(0.8d, 1d); 246 synchronized (layout.seedsM) { 247 updateMessage("Determining new Reducibles..."); 248 for (M n : layout.seedsM.keySet()) { 249 final int _n = IuJ.colHeads().indexOf(n); 250 final Set<Integer> eq = IuJ._attributes.stream().filter(c -> c.contains(_n)).findAny().get(); 251 if (!IuJ._irreducibleAttributes.contains(eq)) { 252 updateMessage("Backup seed: " + n); 253 final Point3D oldSeed = layout.seedsM.remove(n); 254 layout.seedHistoryM.put(n, oldSeed); 255 } 256 } 257 // final HashSet<M> currentIrreducibles = new HashSet<M>(layout.seeds.keySet()); 258 // for (M s : currentIrreducibles) 259 // if (layout.lattice.row(layout.lattice.attributeConcepts.get(s)).size() != 1) 260 if (layout.seedHistoryM.containsKey(m) && !layout.seedsM.values().contains(layout.seedHistoryM.get(m))) { 261 updateMessage("Restoring seed: " + m); 262 layout.seedsM.put(m, layout.seedHistoryM.get(m)); 263 } else { 264 updateMessage("Computing random seed: " + m); 265 layout.seedsM.put(m, new Point3D(1d + Math.random(), 0.5d + Math.random(), 0)); 266 } 267 } 268 synchronized (layout.seedsG) { 269 IuJ._irreducibleObjects 270 .stream() 271 .filter( 272 eq -> eq 273 .stream() 274 .map(IuJ.rowHeads()::get) 275 .filter(((Predicate<G>) layout.seedsG::containsKey).negate()) 276 .findAny() 277 .isPresent()) 278 .map(IuJ._firstObject::apply) 279 .forEach(g -> layout.seedsG.put(g, new Point3D(1d + Math.random(), 0.5d + Math.random(), 0))); 280 layout.seedsG.keySet().removeIf( 281 g -> !IuJ._irreducibleObjects 282 .contains(IuJ._objects.stream().filter(c -> c.contains(IuJ.rowHeads().indexOf(g))).findAny().get())); 283 } 284 updateProgress(0.9d, 1d); 285 updateMessage("Adjusting Layout..."); 286// adjust(layout, m, conflictDistance, tpe); 287 } 288 }; 289 } 290 291 public static final <G, M> TimeTask<Void> ignore( 292 final String id, 293 final AdditiveConceptLayout<G, M> layout, 294 final M m, 295 final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance, 296 final ExecutorService tpe) { 297 return new TimeTask<Void>(id + " - iFox Ignore") { 298 299 private MatrixContext<G, M> I; 300 private Attribute<M> attribute; 301 302 protected Void call() { 303 updateProgress(0d, 1d); 304 if (isCancelled()) 305 return null; 306 updateMessage("Calculating Incremental Update..."); 307 attribute = attribute(layout.lattice.context.selection, m); 308 layout.lattice.context.deselectAttribute(m); 309 I = layout.lattice.context.selection; 310 switch (attribute.type) { 311 case EQUIVALENT: 312 equivalent(); 313 break; 314 case REDUCIBLE: 315 reducible(); 316 break; 317 case IRREDUCIBLE: 318 irreducible(); 319 break; 320 } 321 layout.lattice.pushAllChangedEvent(); 322 layout.invalidate(); 323 updateProgress(1d, 1d); 324 return null; 325 } 326 327 private void equivalent() { 328 // modify only label and intents 329 synchronized (layout.lattice.attributeConcepts) { 330 layout.lattice.attributeConcepts.remove(m); 331 } 332 for (Concept<G, M> c : layout.lattice.rowHeads()) 333 c.intent().remove(m); 334// Platform2.runOnFXThread(() -> c.intent().remove(m)); 335 synchronized (layout.seedsM) { 336 final Point3D seed = layout.seedsM.remove(m); 337 if (seed != null) { 338 layout.seedHistoryM.put(m, seed); 339 final M n = attribute.equivalents.iterator().next(); 340 layout.seedsM.put(n, seed); 341 } 342 } 343 } 344 345 private void reducible() { 346 // modify only label and intents 347 synchronized (layout.lattice.attributeConcepts) { 348 layout.lattice.attributeConcepts.remove(m); 349 } 350 for (Concept<G, M> c : layout.lattice.rowHeads()) 351 c.intent().remove(m); 352// Platform2.runOnFXThread(() -> c.intent().remove(m)); 353 } 354 355 private void irreducible() { 356 // full update 357 updateMessage("Determining old, varying and generating concepts..."); 358 updateProgress(0.1d, 1d); 359 final Set<Concept<G, M>> olds = 360 layout.lattice.rowHeads().stream().filter(c -> !c.intent().contains(m)).collect(Collectors.toSet()); 361 final Set<Concept<G, M>> vars = layout.lattice 362 .rowHeads() 363 .stream() 364 .filter( 365 c -> !olds.contains(c) 366 && c.extent().equals(I.colAnd(Sets.difference(c.intent(), Collections.singleton(m))))) 367 .collect(Collectors.toSet()); 368 final Set<Concept<G, M>> _news = layout.lattice 369 .rowHeads() 370 .stream() 371 .filter(c -> !olds.contains(c) && !vars.contains(c)) 372 .collect(Collectors.toSet()); 373 updateMessage("Computing generating concepts..."); 374 updateProgress(0.2d, 1d); 375 synchronized (layout.lattice.attributeConcepts) { 376 layout.lattice.attributeConcepts.remove(m); 377 } 378 final Map<Concept<G, M>, Concept<G, M>> news = new HashMap<Concept<G, M>, Concept<G, M>>(); 379 for (Concept<G, M> n : _news) { 380 final Concept<G, M> g = olds 381 .stream() 382 // .filter(c -> c.intent().containsAll(n.intent()) && c.intent().size() == n.intent().size() - 1) 383 .filter(c -> c.intent().equals(Sets.difference(n.intent(), Collections.singleton(m)))) 384 .findAny() 385 .get(); 386 synchronized (layout.lattice.objectConcepts) { 387 final Set<G> nol = new HashSet<G>(layout.lattice.objectLabels(n)); 388 for (G ol : nol) 389 layout.lattice.objectConcepts.put(ol, g); 390 } 391 news.put(n, g); 392 synchronized (layout.generators) { 393 layout.generators.put(n, g); 394 } 395 } 396 final Set<Concept<G, M>> oldNonGens = 397 olds.stream().filter(c -> !news.values().contains(c)).collect(Collectors.toSet()); 398 // layout.invalidate(); 399 updateMessage("Updating varying concepts..."); 400 updateProgress(0.3d, 1d); 401 for (Concept<G, M> v : vars) 402 v.intent().remove(m); 403// Platform2.runOnFXThread(() -> v.intent().remove(m)); 404 for (final Entry<Concept<G, M>, Concept<G, M>> n : news.entrySet()) 405 for (final Concept<G, M> v : vars) 406 if (layout.lattice.contains(v, n.getKey()) 407 && oldNonGens.stream().noneMatch(c -> v.smaller(c) && c.smaller(n.getValue()))) 408 layout.lattice.addFast(v, n.getValue()); 409 // layout.invalidate(); 410 updateMessage("Removing concepts..."); 411 // layout.lattice.rowHeads().removeAll(news.keySet()); 412 for (Concept<G, M> y : news.keySet()) { 413 for (Concept<G, M> z : layout.lattice.row(y)) 414 layout.lattice.remove(y, z); 415 for (Concept<G, M> z : layout.lattice.col(y)) 416 layout.lattice.remove(z, y); 417 layout.lattice.rowHeads().remove(y); 418 } 419 updateMessage("Updating seeds..."); 420 updateProgress(0.6d, 1d); 421 synchronized (layout.seedsM) { 422 updateMessage("Backup seed: " + m); 423 // TODO: seed is sometimes not available in the map. check for a solution! 424 final Point3D oldSeed = layout.seedsM.remove(m); 425 layout.seedHistoryM.put(m, oldSeed); 426 Set<Integer> _seeds = 427 new HashSet<Integer>(Collections2.transform(layout.seedsM.keySet(), I.colHeads().indexGuava())); 428 for (Set<Integer> eq : I._irreducibleAttributes) 429 if (!Iterables.any(_seeds, Predicates.in(eq))) { 430 // introduce new seed 431 // interate over all possible attributes in the equivalence class! 432// final M n = Collections2.transform(eq, I.colHeads().index().inverse()).iterator().next(); 433 for (M n : Collections2.transform(eq, I.colHeads().indexGuava().inverse())) { 434 // if (true) { 435 // read seed vector from current layout 436 final Concept<G, M> nc = I.attributeConcept(n); 437 if (layout.getPosition(nc) != null) { 438 final Point3D np = layout.getPosition(nc).getValue();// .add(oldSeed); 439 final Point3D nnp = layout.getPosition(Iterables.getOnlyElement(layout.lattice.row(nc))).getValue(); 440 final Point3D seed = np.subtract(nnp); 441 layout.seedsM.put(n, seed); 442 updateProgress(0.8d, 1d); 443 updateMessage("Adjusting Layout..."); 444// adjust(layout, n, conflictDistance, tpe); 445 // } 446 // if (layout.seedHistory.containsKey(n)) { 447 // updateMessage("Restoring seed: " + n); 448 // final Point3D seedBackup = layout.seedHistory.get(n); 449 // layout.seeds.put(n, seedBackup); 450 // } else { 451 // updateMessage("Computing random seed: " + n); 452 // layout.seeds.put(n, new Point3D(2d * Math.random() - 1d, 0.5d + Math.random(), 0)); 453 // adjust(layout, n); 454 // } 455 break; 456 } 457 } 458 } 459 // final HashSet<M> currentReducibles = 460 // new HashSet<M>(Sets.difference(I.colHeads(), layout.seeds.keySet())); 461 // for (M s : currentReducibles) 462 // if (Sets.difference(layout.lattice.row(layout.lattice.attributeConcepts.get(s)), news.keySet()).size() == 463 // 1) 464 } 465 synchronized (layout.seedsG) { 466 I._irreducibleObjects 467 .stream() 468 .filter( 469 eq -> eq 470 .stream() 471 .map(I.rowHeads()::get) 472 .filter(((Predicate<G>) layout.seedsG::containsKey).negate()) 473 .findAny() 474 .isPresent()) 475 .map(I._firstObject::apply) 476 .forEach(g -> layout.seedsG.put(g, new Point3D(1d + Math.random(), 0.5d + Math.random(), 0))); 477 layout.seedsG.keySet().removeIf( 478 g -> !I._irreducibleObjects 479 .contains(I._objects.stream().filter(c -> c.contains(I.rowHeads().indexOf(g))).findAny().get())); 480 } 481 updateProgress(0.9d, 1d); 482 } 483 }; 484 } 485}