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 }