1 // ****************************************************************************** 2 // 3 // Title: Force Field X. 4 // Description: Force Field X - Software for Molecular Biophysics. 5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2025. 6 // 7 // This file is part of Force Field X. 8 // 9 // Force Field X is free software; you can redistribute it and/or modify it 10 // under the terms of the GNU General Public License version 3 as published by 11 // the Free Software Foundation. 12 // 13 // Force Field X is distributed in the hope that it will be useful, but WITHOUT 14 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 // details. 17 // 18 // You should have received a copy of the GNU General Public License along with 19 // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple 20 // Place, Suite 330, Boston, MA 02111-1307 USA 21 // 22 // Linking this library statically or dynamically with other modules is making a 23 // combined work based on this library. Thus, the terms and conditions of the 24 // GNU General Public License cover the whole combination. 25 // 26 // As a special exception, the copyright holders of this library give you 27 // permission to link this library with independent modules to produce an 28 // executable, regardless of the license terms of these independent modules, and 29 // to copy and distribute the resulting executable under terms of your choice, 30 // provided that you also meet, for each linked independent module, the terms 31 // and conditions of the license of that module. An independent module is a 32 // module which is not derived from or based on this library. If you modify this 33 // library, you may extend this exception to your version of the library, but 34 // you are not obligated to do so. If you do not wish to do so, delete this 35 // exception statement from your version. 36 // 37 // ****************************************************************************** 38 package ffx.numerics.clustering; 39 40 import java.util.ArrayList; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.PriorityQueue; 45 import java.util.logging.Logger; 46 47 /** 48 * Container for linkages 49 * with the minimal methods needed in the package 50 * Created by Alexandre Masselot on 7/18/14. 51 * 52 * @author Lars Behnke, 2013 53 * @author Michael J. Schnieders 54 * @since 1.0 55 */ 56 public class DistanceMap { 57 58 public static final Logger logger = Logger.getLogger(DistanceMap.class.getName()); 59 60 private final Map<String, Item> pairHash; 61 private final PriorityQueue<Item> data; 62 63 private class Item implements Comparable<Item> { 64 final ClusterPair pair; 65 final String hash; 66 boolean removed = false; 67 68 Item(ClusterPair p) { 69 pair = p; 70 hash = hashCodePair(p); 71 } 72 73 @Override 74 public int compareTo(Item o) { 75 return pair.compareTo(o.pair); 76 } 77 78 @Override 79 public String toString() { 80 return hash; 81 } 82 } 83 84 public DistanceMap() { 85 data = new PriorityQueue<>(); 86 pairHash = new HashMap<>(); 87 } 88 89 /** 90 * Returns a snapshot list of all cluster pairs currently stored. 91 * 92 * @return list of ClusterPair entries in this map 93 */ 94 public List<ClusterPair> list() { 95 List<ClusterPair> l = new ArrayList<>(data.size()); 96 for (Item clusterPair : data) { 97 l.add(clusterPair.pair); 98 } 99 return l; 100 } 101 102 /** 103 * Finds the ClusterPair for the two provided clusters. 104 * 105 * @param c1 the first cluster 106 * @param c2 the second cluster 107 * @return the matching ClusterPair, or null if absent 108 */ 109 public ClusterPair findByCodePair(Cluster c1, Cluster c2) { 110 String inCode = hashCodePair(c1, c2); 111 Item item = pairHash.get(inCode); 112 return item == null ? null : item.pair; 113 } 114 115 /** 116 * Removes and returns the minimal-distance pair (according to priority queue ordering). 117 * 118 * @return the next ClusterPair, or null if none 119 */ 120 public ClusterPair removeFirst() { 121 Item poll = data.poll(); 122 while (poll != null && poll.removed) { 123 poll = data.poll(); 124 } 125 if (poll == null) { 126 return null; 127 } 128 ClusterPair link = poll.pair; 129 pairHash.remove(poll.hash); 130 return link; 131 } 132 133 /** 134 * Marks the given ClusterPair as removed (lazy removal) and drops it from the hash index. 135 * 136 * @param link the ClusterPair to remove 137 * @return true if the pair was present and marked removed; false otherwise 138 */ 139 public boolean remove(ClusterPair link) { 140 Item remove = pairHash.remove(hashCodePair(link)); 141 if (remove == null) { 142 return false; 143 } 144 remove.removed = true; 145 // data.remove(remove); // bottleneck 146 return true; 147 } 148 149 /** 150 * Adds a new ClusterPair if no equivalent pair already exists. 151 * 152 * @param link the pair to add 153 * @return true if added; false if a duplicate existed 154 */ 155 public boolean add(ClusterPair link) { 156 Item e = new Item(link); 157 Item existingItem = pairHash.get(e.hash); 158 if (existingItem != null) { 159 logger.info("hashCode = " + existingItem.hash + 160 " adding redundant link:" + link + " (exist:" + existingItem + ")"); 161 return false; 162 } else { 163 pairHash.put(e.hash, e); 164 data.add(e); 165 return true; 166 } 167 } 168 169 /** 170 * Peek at the minimal linkage distance currently in the map. 171 * 172 * @return the smallest linkage distance, or null if empty 173 */ 174 public Double minDist() { 175 Item peek = data.peek(); 176 if (peek != null) 177 return peek.pair.getLinkageDistance(); 178 else 179 return null; 180 } 181 182 /** 183 * Compute some kind of unique ID for a given cluster pair. 184 * 185 * @return The ID 186 */ 187 private String hashCodePair(ClusterPair link) { 188 return hashCodePair(link.getlCluster(), link.getrCluster()); 189 } 190 191 private String hashCodePair(Cluster lCluster, Cluster rCluster) { 192 return hashCodePairNames(lCluster.getName(), rCluster.getName()); 193 } 194 195 private String hashCodePairNames(String lName, String rName) { 196 if (lName.compareTo(rName) < 0) { 197 return lName + "~~~" + rName;//getlCluster().hashCode() + 31 * (getrCluster().hashCode()); 198 } else { 199 return rName + "~~~" + lName;//return getrCluster().hashCode() + 31 * (getlCluster().hashCode()); 200 } 201 } 202 203 @Override 204 public String toString() { 205 return data.toString(); 206 } 207 }