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.stream.Collectors; 034 035import com.google.common.base.Predicate; 036import com.google.common.base.Predicates; 037import com.google.common.collect.Collections2; 038import com.google.common.collect.Iterables; 039import com.google.common.collect.Iterators; 040import com.google.common.collect.Sets; 041 042import conexp.fx.core.collections.Collections3; 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 conexp.fx.gui.util.Platform2; 052import javafx.collections.ObservableSet; 053import javafx.geometry.Point3D; 054 055public final class IFox1<G, M> { 056 057 private enum Type { 058 EQUIVALENT, 059 REDUCIBLE, 060 IRREDUCIBLE; 061 } 062 063 private static final class Attribute<M> { 064 065 private final Type type; 066 private final Set<M> equivalents; 067 068 @SafeVarargs 069 private Attribute(final Type type, final Collection<M>... equivalents) { 070 this.type = type; 071 if (equivalents.length == 0) 072 this.equivalents = Collections.<M> emptySet(); 073 else 074 this.equivalents = new HashSet<M>(equivalents[0]); 075 } 076 } 077 078 private static final <G, M> Attribute<M> attribute(final MatrixContext<G, M> context, final M m) { 079 final int _m = context.selection.colHeads().indexOf(m); 080 for (Set<Integer> _n : context.selection._attributes) 081 if (_n.contains(_m)) 082 if (_n.size() > 1) 083 return new Attribute<M>( 084 Type.EQUIVALENT, 085 Collections2.transform( 086 Sets.difference(_n, Collections.singleton(_m)), 087 context.selection.colHeads().indexGuava().inverse())); 088 else if (context.selection._irreducibleAttributes.contains(_n)) 089 return new Attribute<M>(Type.IRREDUCIBLE); 090 else 091 return new Attribute<M>(Type.REDUCIBLE); 092 return null; 093 } 094 095 private static final <G, M> void adjust( 096 final AdditiveConceptLayout<G, M> layout, 097 final M m, 098 final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance, 099 final AbstractExecutorService tpe) { 100 LayoutEvolution<G, M>.Value v = new LayoutEvolution<G, M>( 101 layout, 102 layout.lattice.attributeConcepts.get(m), 103 ConceptMovement.LABEL_SEED, 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 AbstractExecutorService 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 Platform2.runOnFXThread(new Runnable() { 176 177 public void run() { 178 c.intent().add(m); 179 } 180 }); 181 if (mJ.size() == c.extent().size()) 182 synchronized (layout.lattice.attributeConcepts) { 183 layout.lattice.attributeConcepts.put(m, c); 184 } 185 } 186 } 187 188 private void irreducible() { 189 // full update 190 updateMessage("Determining old, varying and generating concepts..."); 191 updateProgress(0.1d, 1d); 192// final Set<Concept<G, M>> olds = new HashSet<Concept<G, M>>(); 193// final Set<Concept<G, M>> vars = new HashSet<Concept<G, M>>(); 194// final Set<Concept<G, M>> gens = new HashSet<Concept<G, M>>(); 195// final Predicate<Concept<G, M>> varPredicate = new Predicate<Concept<G, M>>() { 196// 197// public final boolean apply(final Concept<G, M> c) { 198// return mJ.containsAll(c.extent()); 199// } 200// }; 201// final Predicate<Concept<G, M>> genPredicate = new Predicate<Concept<G, M>>() { 202// 203// public final boolean apply(final Concept<G, M> c) { 204// return I.rowAnd(Sets.intersection(c.extent(), mJ)).equals(c.intent()); 205// } 206// }; 207// vars.addAll(Collections2.filter(layout.lattice.rowHeads(), varPredicate)); 208// olds.addAll(Sets.difference(layout.lattice.rowHeads(), vars)); 209// gens.addAll(Collections2.filter(olds, genPredicate)); 210 final Set<Concept<G, M>> vars = layout.lattice 211 .rowHeads() 212 .parallelStream() 213 .filter(c -> mJ.containsAll(c.extent())) 214 .collect(Collectors.toSet()); 215 final Set<Concept<G, M>> olds = 216 layout.lattice.rowHeads().parallelStream().filter(c -> !vars.contains(c)).collect(Collectors.toSet()); 217 final Set<Concept<G, M>> gens = 218 olds.parallelStream().filter(c -> I.rowAnd(Sets.intersection(c.extent(), mJ)).equals(c.intent())).collect( 219 Collectors.toSet()); 220 final Map<Concept<G, M>, Concept<G, M>> news = new HashMap<Concept<G, M>, Concept<G, M>>(gens.size()); 221 updateMessage("Updating varying concepts..."); 222 updateProgress(0.2d, 1d); 223 for (final Concept<G, M> v : vars) { 224 Platform2.runOnFXThread(new Runnable() { 225 226 public void run() { 227 v.intent().add(m); 228 } 229 }); 230 for (Concept<G, M> g : gens) 231 layout.lattice.remove(v, g); 232 } 233 updateMessage("Creating new concepts..."); 234 updateProgress(0.3d, 1d); 235 double i = 0d; 236 for (final Concept<G, M> g : gens) { 237 updateProgress(0.3d + 0.25d * (i++ / (double) gens.size()), 1d); 238 final HashSet<G> gol = new HashSet<G>(layout.lattice.objectLabels(g)); 239 final Concept<G, M> n = g.clone(); 240 n.extent().retainAll(mJ); 241 n.intent().add(m); 242 news.put(n, g); 243 synchronized (layout.generators) { 244 layout.generators.put(n, g); 245 } 246 layout.lattice.rowHeads().add(n); 247 layout.lattice.addFast(n, g); 248 if (g.extent().containsAll(mJ)) 249 synchronized (layout.lattice.attributeConcepts) { 250 layout.lattice.attributeConcepts.put(m, n); 251 } 252 for (G ol : Sets.intersection(gol, mJ)) 253 synchronized (layout.lattice.objectConcepts) { 254 layout.lattice.objectConcepts.put(ol, n); 255 } 256 for (final Concept<G, M> v : vars) 257 if (v.smaller(g) && Sets.filter(Sets.union(gens, vars), new Predicate<Concept<G, M>>() { 258 259 public final boolean apply(final Concept<G, M> c) { 260 return v.smaller(c) && c.smaller(g); 261 } 262 }).isEmpty()) 263 layout.lattice.addFast(v, n); 264 // layout.invalidate(); 265 } 266 updateMessage("Creating new concepts neighborhood..."); 267 updateProgress(0.55d, 1d); 268 i = 0d; 269 for (final Entry<Concept<G, M>, Concept<G, M>> n1 : news.entrySet()) 270 for (final Entry<Concept<G, M>, Concept<G, M>> n2 : news.entrySet()) { 271 updateProgress(0.55d + 0.25d * (i++ / Math.pow(news.size(), 2)), 1d); 272 if (n1.getValue().smaller(n2.getValue()) && Sets.filter(gens, new Predicate<Concept<G, M>>() { 273 274 public final boolean apply(final Concept<G, M> c) { 275 return n1.getValue().smaller(c) && c.smaller(n2.getValue()); 276 } 277 }).isEmpty()) 278 layout.lattice.addFast(n1.getKey(), n2.getKey()); 279 } 280 // layout.invalidate(); 281 updateMessage("Updating seeds..."); 282 updateProgress(0.8d, 1d); 283 synchronized (layout.seedsM) { 284 updateMessage("Determining new Reducibles..."); 285 for (M n : layout.seedsM.keySet()) { 286 final int _n = IuJ.colHeads().indexOf(n); 287 final Set<Integer> eq = Iterables.find(IuJ._attributes, new Predicate<Collection<Integer>>() { 288 289 public final boolean apply(final Collection<Integer> c) { 290 return c.contains(_n); 291 } 292 }); 293 if (!IuJ._irreducibleAttributes.contains(eq)) { 294 updateMessage("Backup seed: " + n); 295 final Point3D oldSeed = layout.seedsM.remove(n); 296 layout.seedHistoryM.put(n, oldSeed); 297 } 298 } 299 // final HashSet<M> currentIrreducibles = new HashSet<M>(layout.seeds.keySet()); 300 // for (M s : currentIrreducibles) 301 // if (layout.lattice.row(layout.lattice.attributeConcepts.get(s)).size() != 1) 302 if (layout.seedHistoryM.containsKey(m) && !layout.seedsM.values().contains(layout.seedHistoryM.get(m))) { 303 updateMessage("Restoring seed: " + m); 304 layout.seedsM.put(m, layout.seedHistoryM.get(m)); 305 } else { 306 updateMessage("Computing random seed: " + m); 307 layout.seedsM.put(m, new Point3D(2d * Math.random() - 1d, 0.5d + Math.random(), 0)); 308 } 309 updateProgress(0.9d, 1d); 310 updateMessage("Adjusting Layout..."); 311// adjust(layout, m, conflictDistance, tpe); 312 } 313 } 314 }; 315 } 316 317 public static final <G, M> TimeTask<Void> ignore( 318 final String id, 319 final AdditiveConceptLayout<G, M> layout, 320 final M m, 321 final QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance, 322 final AbstractExecutorService tpe) { 323 return new TimeTask<Void>(id + " - iFox Ignore") { 324 325 private MatrixContext<G, M> I; 326 private Attribute<M> attribute; 327 328 protected Void call() { 329 updateProgress(0d, 1d); 330 if (isCancelled()) 331 return null; 332 updateMessage("Calculating Incremental Update..."); 333 attribute = attribute(layout.lattice.context.selection, m); 334 layout.lattice.context.deselectAttribute(m); 335 I = layout.lattice.context.selection; 336 switch (attribute.type) { 337 case EQUIVALENT: 338 equivalent(); 339 break; 340 case REDUCIBLE: 341 reducible(); 342 break; 343 case IRREDUCIBLE: 344 irreducible(); 345 break; 346 } 347 layout.invalidate(); 348 layout.lattice.pushAllChangedEvent(); 349 updateProgress(1d, 1d); 350 return null; 351 } 352 353 private void equivalent() { 354 // modify only label and intents 355 synchronized (layout.lattice.attributeConcepts) { 356 layout.lattice.attributeConcepts.remove(m); 357 } 358 for (Concept<G, M> c : layout.lattice.rowHeads()) 359 Platform2.runOnFXThread(new Runnable() { 360 361 public void run() { 362 c.intent().remove(m); 363 } 364 }); 365 synchronized (layout.seedsM) { 366 final Point3D seed = layout.seedsM.remove(m); 367 if (seed != null) { 368 layout.seedHistoryM.put(m, seed); 369 final M n = attribute.equivalents.iterator().next(); 370 layout.seedsM.put(n, seed); 371 } 372 } 373 } 374 375 private void reducible() { 376 // modify only label and intents 377 synchronized (layout.lattice.attributeConcepts) { 378 layout.lattice.attributeConcepts.remove(m); 379 } 380 for (Concept<G, M> c : layout.lattice.rowHeads()) 381 Platform2.runOnFXThread(new Runnable() { 382 383 public void run() { 384 c.intent().remove(m); 385 } 386 }); 387 } 388 389 private void irreducible() { 390 // full update 391 updateMessage("Determining old, varying and generating concepts..."); 392 updateProgress(0.1d, 1d); 393 final Set<Concept<G, M>> olds = new HashSet<Concept<G, M>>(); 394 final Set<Concept<G, M>> vars = new HashSet<Concept<G, M>>(); 395 final Set<Concept<G, M>> _news = new HashSet<Concept<G, M>>(); 396 final Map<Concept<G, M>, Concept<G, M>> news = new HashMap<Concept<G, M>, Concept<G, M>>(); 397 final Set<Concept<G, M>> oldNonGens = new HashSet<Concept<G, M>>(); 398 final Predicate<Concept<G, M>> oldPredicate = new Predicate<Concept<G, M>>() { 399 400 public final boolean apply(final Concept<G, M> c) { 401 return !c.intent().contains(m); 402 } 403 }; 404 final Predicate<Concept<G, M>> varPredicate = new Predicate<Concept<G, M>>() { 405 406 public final boolean apply(final Concept<G, M> c) { 407 return !olds.contains(c) 408 && c.extent().equals(I.colAnd(Sets.difference(c.intent(), Collections.singleton(m)))); 409 } 410 }; 411 olds.addAll(Collections2.filter(layout.lattice.rowHeads(), oldPredicate)); 412 vars.addAll(Collections2.filter(layout.lattice.rowHeads(), varPredicate)); 413 _news.addAll(Sets.difference(layout.lattice.rowHeads(), Sets.union(olds, vars))); 414 updateMessage("Computing generating concepts..."); 415 updateProgress(0.2d, 1d); 416 synchronized (layout.lattice.attributeConcepts) { 417 layout.lattice.attributeConcepts.remove(m); 418 } 419 for (Concept<G, M> n : _news) { 420 final ObservableSet<M> intent = n.intent(); 421 final Predicate<Concept<G, M>> genPredicate = new Predicate<Concept<G, M>>() { 422 423 public final boolean apply(final Concept<G, M> c) { 424 return c.intent().equals(Sets.difference(intent, Collections.singleton(m))); 425 } 426 }; 427 final Concept<G, M> g = Iterators.find(olds.iterator(), genPredicate); 428 synchronized (layout.lattice.objectConcepts) { 429 final Set<G> nol = new HashSet<G>(layout.lattice.objectLabels(n)); 430 for (G ol : nol) 431 layout.lattice.objectConcepts.put(ol, g); 432 } 433 news.put(n, g); 434 synchronized (layout.generators) { 435 layout.generators.put(n, g); 436 } 437 } 438 oldNonGens.addAll(Collections3.difference(olds, news.values())); 439 // layout.invalidate(); 440 updateMessage("Updating varying concepts..."); 441 updateProgress(0.3d, 1d); 442 for (Concept<G, M> v : vars) 443 Platform2.runOnFXThread(new Runnable() { 444 445 public void run() { 446 v.intent().remove(m); 447 } 448 }); 449 for (final Entry<Concept<G, M>, Concept<G, M>> n : news.entrySet()) 450 for (final Concept<G, M> v : vars) { 451 final Predicate<Concept<G, M>> vnIntervalPredicate = new Predicate<Concept<G, M>>() { 452 453 public final boolean apply(final Concept<G, M> c) { 454 return v.smaller(c) && c.smaller(n.getValue()); 455 } 456 }; 457 if (layout.lattice.contains(v, n.getKey()) && Sets.filter(oldNonGens, vnIntervalPredicate).isEmpty()) 458 layout.lattice.addFast(v, n.getValue()); 459 } 460 // layout.invalidate(); 461 updateMessage("Removing concepts..."); 462 // layout.lattice.rowHeads().removeAll(news.keySet()); 463 for (Concept<G, M> y : news.keySet()) { 464 for (Concept<G, M> z : layout.lattice.row(y)) 465 layout.lattice.remove(y, z); 466 for (Concept<G, M> z : layout.lattice.col(y)) 467 layout.lattice.remove(z, y); 468 layout.lattice.rowHeads().remove(y); 469 } 470 updateMessage("Updating seeds..."); 471 updateProgress(0.6d, 1d); 472 synchronized (layout.seedsM) { 473 updateMessage("Backup seed: " + m); 474 final Point3D oldSeed = layout.seedsM.remove(m); 475 layout.seedHistoryM.put(m, oldSeed); 476 Set<Integer> _seeds = 477 new HashSet<Integer>(Collections2.transform(layout.seedsM.keySet(), I.colHeads().indexGuava())); 478 for (Set<Integer> eq : I._irreducibleAttributes) 479 if (!Iterables.any(_seeds, Predicates.in(eq))) { 480 // introduce new seed 481 482 // interate over all possible attributes in the equivalence class! 483// final M n = Collections2.transform(eq, I.colHeads().index().inverse()).iterator().next(); 484 for (M n : Collections2.transform(eq, I.colHeads().indexGuava().inverse())) { 485 // if (true) { 486 // read seed vector from current layout 487 final Concept<G, M> nc = I.attributeConcept(n); 488 if (layout.getPosition(nc) != null) { 489 final Point3D np = layout.getOrAddPosition(nc).getValue().add(oldSeed); 490 final Point3D nnp = 491 layout.getOrAddPosition(Iterables.getOnlyElement(layout.lattice.row(nc))).getValue(); 492 final Point3D seed = np.subtract(nnp); 493 layout.seedsM.put(n, seed); 494 updateProgress(0.8d, 1d); 495 updateMessage("Adjusting Layout..."); 496// adjust(layout, n, conflictDistance, tpe); 497 // } 498 // if (layout.seedHistory.containsKey(n)) { 499 // updateMessage("Restoring seed: " + n); 500 // final Point3D seedBackup = layout.seedHistory.get(n); 501 // layout.seeds.put(n, seedBackup); 502 // } else { 503 // updateMessage("Computing random seed: " + n); 504 // layout.seeds.put(n, new Point3D(2d * Math.random() - 1d, 0.5d + Math.random(), 0)); 505 // adjust(layout, n); 506 // } 507 break; 508 } 509 } 510 } 511 // final HashSet<M> currentReducibles = 512 // new HashSet<M>(Sets.difference(I.colHeads(), layout.seeds.keySet())); 513 // for (M s : currentReducibles) 514 // if (Sets.difference(layout.lattice.row(layout.lattice.attributeConcepts.get(s)), news.keySet()).size() == 515 // 1) 516 } 517 updateProgress(0.9d, 1d); 518 } 519 }; 520 } 521}