View Javadoc
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 }