Repository navigation
Expand file tree
/
Copy pathAtomPositionMap.java
More file actions
432 lines (395 loc) · 13 KB
/
Copy pathAtomPositionMap.java
File metadata and controls
432 lines (395 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/*
* BioJava development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public Licence. This should
* be distributed with the code. If you do not have a copy,
* see:
*
* http://www.gnu.org/copyleft/lesser.html
*
* Copyright for this code is held jointly by the individual
* authors. These should be listed in @author doc comments.
*
* For more information on the BioJava project and its aims,
* or to join the biojava-l mailing list, visit the home page
* at:
*
* http://www.biojava.org/
*
* Created on 2012-12-01
*
*/
package org.biojava.nbio.structure;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import org.biojava.nbio.structure.io.mmcif.chem.PolymerType;
import org.biojava.nbio.structure.io.mmcif.chem.ResidueType;
import org.biojava.nbio.structure.io.mmcif.model.ChemComp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A map from {@link ResidueNumber ResidueNumbers} to ATOM record positions in a PDB file.
*
* <p>To use:
*
* <pre>
* Atom[] atoms = new AtomCache().getAtoms("1w0p");
* AtomPositionMap map = new AtomPositionMap(atoms);
* ResidueNumber start = new ResidueNumber("A", 100, null);
* ResidueNumber end = map.getEnd("A");
* int pos = map.getPosition(start);
* int length = map.calcLength(start, end);
* </pre>
*
* <p>Note: The getLength() methods were introduced in BioJava 4.0.0 to replace
* the calcLength methods. The new method returns the number of residues between
* two residues, inclusive, whereas the previous method returned 1 less than that.
* @author Douglas Myers-Turnbull
*/
public class AtomPositionMap {
private static final Logger logger = LoggerFactory.getLogger(AtomPositionMap.class);
private HashMap<ResidueNumber, Integer> hashMap;
private TreeMap<ResidueNumber, Integer> treeMap;
/**
* Used as a Predicate to indicate whether a particular Atom should be mapped
*/
public static interface GroupMatcher {
boolean matches(Group group);
}
/**
* Matches CA atoms of protein groups
*/
public static final GroupMatcher AMINO_ACID_MATCHER = new GroupMatcher() {
@Override
public boolean matches(Group group) {
if( group == null )
return false;
ChemComp chem = group.getChemComp();
if(chem == null)
return false;
// Get polymer type
PolymerType polyType = chem.getPolymerType();
if( polyType == null) {
ResidueType type = chem.getResidueType();
if(type != null ) {
polyType = type.getPolymerType();
}
}
if( polyType == null ) {
return false;
}
return PolymerType.PROTEIN_ONLY.contains(polyType)
&& group.hasAtom(StructureTools.CA_ATOM_NAME);
}
};
/**
* Matches all atoms
*/
public static final GroupMatcher ANYTHING_MATCHER = new GroupMatcher() {
@Override
public boolean matches(Group group) {
return true;
}
};
/**
* A map that is sorted by its values. Used for the treemap
*
* @author dmyerstu
*
* @param <T>
* The key type
* @param <V>
* The value type
*/
private static class ValueComparator<T, V extends Comparable<V>> implements Comparator<T>, Serializable {
private static final long serialVersionUID = 1;
private Map<T, V> map;
public ValueComparator(Map<T, V> map) {
this.map = map;
}
@Override
public int compare(T o1, T o2) {
return map.get(o1).compareTo(map.get(o2));
}
}
/**
* Creates a new AtomPositionMap containing peptide alpha-carbon atoms
*
* @param atoms
*/
public AtomPositionMap(Atom[] atoms) {
this(atoms, AMINO_ACID_MATCHER);
}
/**
* Creates a new AtomPositionMap containing only atoms matched by {@code matcher}.
*
* If multiple atoms are present from a group, the first atom encountered will
* be used.
* @param atoms
*/
public AtomPositionMap(Atom[] atoms, GroupMatcher matcher) {
hashMap = new HashMap<ResidueNumber, Integer>();
for (int i = 0; i < atoms.length; i++) {
Group group = atoms[i].getGroup();
ResidueNumber rn = group.getResidueNumber();
if (matcher.matches(group)) {
if (!hashMap.containsKey(rn)) {
hashMap.put(rn, i);
}
}
}
Comparator<ResidueNumber> vc = new ValueComparator<ResidueNumber, Integer>(hashMap);
treeMap = new TreeMap<ResidueNumber, Integer>(vc);
treeMap.putAll(hashMap);
}
/**
* Creates a new AtomPositionMap containing representative atoms
* from a structure.
* @param s
*/
public AtomPositionMap(Structure s) {
this(StructureTools.getRepresentativeAtomArray(s));
}
/**
* Calculates the number of residues of the specified chain in a given range, inclusive.
* @param positionA index of the first atom to count
* @param positionB index of the last atom to count
* @param startingChain Case-sensitive chain
* @return The number of atoms between A and B inclusive belonging to the given chain
*/
public int getLength(int positionA, int positionB, String startingChain) {
int positionStart, positionEnd;
if (positionA <= positionB) {
positionStart = positionA;
positionEnd = positionB;
} else {
positionStart = positionB;
positionEnd = positionA;
}
int count = 0;
// Inefficient search
for (Map.Entry<ResidueNumber, Integer> entry : treeMap.entrySet()) {
if (entry.getKey().getChainName().equals(startingChain)
&& positionStart <= entry.getValue()
&& entry.getValue() <= positionEnd)
{
count++;
}
}
return count;
}
/**
* Calculates the number of residues of the specified chain in a given range.
* Will return a negative value if the start is past the end.
* @param positionStart index of the first atom to count
* @param positionEnd index of the last atom to count
* @param startingChain Case-sensitive chain
* @return The number of atoms from A to B inclusive belonging to the given chain
*/
public int getLengthDirectional(int positionStart, int positionEnd, String startingChain) {
int count = getLength(positionStart,positionEnd,startingChain);
if(positionStart <= positionEnd) {
return count;
} else {
return -count;
}
}
/**
* Calculates the number of atoms between two ResidueNumbers, inclusive. Both residues
* must belong to the same chain.
* @param start First residue
* @param end Last residue
* @return The number of atoms from A to B inclusive
* @throws IllegalArgumentException if start and end are on different chains,
* or if either of the residues doesn't exist
*/
public int getLength(ResidueNumber start, ResidueNumber end) {
if( ! start.getChainName().equals(end.getChainName())) {
throw new IllegalArgumentException(String.format(
"Chains differ between %s and %s. Unable to calculate length.",
start,end));
}
Integer startPos = getPosition(start);
Integer endPos = getPosition(end);
if(startPos == null) {
throw new IllegalArgumentException("Residue "+start+" was not found.");
}
if(endPos == null) {
throw new IllegalArgumentException("Residue "+start+" was not found.");
}
return getLength(startPos, endPos, start.getChainName());
}
/**
* Calculates the number of atoms between two ResidueNumbers, inclusive. Both residues
* must belong to the same chain.
* Will return a negative value if the start is past the end.
* @param start First residue
* @param end Last residue
* @return The number of atoms from A to B inclusive
* @throws IllegalArgumentException if start and end are on different chains,
* or if either of the residues doesn't exist
*/
public int getLengthDirectional(ResidueNumber start, ResidueNumber end) {
if( ! start.getChainName().equals(end.getChainName())) {
throw new IllegalArgumentException(String.format(
"Chains differ between %s and %s. Unable to calculate length.",
start,end));
}
Integer startPos = getPosition(start);
Integer endPos = getPosition(end);
if(startPos == null) {
throw new IllegalArgumentException("Residue "+start+" was not found.");
}
if(endPos == null) {
throw new IllegalArgumentException("Residue "+start+" was not found.");
}
return getLengthDirectional(startPos, endPos, start.getChainName());
}
public NavigableMap<ResidueNumber, Integer> getNavMap() {
return treeMap;
}
/**
* Gets the 0-based index of residueNumber to the matched atoms
* @param residueNumber
* @return The position of the ATOM record in the PDB file corresponding to
* the {@code residueNumber}, or null if the residueNumber was not found
*/
public Integer getPosition(ResidueNumber residueNumber) {
return hashMap.get(residueNumber);
}
/**
* @param chainId
* @return The first {@link ResidueNumber} of the specified chain (the one highest down in the PDB file)
*/
public ResidueNumber getFirst(String chainId) {
Map.Entry<ResidueNumber,Integer> entry = treeMap.firstEntry();
while (true) {
if (entry.getKey().getChainName().equals(chainId)) return entry.getKey();
entry = treeMap.higherEntry(entry.getKey());
if (entry == null) return null;
}
}
/**
* @param chainId
* @return The last {@link ResidueNumber} of the specified chain (the one farthest down in the PDB file)
*/
public ResidueNumber getLast(String chainId) {
Map.Entry<ResidueNumber,Integer> entry = treeMap.lastEntry();
while (true) {
if (entry.getKey().getChainName().equals(chainId)) return entry.getKey();
entry = treeMap.lowerEntry(entry.getKey());
if (entry == null) return null;
}
}
/**
* @return The first {@link ResidueNumber} of any chain (the one farthest down in the PDB file)
*/
public ResidueNumber getFirst() {
return treeMap.firstKey();
}
/**
* @return The last {@link ResidueNumber} of any chain (the one farthest down in the PDB file)
*/
public ResidueNumber getLast() {
return treeMap.lastKey();
}
/**
* Returns a list of {@link ResidueRange ResidueRanges} corresponding to this entire AtomPositionMap.
*/
public List<ResidueRangeAndLength> getRanges() {
String currentChain = "";
ResidueNumber first = null;
ResidueNumber prev = null;
List<ResidueRangeAndLength> ranges = new ArrayList<ResidueRangeAndLength>();
for (ResidueNumber rn : treeMap.keySet()) {
if (!rn.getChainName().equals(currentChain)) {
if (first != null) {
ResidueRangeAndLength newRange = new ResidueRangeAndLength(currentChain, first, prev, this.getLength(first, prev));
ranges.add(newRange);
}
first = rn;
}
prev = rn;
currentChain = rn.getChainName();
}
ResidueRangeAndLength newRange = new ResidueRangeAndLength(currentChain, first, prev, this.getLength(first, prev));
ranges.add(newRange);
return ranges;
}
/**
* Trims a residue range so that both endpoints are contained in this map.
* @param rr residue range
* @return residue range and length
*/
public ResidueRangeAndLength trimToValidResidues(ResidueRange rr) {
ResidueNumber start = rr.getStart();
ResidueNumber end = rr.getEnd();
String chain = rr.getChainName();
// Add chainName
if(start.getChainName() == null) {
start = new ResidueNumber(chain,start.getSeqNum(),start.getInsCode());
}
if(end.getChainName() == null) {
end = new ResidueNumber(chain,end.getSeqNum(),end.getInsCode());
}
// Check that start and end are present in the map.
// If not, try to find the next valid residue
// (terminal residues sometimes lack CA atoms, so they don't appear)
Integer startIndex = getPosition(start);
if( startIndex == null) {
// Assume that the residue numbers are sequential
// Find startIndex such that the SeqNum is bigger than start's seqNum
for(ResidueNumber key : treeMap.keySet()) {
if( !key.getChainName().equals(chain) )
continue;
if( start.getSeqNum() <= key.getSeqNum() ) {
start = key;
startIndex = getPosition(key);
break;
}
}
if( startIndex == null ) {
logger.error("Unable to find Residue {} in AtomPositionMap, and no plausible substitute.",start);
return null;
} else {
logger.warn("Unable to find Residue {}, so substituting {}.",rr.getStart(),start);
}
}
Integer endIndex = getPosition(end);
if( endIndex == null) {
// Assume that the residue numbers are sequential
// Find startIndex such that the SeqNum is bigger than start's seqNum
for(ResidueNumber key : treeMap.descendingKeySet()) {
if( !key.getChainName().equals(chain) )
continue;
Integer value = getPosition(key);
if( value < startIndex ) {
// start is before the end!
break;
}
if( end.getSeqNum() >= key.getSeqNum() ) {
end = key;
endIndex = value;
break;
}
}
if( endIndex == null ) {
logger.error("Unable to find Residue {} in AtomPositionMap, and no plausible substitute.",end);
return null;
} else {
logger.warn("Unable to find Residue {}, so substituting {}.",rr.getEnd(),end);
}
}
// now use those to calculate the length
// if start or end is null, will throw NPE
int length = getLength(startIndex, endIndex,chain);
return new ResidueRangeAndLength(chain, start, end, length);
}
}