001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021package org.biojava.nbio.structure.contact; 022 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Map; 027import java.util.TreeMap; 028 029import org.biojava.nbio.structure.Atom; 030import org.biojava.nbio.structure.Chain; 031import org.biojava.nbio.structure.Element; 032import org.biojava.nbio.structure.EntityInfo; 033import org.biojava.nbio.structure.Group; 034import org.biojava.nbio.structure.GroupType; 035import org.biojava.nbio.structure.ResidueNumber; 036import org.biojava.nbio.structure.Structure; 037import org.biojava.nbio.structure.asa.AsaCalculator; 038import org.biojava.nbio.structure.asa.GroupAsa; 039import org.biojava.nbio.structure.io.FileConvert; 040import org.biojava.nbio.structure.io.FileParsingParameters; 041import org.biojava.nbio.structure.io.mmcif.MMCIFFileTools; 042import org.biojava.nbio.structure.io.mmcif.SimpleMMcifParser; 043import org.biojava.nbio.structure.io.mmcif.chem.PolymerType; 044import org.biojava.nbio.structure.io.mmcif.model.AtomSite; 045import org.biojava.nbio.structure.io.mmcif.model.ChemComp; 046import org.biojava.nbio.structure.xtal.CrystalTransform; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050 051/** 052 * An interface between 2 molecules (2 sets of atoms). 053 * 054 * @author duarte_j 055 * 056 */ 057public class StructureInterface implements Serializable, Comparable<StructureInterface> { 058 059 private static final long serialVersionUID = 1L; 060 061 private static final Logger logger = LoggerFactory.getLogger(StructureInterface.class); 062 063 /** 064 * Interfaces with larger inverse self contact overlap score will be considered isologous 065 */ 066 private static final double SELF_SCORE_FOR_ISOLOGOUS = 0.3; 067 068 private int id; 069 private double totalArea; 070 private AtomContactSet contacts; 071 private GroupContactSet groupContacts; 072 073 private Pair<Atom[]> molecules; 074 075 /** 076 * The identifier for each of the atom arrays (usually a chain identifier, i.e. a single capital letter) 077 * Serves to identify the molecules within the Asymmetric Unit of the crystal 078 */ 079 private Pair<String> moleculeIds; 080 081 /** 082 * The transformations (crystal operators) applied to each molecule (if applicable) 083 */ 084 private Pair<CrystalTransform> transforms; 085 086 private Map<ResidueNumber, GroupAsa> groupAsas1; 087 private Map<ResidueNumber, GroupAsa> groupAsas2; 088 089 private StructureInterfaceCluster cluster; 090 091 /** 092 * Constructs a StructureInterface 093 * @param firstMolecule the atoms of the first molecule 094 * @param secondMolecule the atoms of the second molecule 095 * @param firstMoleculeId an identifier that identifies the first molecule within the Asymmetric Unit 096 * @param secondMoleculeId an identifier that identifies the second molecule within the Asymmetric Unit 097 * @param contacts the contacts between the 2 molecules 098 * @param firstTransf the transformation (crystal operator) applied to first molecule 099 * @param secondTransf the transformation (crystal operator) applied to second molecule 100 */ 101 public StructureInterface( 102 Atom[] firstMolecule, Atom[] secondMolecule, 103 String firstMoleculeId, String secondMoleculeId, 104 AtomContactSet contacts, 105 CrystalTransform firstTransf, CrystalTransform secondTransf) { 106 107 this.molecules = new Pair<>(firstMolecule, secondMolecule); 108 this.moleculeIds = new Pair<>(firstMoleculeId,secondMoleculeId); 109 this.contacts = contacts; 110 this.transforms = new Pair<>(firstTransf, secondTransf); 111 } 112 113 /** 114 * Constructs an empty StructureInterface 115 */ 116 public StructureInterface() { 117 this.groupAsas1 = new TreeMap<>(); 118 this.groupAsas2 = new TreeMap<>(); 119 } 120 121 public int getId() { 122 return id; 123 } 124 125 public void setId(int id) { 126 this.id = id; 127 } 128 129 /** 130 * Returns a pair of identifiers for each of the 2 member molecules that 131 * identify them uniquely in the crystal: 132 * <molecule id (asym unit id)>+<operator id>+<crystal translation> 133 * @return 134 */ 135 public Pair<String> getCrystalIds() { 136 return new Pair<>( 137 moleculeIds.getFirst()+transforms.getFirst().getTransformId()+transforms.getFirst().getCrystalTranslation(), 138 moleculeIds.getSecond()+transforms.getSecond().getTransformId()+transforms.getSecond().getCrystalTranslation()); 139 } 140 141 /** 142 * Returns the total area buried upon formation of this interface, 143 * defined as: 1/2[ (ASA1u-ASA1c) + (ASA2u-ASA2u) ] , with: 144 * <p>ASAxu = ASA of first/second unbound chain</p> 145 * <p>ASAxc = ASA of first/second complexed chain</p> 146 * In the area calculation HETATOM groups not part of the main protein/nucleotide chain 147 * are not included. 148 * @return 149 */ 150 public double getTotalArea() { 151 return totalArea; 152 } 153 154 public void setTotalArea(double totalArea) { 155 this.totalArea = totalArea; 156 } 157 158 public AtomContactSet getContacts() { 159 return contacts; 160 } 161 162 public void setContacts(AtomContactSet contacts) { 163 this.contacts = contacts; 164 } 165 166 public Pair<Atom[]> getMolecules() { 167 return molecules; 168 } 169 170 public void setMolecules(Pair<Atom[]> molecules) { 171 this.molecules = molecules; 172 } 173 174 /** 175 * Return the pair of identifiers identifying each of the 2 molecules of this interface 176 * in the asymmetry unit (usually the chain identifier if this interface is between 2 chains) 177 * @return 178 */ 179 public Pair<String> getMoleculeIds() { 180 return moleculeIds; 181 } 182 183 public void setMoleculeIds(Pair<String> moleculeIds) { 184 this.moleculeIds = moleculeIds; 185 } 186 187 /** 188 * Return the 2 crystal transform operations performed on each of the 189 * molecules of this interface. 190 * @return 191 */ 192 public Pair<CrystalTransform> getTransforms() { 193 return transforms; 194 } 195 196 public void setTransforms(Pair<CrystalTransform> transforms) { 197 this.transforms = transforms; 198 } 199 200 /** 201 * Set ASA annotations by passing the uncomplexed ASA values of the 2 partners. 202 * This will calculate complexed ASA and set the ASA values in the member variables. 203 * @param asas1 ASA values for atoms of partner 1 204 * @param asas2 ASA values for atoms of partner 2 205 * @param nSpherePoints the number of sphere points to be used for complexed ASA calculation 206 * @param nThreads the number of threads to be used for complexed ASA calculation 207 * @param cofactorSizeToUse the minimum size of cofactor molecule (non-chain HET atoms) that will be used in ASA calculation 208 */ 209 void setAsas(double[] asas1, double[] asas2, int nSpherePoints, int nThreads, int cofactorSizeToUse) { 210 211 Atom[] atoms = getAtomsForAsa(cofactorSizeToUse); 212 AsaCalculator asaCalc = new AsaCalculator(atoms, 213 AsaCalculator.DEFAULT_PROBE_SIZE, nSpherePoints, nThreads); 214 215 double[] complexAsas = asaCalc.calculateAsas(); 216 217 if (complexAsas.length!=asas1.length+asas2.length) 218 throw new IllegalArgumentException("The size of ASAs of complex doesn't match that of ASAs 1 + ASAs 2"); 219 220 221 groupAsas1 = new TreeMap<>(); 222 groupAsas2 = new TreeMap<>(); 223 224 this.totalArea = 0; 225 226 for (int i=0;i<asas1.length;i++) { 227 Group g = atoms[i].getGroup(); 228 229 if (!g.getType().equals(GroupType.HETATM) || 230 isInChain(g)) { 231 // interface area should be only for protein/nucleotide but not hetatoms that are not part of the chain 232 this.totalArea += (asas1[i] - complexAsas[i]); 233 } 234 235 if (!groupAsas1.containsKey(g.getResidueNumber())) { 236 GroupAsa groupAsa = new GroupAsa(g); 237 groupAsa.addAtomAsaU(asas1[i]); 238 groupAsa.addAtomAsaC(complexAsas[i]); 239 groupAsas1.put(g.getResidueNumber(), groupAsa); 240 } else { 241 GroupAsa groupAsa = groupAsas1.get(g.getResidueNumber()); 242 groupAsa.addAtomAsaU(asas1[i]); 243 groupAsa.addAtomAsaC(complexAsas[i]); 244 } 245 } 246 247 for (int i=0;i<asas2.length;i++) { 248 Group g = atoms[i+asas1.length].getGroup(); 249 250 if (!g.getType().equals(GroupType.HETATM) || 251 isInChain(g)) { 252 // interface area should be only for protein/nucleotide but not hetatoms that are not part of the chain 253 this.totalArea += (asas2[i] - complexAsas[i+asas1.length]); 254 } 255 256 if (!groupAsas2.containsKey(g.getResidueNumber())) { 257 GroupAsa groupAsa = new GroupAsa(g); 258 groupAsa.addAtomAsaU(asas2[i]); 259 groupAsa.addAtomAsaC(complexAsas[i+asas1.length]); 260 groupAsas2.put(g.getResidueNumber(), groupAsa); 261 } else { 262 GroupAsa groupAsa = groupAsas2.get(g.getResidueNumber()); 263 groupAsa.addAtomAsaU(asas2[i]); 264 groupAsa.addAtomAsaC(complexAsas[i+asas1.length]); 265 } 266 } 267 268 // our interface area definition: average of bsa of both molecules 269 this.totalArea = this.totalArea/2.0; 270 271 } 272 273 protected Atom[] getFirstAtomsForAsa(int cofactorSizeToUse) { 274 275 return getAllNonHAtomArray(molecules.getFirst(), cofactorSizeToUse); 276 } 277 278 protected Atom[] getSecondAtomsForAsa(int cofactorSizeToUse) { 279 280 return getAllNonHAtomArray(molecules.getSecond(), cofactorSizeToUse); 281 } 282 283 protected Atom[] getAtomsForAsa(int cofactorSizeToUse) { 284 Atom[] atoms1 = getFirstAtomsForAsa(cofactorSizeToUse); 285 Atom[] atoms2 = getSecondAtomsForAsa(cofactorSizeToUse); 286 287 Atom[] atoms = new Atom[atoms1.length+atoms2.length]; 288 for (int i=0;i<atoms1.length;i++) { 289 atoms[i] = atoms1[i]; 290 } 291 for (int i=0;i<atoms2.length;i++) { 292 atoms[i+atoms1.length] = atoms2[i]; 293 } 294 295 return atoms; 296 } 297 298 /** 299 * Returns and array of all non-Hydrogen atoms in the given molecule, including all 300 * main chain HETATOM groups. Non main-chain HETATOM groups with fewer than minSizeHetAtomToInclude 301 * non-Hydrogen atoms are not included. 302 * @param m 303 * @param minSizeHetAtomToInclude HETATOM groups (non main-chain) with fewer number of 304 * non-Hydrogen atoms are not included 305 * @return 306 */ 307 private static final Atom[] getAllNonHAtomArray(Atom[] m, int minSizeHetAtomToInclude) { 308 List<Atom> atoms = new ArrayList<>(); 309 310 for (Atom a:m){ 311 312 if (a.getElement()==Element.H) continue; 313 314 Group g = a.getGroup(); 315 if (g.getType().equals(GroupType.HETATM) && 316 !isInChain(g) && 317 getSizeNoH(g)<minSizeHetAtomToInclude) { 318 continue; 319 } 320 321 atoms.add(a); 322 323 } 324 return atoms.toArray(new Atom[atoms.size()]); 325 } 326 327 /** 328 * Calculates the number of non-Hydrogen atoms in the given group 329 * @param g 330 * @return 331 */ 332 private static int getSizeNoH(Group g) { 333 int size = 0; 334 for (Atom a:g.getAtoms()) { 335 if (a.getElement()!=Element.H) 336 size++; 337 } 338 return size; 339 } 340 341 /** 342 * Returns true if the given group is part of the main chain, i.e. if it is 343 * a peptide-linked group or a nucleotide 344 * @param g 345 * @return 346 */ 347 private static boolean isInChain(Group g) { 348 ChemComp chemComp = g.getChemComp(); 349 350 if (chemComp==null) { 351 logger.warn("Warning: can't determine PolymerType for group "+g.getResidueNumber()+" ("+g.getPDBName()+"). Will consider it as non-nucleotide/non-protein type."); 352 return false; 353 } 354 355 PolymerType polyType = chemComp.getPolymerType(); 356 for (PolymerType protOnlyType: PolymerType.PROTEIN_ONLY) { 357 if (polyType==protOnlyType) return true; 358 } 359 for (PolymerType protOnlyType: PolymerType.POLYNUCLEOTIDE_ONLY) { 360 if (polyType==protOnlyType) return true; 361 } 362 363 return false; 364 } 365 366 /** 367 * Tells whether the interface corresponds to one mediated by crystallographic symmetry, 368 * i.e. it is between symmetry-related molecules (with same chain identifier) 369 * @return 370 */ 371 public boolean isSymRelated() { 372 return moleculeIds.getFirst().equals(moleculeIds.getSecond()); 373 } 374 375 /** 376 * Returns true if the transformation applied to the second molecule of this interface 377 * has an infinite character (pure translation or screw rotation) 378 * and both molecules of the interface have the same asymmetric unit identifier (chain id): in such cases the 379 * interface would lead to infinite fiber-like (linear or helical) assemblies 380 * @return 381 */ 382 public boolean isInfinite() { 383 return ((isSymRelated() && transforms.getSecond().getTransformType().isInfinite())); 384 } 385 386 /** 387 * Returns true if the 2 molecules of this interface are the same entity (i.e. homomeric interface), false 388 * otherwise (i.e. heteromeric interface) 389 * @return true if homomeric or if either of the entities is unknonw (null Compounds), false otherwise 390 */ 391 public boolean isHomomeric() { 392 EntityInfo first = getParentChains().getFirst().getEntityInfo(); 393 EntityInfo second = getParentChains().getSecond().getEntityInfo(); 394 if (first==null || second==null) { 395 logger.warn("Some compound of interface {} is null, can't determine whether it is homo/heteromeric. Consider it homomeric", getId()); 396 return true; 397 } 398 return 399 first.getRepresentative().getId().equals(second.getRepresentative().getId()); 400 } 401 402 /** 403 * Gets a map of ResidueNumbers to GroupAsas for all groups of first chain. 404 * @return 405 */ 406 public Map<ResidueNumber, GroupAsa> getFirstGroupAsas() { 407 return groupAsas1; 408 } 409 410 /** 411 * Gets the GroupAsa for the corresponding residue number of first chain 412 * @param resNum 413 * @return 414 */ 415 public GroupAsa getFirstGroupAsa(ResidueNumber resNum) { 416 return groupAsas1.get(resNum); 417 } 418 419 public void setFirstGroupAsa(GroupAsa groupAsa) { 420 groupAsas1.put(groupAsa.getGroup().getResidueNumber(), groupAsa); 421 } 422 423 void setFirstGroupAsas(Map<ResidueNumber, GroupAsa> firstGroupAsas) { 424 this.groupAsas1 = firstGroupAsas; 425 } 426 427 void setSecondGroupAsas(Map<ResidueNumber, GroupAsa> secondGroupAsas) { 428 this.groupAsas2 = secondGroupAsas; 429 } 430 431 /** 432 * Gets a map of ResidueNumbers to GroupAsas for all groups of second chain. 433 * @return 434 */ 435 public Map<ResidueNumber, GroupAsa> getSecondGroupAsas() { 436 return groupAsas2; 437 } 438 439 public void setSecondGroupAsa(GroupAsa groupAsa) { 440 groupAsas2.put(groupAsa.getGroup().getResidueNumber(), groupAsa); 441 } 442 443 /** 444 * Gets the GroupAsa for the corresponding residue number of second chain 445 * @param resNum 446 * @return 447 */ 448 public GroupAsa getSecondGroupAsa(ResidueNumber resNum) { 449 return groupAsas2.get(resNum); 450 } 451 452 /** 453 * Returns the residues belonging to the interface core, defined as those residues at 454 * the interface (BSA>0) and for which the BSA/ASA ratio is above the given bsaToAsaCutoff 455 * @param bsaToAsaCutoff 456 * @param minAsaForSurface the minimum ASA to consider a residue on the surface 457 * @return 458 */ 459 public Pair<List<Group>> getCoreResidues(double bsaToAsaCutoff, double minAsaForSurface) { 460 461 List<Group> core1 = new ArrayList<Group>(); 462 List<Group> core2 = new ArrayList<Group>(); 463 464 for (GroupAsa groupAsa:groupAsas1.values()) { 465 466 if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) { 467 if (groupAsa.getBsaToAsaRatio()<bsaToAsaCutoff) { 468 //rim1.add(groupAsa.getGroup()); 469 } else { 470 core1.add(groupAsa.getGroup()); 471 } 472 } 473 } 474 for (GroupAsa groupAsa:groupAsas2.values()) { 475 476 if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) { 477 if (groupAsa.getBsaToAsaRatio()<bsaToAsaCutoff) { 478 //rim2.add(groupAsa.getGroup()); 479 } else { 480 core2.add(groupAsa.getGroup()); 481 } 482 } 483 } 484 485 return new Pair<List<Group>>(core1, core2); 486 } 487 488 /** 489 * Returns the residues belonging to the interface rim, defined as those residues at 490 * the interface (BSA>0) and for which the BSA/ASA ratio is below the given bsaToAsaCutoff 491 * @param bsaToAsaCutoff 492 * @param minAsaForSurface the minimum ASA to consider a residue on the surface 493 * @return 494 */ 495 public Pair<List<Group>> getRimResidues(double bsaToAsaCutoff, double minAsaForSurface) { 496 497 List<Group> rim1 = new ArrayList<Group>(); 498 List<Group> rim2 = new ArrayList<Group>(); 499 500 for (GroupAsa groupAsa:groupAsas1.values()) { 501 502 if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) { 503 if (groupAsa.getBsaToAsaRatio()<bsaToAsaCutoff) { 504 rim1.add(groupAsa.getGroup()); 505 } else { 506 //core1.add(groupAsa.getGroup()); 507 } 508 } 509 } 510 for (GroupAsa groupAsa:groupAsas2.values()) { 511 512 if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) { 513 if (groupAsa.getBsaToAsaRatio()<bsaToAsaCutoff) { 514 rim2.add(groupAsa.getGroup()); 515 } else { 516 //core2.add(groupAsa.getGroup()); 517 } 518 } 519 } 520 521 return new Pair<List<Group>>(rim1, rim2); 522 } 523 524 /** 525 * Returns the residues belonging to the interface, i.e. the residues 526 * at the surface with BSA>0 527 * @param minAsaForSurface the minimum ASA to consider a residue on the surface 528 * @return 529 */ 530 public Pair<List<Group>> getInterfacingResidues(double minAsaForSurface) { 531 532 List<Group> interf1 = new ArrayList<Group>(); 533 List<Group> interf2 = new ArrayList<Group>(); 534 535 for (GroupAsa groupAsa:groupAsas1.values()) { 536 537 if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) { 538 interf1.add(groupAsa.getGroup()); 539 } 540 } 541 for (GroupAsa groupAsa:groupAsas2.values()) { 542 543 if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) { 544 interf2.add(groupAsa.getGroup()); 545 } 546 } 547 548 return new Pair<List<Group>>(interf1, interf2); 549 } 550 551 /** 552 * Returns the residues belonging to the surface 553 * @param minAsaForSurface the minimum ASA to consider a residue on the surface 554 * @return 555 */ 556 public Pair<List<Group>> getSurfaceResidues(double minAsaForSurface) { 557 List<Group> surf1 = new ArrayList<Group>(); 558 List<Group> surf2 = new ArrayList<Group>(); 559 560 for (GroupAsa groupAsa:groupAsas1.values()) { 561 562 if (groupAsa.getAsaU()>minAsaForSurface) { 563 surf1.add(groupAsa.getGroup()); 564 } 565 } 566 for (GroupAsa groupAsa:groupAsas2.values()) { 567 568 if (groupAsa.getAsaU()>minAsaForSurface) { 569 surf2.add(groupAsa.getGroup()); 570 } 571 } 572 573 return new Pair<List<Group>>(surf1, surf2); 574 } 575 576 public StructureInterfaceCluster getCluster() { 577 return cluster; 578 } 579 580 public void setCluster(StructureInterfaceCluster cluster) { 581 this.cluster = cluster; 582 } 583 584 /** 585 * Calculates the contact overlap score between this StructureInterface and 586 * the given one. 587 * The two sides of the given StructureInterface need to match this StructureInterface 588 * in the sense that they must come from the same Compound (Entity), i.e. 589 * their residue numbers need to align with 100% identity, except for unobserved 590 * density residues. The SEQRES indices obtained through {@link EntityInfo#getAlignedResIndex(Group, Chain)} are 591 * used to match residues, thus if no SEQRES is present or if {@link FileParsingParameters#setAlignSeqRes(boolean)} 592 * is not used, this calculation is not guaranteed to work properly. 593 * @param other 594 * @param invert if false the comparison will be done first-to-first and second-to-second, 595 * if true the match will be first-to-second and second-to-first 596 * @return the contact overlap score, range [0.0,1.0] 597 */ 598 public double getContactOverlapScore(StructureInterface other, boolean invert) { 599 600 Structure thisStruct = getParentStructure(); 601 Structure otherStruct = other.getParentStructure(); 602 603 if (thisStruct!=otherStruct) { 604 // in the current implementation, comparison between different structure doesn't make much sense 605 // and won't even work since the compounds of both will never match. We warn because it 606 // really is not what this is intended for at the moment 607 logger.warn("Comparing interfaces from different structures, contact overlap score will be 0"); 608 return 0; 609 } 610 611 Pair<Chain> thisChains = getParentChains(); 612 Pair<Chain> otherChains = other.getParentChains(); 613 614 if (thisChains.getFirst().getEntityInfo() == null || thisChains.getSecond().getEntityInfo() == null || 615 otherChains.getFirst().getEntityInfo() == null || otherChains.getSecond().getEntityInfo() == null ) { 616 // this happens in cases like 2uub 617 logger.warn("Found chains with null compounds while comparing interfaces {} and {}. Contact overlap score for them will be 0.", 618 this.getId(), other.getId()); 619 return 0; 620 } 621 622 Pair<EntityInfo> thisCompounds = new Pair<EntityInfo>(thisChains.getFirst().getEntityInfo(), thisChains.getSecond().getEntityInfo()); 623 Pair<EntityInfo> otherCompounds = new Pair<EntityInfo>(otherChains.getFirst().getEntityInfo(), otherChains.getSecond().getEntityInfo()); 624 625 if ( ( (thisCompounds.getFirst() == otherCompounds.getFirst()) && 626 (thisCompounds.getSecond() == otherCompounds.getSecond()) ) || 627 ( (thisCompounds.getFirst() == otherCompounds.getSecond()) && 628 (thisCompounds.getSecond() == otherCompounds.getFirst()) ) ) { 629 630 int common = 0; 631 GroupContactSet thisContacts = getGroupContacts(); 632 GroupContactSet otherContacts = other.getGroupContacts(); 633 634 for (GroupContact thisContact:thisContacts) { 635 636 ResidueIdentifier first = null; 637 ResidueIdentifier second = null; 638 639 if (!invert) { 640 first = new ResidueIdentifier(thisContact.getPair().getFirst()); 641 642 second = new ResidueIdentifier(thisContact.getPair().getSecond()); 643 } else { 644 first = new ResidueIdentifier(thisContact.getPair().getSecond()); 645 646 second = new ResidueIdentifier(thisContact.getPair().getFirst()); 647 } 648 649 if (otherContacts.hasContact(first,second)) { 650 common++; 651 } 652 } 653 return (2.0*common)/(thisContacts.size()+otherContacts.size()); 654 } else { 655 logger.debug("Chain pairs {},{} and {},{} belong to different compound pairs, contact overlap score will be 0 ", 656 thisChains.getFirst().getId(),thisChains.getSecond().getId(), 657 otherChains.getFirst().getId(),otherChains.getSecond().getId()); 658 return 0.0; 659 } 660 } 661 662 public GroupContactSet getGroupContacts() { 663 if (groupContacts==null) { 664 this.groupContacts = new GroupContactSet(contacts); 665 } 666 return this.groupContacts; 667 } 668 669 /** 670 * Tell whether the interface is isologous, i.e. it is formed 671 * by the same patches of same Compound on both sides. 672 * 673 * @return true if isologous, false if heterologous 674 */ 675 public boolean isIsologous() { 676 double scoreInverse = this.getContactOverlapScore(this, true); 677 logger.debug("Interface {} contact overlap score with itself inverted: {}", 678 getId(), scoreInverse); 679 return (scoreInverse>SELF_SCORE_FOR_ISOLOGOUS); 680 } 681 682 /** 683 * Finds the parent chains by looking up the references of first atom of each side of this interface 684 * @return 685 */ 686 public Pair<Chain> getParentChains() { 687 Atom[] firstMol = this.molecules.getFirst(); 688 Atom[] secondMol = this.molecules.getSecond(); 689 if (firstMol.length==0 || secondMol.length==0) { 690 logger.warn("No atoms found in first or second molecule, can't get parent Chains"); 691 return null; 692 } 693 694 return new Pair<Chain>(firstMol[0].getGroup().getChain(), secondMol[0].getGroup().getChain()); 695 } 696 697 /** 698 * Finds the parent compounds by looking up the references of first atom of each side of this interface 699 * @return 700 */ 701 public Pair<EntityInfo> getParentCompounds() { 702 Pair<Chain> chains = getParentChains(); 703 if (chains == null) { 704 logger.warn("Could not find parents chains, compounds will be null"); 705 return null; 706 } 707 return new Pair<EntityInfo>(chains.getFirst().getEntityInfo(), chains.getSecond().getEntityInfo()); 708 } 709 710 private Structure getParentStructure() { 711 Atom[] firstMol = this.molecules.getFirst(); 712 if (firstMol.length==0) { 713 logger.warn("No atoms found in first molecule, can't get parent Structure"); 714 return null; 715 } 716 return firstMol[0].getGroup().getChain().getStructure(); 717 } 718 719 /** 720 * Return a String representing the 2 molecules of this interface in PDB format. 721 * If the molecule ids (i.e. chain ids) are the same for both molecules, then the second 722 * one will be replaced by the next letter in alphabet (or A for Z) 723 * @return 724 */ 725 public String toPDB() { 726 727 String molecId1 = getMoleculeIds().getFirst(); 728 String molecId2 = getMoleculeIds().getSecond(); 729 730 if (molecId2.equals(molecId1)) { 731 // if both chains are named equally we want to still named them differently in the output pdb file 732 // so that molecular viewers can handle properly the 2 chains as separate entities 733 char letter = molecId1.charAt(0); 734 if (letter!='Z' && letter!='z') { 735 molecId2 = Character.toString((char)(letter+1)); // i.e. next letter in alphabet 736 } else { 737 molecId2 = Character.toString((char)(letter-25)); //i.e. 'A' or 'a' 738 } 739 } 740 741 StringBuilder sb = new StringBuilder(); 742 for (Atom atom:this.molecules.getFirst()) { 743 sb.append(FileConvert.toPDB(atom, molecId1)); 744 } 745 sb.append("TER"); 746 sb.append(System.getProperty("line.separator")); 747 for (Atom atom:this.molecules.getSecond()) { 748 sb.append(FileConvert.toPDB(atom,molecId2)); 749 } 750 sb.append("TER"); 751 sb.append(System.getProperty("line.separator")); 752 sb.append("END"); 753 sb.append(System.getProperty("line.separator")); 754 return sb.toString(); 755 } 756 757 /** 758 * Return a String representing the 2 molecules of this interface in mmCIF format. 759 * If the molecule ids (i.e. chain ids) are the same for both molecules, then the second 760 * one will be written as chainId_operatorId (with operatorId taken from {@link #getTransforms()} 761 * @return 762 */ 763 public String toMMCIF() { 764 StringBuilder sb = new StringBuilder(); 765 766 String molecId1 = getMoleculeIds().getFirst(); 767 String molecId2 = getMoleculeIds().getSecond(); 768 769 if (isSymRelated()) { 770 // if both chains are named equally we want to still named them differently in the output mmcif file 771 // so that molecular viewers can handle properly the 2 chains as separate entities 772 molecId2 = molecId2 + "_" +getTransforms().getSecond().getTransformId(); 773 } 774 775 sb.append(SimpleMMcifParser.MMCIF_TOP_HEADER).append("BioJava_interface_").append(getId()).append(System.getProperty("line.separator")); 776 777 sb.append(FileConvert.getAtomSiteHeader()); 778 779 // we reassign atom ids if sym related (otherwise atom ids would be duplicated and some molecular viewers can't cope with that) 780 int atomId = 1; 781 List<AtomSite> atomSites = new ArrayList<>(); 782 for (Atom atom:this.molecules.getFirst()) { 783 if (isSymRelated()) { 784 atomSites.add(MMCIFFileTools.convertAtomToAtomSite(atom, 1, molecId1, molecId1, atomId)); 785 } else { 786 atomSites.add(MMCIFFileTools.convertAtomToAtomSite(atom, 1, molecId1, molecId1)); 787 } 788 atomId++; 789 } 790 for (Atom atom:this.molecules.getSecond()) { 791 if (isSymRelated()) { 792 atomSites.add(MMCIFFileTools.convertAtomToAtomSite(atom, 1, molecId2, molecId2, atomId)); 793 } else { 794 atomSites.add(MMCIFFileTools.convertAtomToAtomSite(atom, 1, molecId2, molecId2)); 795 } 796 atomId++; 797 } 798 799 sb.append(MMCIFFileTools.toMMCIF(atomSites,AtomSite.class)); 800 801 return sb.toString(); 802 } 803 804 @Override 805 public int compareTo(StructureInterface o) { 806 // this will sort descending on interface areas 807 return (Double.compare(o.totalArea,this.totalArea)); 808 } 809 810 @Override 811 public String toString() { 812 return String.format("StructureInterface %d (%s, %.0f A, <%s; %s>)", id, moleculeIds,totalArea,transforms.getFirst().toXYZString(),transforms.getSecond().toXYZString()); 813 } 814 815}