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  /**
41   * Immutable-like holder describing a pair of clusters and the linkage distance
42   * between them; used as entries within the DistanceMap during agglomeration.
43   *
44   * @author Lars Behnke, 2013
45   * @author Michael J. Schnieders
46   * @since 1.0
47   */
48  public class ClusterPair implements Comparable<ClusterPair> {
49  
50    private Cluster lCluster;
51    private Cluster rCluster;
52    private Double linkageDistance;
53  
54    /**
55     * Creates an empty ClusterPair.
56     */
57    public ClusterPair() {
58    }
59  
60    /**
61     * Creates a ClusterPair linking two clusters at a given distance.
62     *
63     * @param left     the left cluster
64     * @param right    the right cluster
65     * @param distance the linkage distance between clusters
66     */
67    public ClusterPair(Cluster left, Cluster right, Double distance) {
68      lCluster = left;
69      rCluster = right;
70      linkageDistance = distance;
71    }
72  
73    /**
74     * Returns the opposite cluster of the provided one in this pair.
75     *
76     * @param c one cluster in this pair
77     * @return the other cluster in the pair
78     */
79    public Cluster getOtherCluster(Cluster c) {
80      return lCluster == c ? rCluster : lCluster;
81    }
82  
83    /**
84     * Gets the left cluster.
85     *
86     * @return the left cluster
87     */
88    public Cluster getlCluster() {
89      return lCluster;
90    }
91  
92    /**
93     * Sets the left cluster.
94     *
95     * @param lCluster the left cluster
96     */
97    public void setlCluster(Cluster lCluster) {
98      this.lCluster = lCluster;
99    }
100 
101   /**
102    * Gets the right cluster.
103    *
104    * @return the right cluster
105    */
106   public Cluster getrCluster() {
107     return rCluster;
108   }
109 
110   /**
111    * Sets the right cluster.
112    *
113    * @param rCluster the right cluster
114    */
115   public void setrCluster(Cluster rCluster) {
116     this.rCluster = rCluster;
117   }
118 
119   /**
120    * Gets the linkage distance.
121    *
122    * @return the linkage distance
123    */
124   public Double getLinkageDistance() {
125     return linkageDistance;
126   }
127 
128   /**
129    * Sets the linkage distance.
130    *
131    * @param distance the linkage distance to set
132    */
133   public void setLinkageDistance(Double distance) {
134     this.linkageDistance = distance;
135   }
136 
137   /**
138    * Creates a new ClusterPair with left and right clusters swapped.
139    *
140    * @return a new ClusterPair with the two left/right inverted
141    */
142   public ClusterPair reverse() {
143     return new ClusterPair(getrCluster(), getlCluster(), getLinkageDistance());
144   }
145 
146 
147   /**
148    * {@inheritDoc}
149    */
150   @Override
151   public int compareTo(ClusterPair o) {
152     int result;
153     if (o == null || o.getLinkageDistance() == null) {
154       result = -1;
155     } else if (getLinkageDistance() == null) {
156       result = 1;
157     } else {
158       result = getLinkageDistance().compareTo(o.getLinkageDistance());
159     }
160 
161     return result;
162   }
163 
164   /**
165    * Agglomerates left and right clusters under an auto-generated name.
166    *
167    * @param clusterIdx index appended to the generated cluster name
168    * @return the new parent Cluster
169    */
170   public Cluster agglomerate(int clusterIdx) {
171     return agglomerate("clstr#" + clusterIdx);
172   }
173 
174   /**
175    * Agglomerates left and right clusters into a new parent with the given name.
176    *
177    * @param name name of the new parent cluster
178    * @return the new parent Cluster
179    */
180   public Cluster agglomerate(String name) {
181     Cluster cluster = new Cluster(name);
182     cluster.setDistance(new Distance(getLinkageDistance()));
183     //New clusters will track their children's leaf names; i.e. each cluster knows what part of the original data it contains
184     cluster.appendLeafNames(lCluster.getLeafNames());
185     cluster.appendLeafNames(rCluster.getLeafNames());
186     cluster.addChild(lCluster);
187     cluster.addChild(rCluster);
188     lCluster.setParent(cluster);
189     rCluster.setParent(cluster);
190 
191     Double lWeight = lCluster.getWeightValue();
192     Double rWeight = rCluster.getWeightValue();
193     double weight = lWeight + rWeight;
194     cluster.getDistance().setWeight(weight);
195 
196     return cluster;
197   }
198 
199   /**
200    * {@inheritDoc}
201    */
202   @Override
203   public String toString() {
204     StringBuilder sb = new StringBuilder();
205     if (lCluster != null) {
206       sb.append(lCluster.getName());
207     }
208     if (rCluster != null) {
209       if (!sb.isEmpty()) {
210         sb.append(" + ");
211       }
212       sb.append(rCluster.getName());
213     }
214     sb.append(" : ").append(linkageDistance);
215     return sb.toString();
216   }
217 
218 }