001package conexp.fx.core.layout; 002/* 003 * #%L 004 * Concept Explorer FX 005 * %% 006 * Copyright (C) 2010 - 2023 Francesco Kriegel 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License as 010 * published by the Free Software Foundation, either version 3 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/gpl-3.0.html>. 021 * #L% 022 */ 023 024import java.util.Map.Entry; 025import java.util.function.Function; 026import java.util.stream.Stream; 027 028import com.google.common.collect.Maps; 029 030import conexp.fx.core.collections.ListIterators; 031import conexp.fx.core.collections.Pair; 032import conexp.fx.core.context.Concept; 033import conexp.fx.core.math.Points; 034import javafx.beans.binding.Binding; 035import javafx.geometry.Point3D; 036 037public abstract class QualityMeasure<G, M, V> implements Function<AdditiveConceptLayout<G, M>, V> { 038 039// implements com.google.common.base.Function<AdditiveConceptLayout<G, M>, V>, 040// java.util.function.Function<AdditiveConceptLayout<G, M>, V> { 041// 042// private final Map<Pair<Map<G, Point3D>, Map<M, Point3D>>, V> cache = new ConcurrentHashMap<>(); 043// 044// public final V apply(final AdditiveConceptLayout<G, M> layout) { 045// return cache.computeIfAbsent(Pair.of(layout._seedsG, layout._seedsM), __ -> compute(layout)); 046// } 047// 048 @Override 049 public V apply(AdditiveConceptLayout<G, M> t) { 050 return compute(t); 051 } 052 053 protected abstract V compute(AdditiveConceptLayout<G, M> layout); 054 055 public static final <G, M, V> QualityMeasure<G, M, V> 056 of(final java.util.function.Function<AdditiveConceptLayout<G, M>, V> f) { 057 return new QualityMeasure<G, M, V>() { 058 059 @Override 060 protected V compute(AdditiveConceptLayout<G, M> layout) { 061 return f.apply(layout); 062 } 063 }; 064 } 065 066 @SafeVarargs 067 public static final <G, M> QualityMeasure<G, M, Double> 068 linearCombination(final Pair<Double, QualityMeasure<G, M, Double>>... weightedMeasures) { 069 return of( 070 layout -> Stream 071 .of(weightedMeasures) 072 .map(p -> p.first() * p.second().apply(layout)) 073 .reduce(Double::sum) 074 .orElse(0d)); 075 } 076 077 public static final <G, M> QualityMeasure<G, M, Double> topBottomXDistance() { 078 return of( 079 layout -> 1d - (Math.abs( 080 layout.getOrAddPosition(layout.lattice.context.topConcept()).getValue().getX() 081 - layout.getOrAddPosition(layout.lattice.context.bottomConcept()).getValue().getX()) 082 / layout.getCurrentBoundingBox(false, false).getWidth())); 083 } 084 085 public static final <G, M> QualityMeasure<G, M, Double> heightWidthRatio() { 086 return of(layout -> { 087 final double h = layout.getCurrentBoundingBox(false, false).getHeight(); 088 final double w = layout.getCurrentBoundingBox(false, false).getWidth(); 089 final double min = Math.min(h, w); 090 final double max = Math.max(h, w); 091 return min / max; 092 }); 093 } 094 095 public static final <G, M> QualityMeasure<G, M, Integer> edgeIntersections() { 096 return of(layout -> { 097 int intersections = 0; 098 for (Pair<Pair<Concept<G, M>, Concept<G, M>>, Pair<Concept<G, M>, Concept<G, M>>> e : ListIterators 099 .upperCartesianDiagonalStrict(layout.lattice)) 100 if (!e.first().x().equals(e.second().y()) && !e.first().y().equals(e.second().x())) 101 if (Points.intersectingLineSegments( 102 layout.getOrAddPosition(e.first().x()).getValue(), 103 layout.getOrAddPosition(e.first().y()).getValue(), 104 layout.getOrAddPosition(e.second().x()).getValue(), 105 layout.getOrAddPosition(e.second().y()).getValue())) 106 intersections++; 107 return intersections; 108 }); 109 } 110 111 public static final <G, M> QualityMeasure<G, M, Pair<Concept<G, M>, Double>> conflictDistance() { 112 return of(layout -> { 113 double num = 0d; 114 double minC = 1d; 115 double sumC = 0d; 116 double minL = Double.MAX_VALUE; 117 double sumL = 0d; 118 final Concept<G, M> bottom = layout.lattice.context.selection.bottomConcept(); 119 Concept<G, M> faulty = bottom; 120 for (Entry<Concept<G, M>, Binding<Point3D>> entry : Maps 121 .asMap(layout.lattice.rowHeads(), layout::getOrAddPosition) 122 .entrySet()) { 123 // layout.positionBindings.entrySet()) 124 // // 125 // { 126 if (!entry.getKey().equals(bottom)) { 127 final Point3D p = entry.getValue().getValue(); 128 final Concept<G, M> c = entry.getKey(); 129 for (Pair<Concept<G, M>, Concept<G, M>> edge : layout.lattice) 130 if (!edge.x().equals(bottom) && !c.equals(edge.x()) && !c.equals(edge.y())) { 131 final Point3D q1 = layout.getOrAddPosition(edge.x()).getValue(); 132 final Point3D q2 = layout.getOrAddPosition(edge.y()).getValue(); 133 final double length = q1.distance(q2); 134 final double conflictDistance = Math.min(Points.pointSegmentDistance(p, q1, q2) / length, 0.5d) * 2d; 135 num++; 136 minL = Math.min(minL, length); 137 sumL += length; 138 sumC += conflictDistance; 139 if (conflictDistance < minC) { 140 minC = conflictDistance; 141 faulty = c; 142 } 143 } 144 } 145 } 146 final double avgC = num == 0d ? 1d : (sumC / num); 147 final double avgL = num == 0d ? 1d : (sumL / num); 148 return Pair.of(faulty, Math.pow(minC * Math.pow(minC / avgC, 0.5d) * Math.pow(minL / avgL, 0.25d), 0.125d)); 149 }); 150 } 151 152 public static final <G, M> QualityMeasure<G, M, Integer> parallelEdges() { 153 return of(layout -> { 154 return 0; 155 }); 156 } 157 158 public static final <G, M> QualityMeasure<G, M, Integer> distinctDirections() { 159 return of(layout -> { 160 return 0; 161 }); 162 } 163 164 public static final <G, M> QualityMeasure<G, M, Integer> distinctAngles() { 165 return of(layout -> { 166 return 0; 167 }); 168 } 169}