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 }