/*
* 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/
*
*/
package org.biojava.nbio.structure.contact;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.biojava.nbio.structure.Atom;
import org.biojava.nbio.structure.Chain;
import org.biojava.nbio.structure.Element;
import org.biojava.nbio.structure.EntityInfo;
import org.biojava.nbio.structure.Group;
import org.biojava.nbio.structure.GroupType;
import org.biojava.nbio.structure.ResidueNumber;
import org.biojava.nbio.structure.Structure;
import org.biojava.nbio.structure.asa.AsaCalculator;
import org.biojava.nbio.structure.asa.GroupAsa;
import org.biojava.nbio.structure.io.FileConvert;
import org.biojava.nbio.structure.io.FileParsingParameters;
import org.biojava.nbio.structure.io.mmcif.MMCIFFileTools;
import org.biojava.nbio.structure.io.mmcif.SimpleMMcifParser;
import org.biojava.nbio.structure.io.mmcif.chem.PolymerType;
import org.biojava.nbio.structure.io.mmcif.model.AtomSite;
import org.biojava.nbio.structure.io.mmcif.model.ChemComp;
import org.biojava.nbio.structure.xtal.CrystalTransform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An interface between 2 molecules (2 sets of atoms).
*
* @author duarte_j
*
*/
public class StructureInterface implements Serializable, Comparable<StructureInterface> {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(StructureInterface.class);
/**
* Interfaces with larger inverse self contact overlap score will be considered isologous
*/
private static final double SELF_SCORE_FOR_ISOLOGOUS = 0.3;
private int id;
private double totalArea;
private AtomContactSet contacts;
private GroupContactSet groupContacts;
private Pair<Atom[]> molecules;
/**
* The identifier for each of the atom arrays (usually a chain identifier, i.e. a single capital letter)
* Serves to identify the molecules within the Asymmetric Unit of the crystal
*/
private Pair<String> moleculeIds;
/**
* The transformations (crystal operators) applied to each molecule (if applicable)
*/
private Pair<CrystalTransform> transforms;
private Map<ResidueNumber, GroupAsa> groupAsas1;
private Map<ResidueNumber, GroupAsa> groupAsas2;
private StructureInterfaceCluster cluster;
/**
* Constructs a StructureInterface
* @param firstMolecule the atoms of the first molecule
* @param secondMolecule the atoms of the second molecule
* @param firstMoleculeId an identifier that identifies the first molecule within the Asymmetric Unit
* @param secondMoleculeId an identifier that identifies the second molecule within the Asymmetric Unit
* @param contacts the contacts between the 2 molecules
* @param firstTransf the transformation (crystal operator) applied to first molecule
* @param secondTransf the transformation (crystal operator) applied to second molecule
*/
public StructureInterface(
Atom[] firstMolecule, Atom[] secondMolecule,
String firstMoleculeId, String secondMoleculeId,
AtomContactSet contacts,
CrystalTransform firstTransf, CrystalTransform secondTransf) {
this.molecules = new Pair<Atom[]>(firstMolecule, secondMolecule);
this.moleculeIds = new Pair<String>(firstMoleculeId,secondMoleculeId);
this.contacts = contacts;
this.transforms = new Pair<CrystalTransform>(firstTransf, secondTransf);
}
/**
* Constructs an empty StructureInterface
*/
public StructureInterface() {
this.groupAsas1 = new TreeMap<ResidueNumber, GroupAsa>();
this.groupAsas2 = new TreeMap<ResidueNumber, GroupAsa>();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
/**
* Returns a pair of identifiers for each of the 2 member molecules that
* identify them uniquely in the crystal:
* <molecule id (asym unit id)>+<operator id>+<crystal translation>
* @return
*/
public Pair<String> getCrystalIds() {
return new Pair<String>(
moleculeIds.getFirst()+transforms.getFirst().getTransformId()+transforms.getFirst().getCrystalTranslation(),
moleculeIds.getSecond()+transforms.getSecond().getTransformId()+transforms.getSecond().getCrystalTranslation());
}
/**
* Returns the total area buried upon formation of this interface,
* defined as: 1/2[ (ASA1u-ASA1c) + (ASA2u-ASA2u) ] , with:
* <p>ASAxu = ASA of first/second unbound chain</p>
* <p>ASAxc = ASA of first/second complexed chain</p>
* In the area calculation HETATOM groups not part of the main protein/nucleotide chain
* are not included.
* @return
*/
public double getTotalArea() {
return totalArea;
}
public void setTotalArea(double totalArea) {
this.totalArea = totalArea;
}
public AtomContactSet getContacts() {
return contacts;
}
public void setContacts(AtomContactSet contacts) {
this.contacts = contacts;
}
public Pair<Atom[]> getMolecules() {
return molecules;
}
public void setMolecules(Pair<Atom[]> molecules) {
this.molecules = molecules;
}
/**
* Return the pair of identifiers identifying each of the 2 molecules of this interface
* in the asymmetry unit (usually the chain identifier if this interface is between 2 chains)
* @return
*/
public Pair<String> getMoleculeIds() {
return moleculeIds;
}
public void setMoleculeIds(Pair<String> moleculeIds) {
this.moleculeIds = moleculeIds;
}
/**
* Return the 2 crystal transform operations performed on each of the
* molecules of this interface.
* @return
*/
public Pair<CrystalTransform> getTransforms() {
return transforms;
}
public void setTransforms(Pair<CrystalTransform> transforms) {
this.transforms = transforms;
}
protected void setAsas(double[] asas1, double[] asas2, int nSpherePoints, int nThreads, int cofactorSizeToUse) {
Atom[] atoms = getAtomsForAsa(cofactorSizeToUse);
AsaCalculator asaCalc = new AsaCalculator(atoms,
AsaCalculator.DEFAULT_PROBE_SIZE, nSpherePoints, nThreads);
double[] complexAsas = asaCalc.calculateAsas();
if (complexAsas.length!=asas1.length+asas2.length)
throw new IllegalArgumentException("The size of ASAs of complex doesn't match that of ASAs 1 + ASAs 2");
groupAsas1 = new TreeMap<ResidueNumber, GroupAsa>();
groupAsas2 = new TreeMap<ResidueNumber, GroupAsa>();
this.totalArea = 0;
for (int i=0;i<asas1.length;i++) {
Group g = atoms[i].getGroup();
if (!g.getType().equals(GroupType.HETATM) ||
isInChain(g)) {
// interface area should be only for protein/nucleotide but not hetatoms that are not part of the chain
this.totalArea += (asas1[i] - complexAsas[i]);
}
if (!groupAsas1.containsKey(g.getResidueNumber())) {
GroupAsa groupAsa = new GroupAsa(g);
groupAsa.addAtomAsaU(asas1[i]);
groupAsa.addAtomAsaC(complexAsas[i]);
groupAsas1.put(g.getResidueNumber(), groupAsa);
} else {
GroupAsa groupAsa = groupAsas1.get(g.getResidueNumber());
groupAsa.addAtomAsaU(asas1[i]);
groupAsa.addAtomAsaC(complexAsas[i]);
}
}
for (int i=0;i<asas2.length;i++) {
Group g = atoms[i+asas1.length].getGroup();
if (!g.getType().equals(GroupType.HETATM) ||
isInChain(g)) {
// interface area should be only for protein/nucleotide but not hetatoms that are not part of the chain
this.totalArea += (asas2[i] - complexAsas[i+asas1.length]);
}
if (!groupAsas2.containsKey(g.getResidueNumber())) {
GroupAsa groupAsa = new GroupAsa(g);
groupAsa.addAtomAsaU(asas2[i]);
groupAsa.addAtomAsaC(complexAsas[i+asas1.length]);
groupAsas2.put(g.getResidueNumber(), groupAsa);
} else {
GroupAsa groupAsa = groupAsas2.get(g.getResidueNumber());
groupAsa.addAtomAsaU(asas2[i]);
groupAsa.addAtomAsaC(complexAsas[i+asas1.length]);
}
}
// our interface area definition: average of bsa of both molecules
this.totalArea = this.totalArea/2.0;
}
protected Atom[] getFirstAtomsForAsa(int cofactorSizeToUse) {
return getAllNonHAtomArray(molecules.getFirst(), cofactorSizeToUse);
}
protected Atom[] getSecondAtomsForAsa(int cofactorSizeToUse) {
return getAllNonHAtomArray(molecules.getSecond(), cofactorSizeToUse);
}
protected Atom[] getAtomsForAsa(int cofactorSizeToUse) {
Atom[] atoms1 = getFirstAtomsForAsa(cofactorSizeToUse);
Atom[] atoms2 = getSecondAtomsForAsa(cofactorSizeToUse);
Atom[] atoms = new Atom[atoms1.length+atoms2.length];
for (int i=0;i<atoms1.length;i++) {
atoms[i] = atoms1[i];
}
for (int i=0;i<atoms2.length;i++) {
atoms[i+atoms1.length] = atoms2[i];
}
return atoms;
}
/**
* Returns and array of all non-Hydrogen atoms in the given molecule, including all
* main chain HETATOM groups. Non main-chain HETATOM groups with fewer than minSizeHetAtomToInclude
* non-Hydrogen atoms are not included.
* @param m
* @param minSizeHetAtomToInclude HETATOM groups (non main-chain) with fewer number of
* non-Hydrogen atoms are not included
* @return
*/
private static final Atom[] getAllNonHAtomArray(Atom[] m, int minSizeHetAtomToInclude) {
List<Atom> atoms = new ArrayList<Atom>();
for (Atom a:m){
if (a.getElement()==Element.H) continue;
Group g = a.getGroup();
if (g.getType().equals(GroupType.HETATM) &&
!isInChain(g) &&
getSizeNoH(g)<minSizeHetAtomToInclude) {
continue;
}
atoms.add(a);
}
return atoms.toArray(new Atom[atoms.size()]);
}
/**
* Calculates the number of non-Hydrogen atoms in the given group
* @param g
* @return
*/
private static int getSizeNoH(Group g) {
int size = 0;
for (Atom a:g.getAtoms()) {
if (a.getElement()!=Element.H)
size++;
}
return size;
}
/**
* Returns true if the given group is part of the main chain, i.e. if it is
* a peptide-linked group or a nucleotide
* @param g
* @return
*/
private static boolean isInChain(Group g) {
ChemComp chemComp = g.getChemComp();
if (chemComp==null) {
logger.warn("Warning: can't determine PolymerType for group "+g.getResidueNumber()+" ("+g.getPDBName()+"). Will consider it as non-nucleotide/non-protein type.");
return false;
}
PolymerType polyType = chemComp.getPolymerType();
for (PolymerType protOnlyType: PolymerType.PROTEIN_ONLY) {
if (polyType==protOnlyType) return true;
}
for (PolymerType protOnlyType: PolymerType.POLYNUCLEOTIDE_ONLY) {
if (polyType==protOnlyType) return true;
}
return false;
}
/**
* Tells whether the interface corresponds to one mediated by crystallographic symmetry,
* i.e. it is between symmetry-related molecules (with same chain identifier)
* @return
*/
public boolean isSymRelated() {
return moleculeIds.getFirst().equals(moleculeIds.getSecond());
}
/**
* Returns true if the transformation applied to the second molecule of this interface
* has an infinite character (pure translation or screw rotation)
* and both molecules of the interface have the same asymmetric unit identifier (chain id): in such cases the
* interface would lead to infinite fiber-like (linear or helical) assemblies
* @return
*/
public boolean isInfinite() {
return ((isSymRelated() && transforms.getSecond().getTransformType().isInfinite()));
}
/**
* Returns true if the 2 molecules of this interface are the same entity (i.e. homomeric interface), false
* otherwise (i.e. heteromeric interface)
* @return true if homomeric or if either of the entities is unknonw (null Compounds), false otherwise
*/
public boolean isHomomeric() {
EntityInfo first = getParentChains().getFirst().getEntityInfo();
EntityInfo second = getParentChains().getSecond().getEntityInfo();
if (first==null || second==null) {
logger.warn("Some compound of interface {} is null, can't determine whether it is homo/heteromeric. Consider it homomeric", getId());
return true;
}
return
first.getRepresentative().getId().equals(second.getRepresentative().getId());
}
/**
* Gets a map of ResidueNumbers to GroupAsas for all groups of first chain.
* @return
*/
public Map<ResidueNumber, GroupAsa> getFirstGroupAsas() {
return groupAsas1;
}
/**
* Gets the GroupAsa for the corresponding residue number of first chain
* @param resNum
* @return
*/
public GroupAsa getFirstGroupAsa(ResidueNumber resNum) {
return groupAsas1.get(resNum);
}
public void setFirstGroupAsa(GroupAsa groupAsa) {
groupAsas1.put(groupAsa.getGroup().getResidueNumber(), groupAsa);
}
/**
* Gets a map of ResidueNumbers to GroupAsas for all groups of second chain.
* @return
*/
public Map<ResidueNumber, GroupAsa> getSecondGroupAsas() {
return groupAsas2;
}
public void setSecondGroupAsa(GroupAsa groupAsa) {
groupAsas2.put(groupAsa.getGroup().getResidueNumber(), groupAsa);
}
/**
* Gets the GroupAsa for the corresponding residue number of second chain
* @param resNum
* @return
*/
public GroupAsa getSecondGroupAsa(ResidueNumber resNum) {
return groupAsas2.get(resNum);
}
/**
* Returns the residues belonging to the interface core, defined as those residues at
* the interface (BSA>0) and for which the BSA/ASA ratio is above the given bsaToAsaCutoff
* @param bsaToAsaCutoff
* @param minAsaForSurface the minimum ASA to consider a residue on the surface
* @return
*/
public Pair<List<Group>> getCoreResidues(double bsaToAsaCutoff, double minAsaForSurface) {
List<Group> core1 = new ArrayList<Group>();
List<Group> core2 = new ArrayList<Group>();
for (GroupAsa groupAsa:groupAsas1.values()) {
if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) {
if (groupAsa.getBsaToAsaRatio()<bsaToAsaCutoff) {
//rim1.add(groupAsa.getGroup());
} else {
core1.add(groupAsa.getGroup());
}
}
}
for (GroupAsa groupAsa:groupAsas2.values()) {
if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) {
if (groupAsa.getBsaToAsaRatio()<bsaToAsaCutoff) {
//rim2.add(groupAsa.getGroup());
} else {
core2.add(groupAsa.getGroup());
}
}
}
return new Pair<List<Group>>(core1, core2);
}
/**
* Returns the residues belonging to the interface rim, defined as those residues at
* the interface (BSA>0) and for which the BSA/ASA ratio is below the given bsaToAsaCutoff
* @param bsaToAsaCutoff
* @param minAsaForSurface the minimum ASA to consider a residue on the surface
* @return
*/
public Pair<List<Group>> getRimResidues(double bsaToAsaCutoff, double minAsaForSurface) {
List<Group> rim1 = new ArrayList<Group>();
List<Group> rim2 = new ArrayList<Group>();
for (GroupAsa groupAsa:groupAsas1.values()) {
if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) {
if (groupAsa.getBsaToAsaRatio()<bsaToAsaCutoff) {
rim1.add(groupAsa.getGroup());
} else {
//core1.add(groupAsa.getGroup());
}
}
}
for (GroupAsa groupAsa:groupAsas2.values()) {
if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) {
if (groupAsa.getBsaToAsaRatio()<bsaToAsaCutoff) {
rim2.add(groupAsa.getGroup());
} else {
//core2.add(groupAsa.getGroup());
}
}
}
return new Pair<List<Group>>(rim1, rim2);
}
/**
* Returns the residues belonging to the interface, i.e. the residues
* at the surface with BSA>0
* @param minAsaForSurface the minimum ASA to consider a residue on the surface
* @return
*/
public Pair<List<Group>> getInterfacingResidues(double minAsaForSurface) {
List<Group> interf1 = new ArrayList<Group>();
List<Group> interf2 = new ArrayList<Group>();
for (GroupAsa groupAsa:groupAsas1.values()) {
if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) {
interf1.add(groupAsa.getGroup());
}
}
for (GroupAsa groupAsa:groupAsas2.values()) {
if (groupAsa.getAsaU()>minAsaForSurface && groupAsa.getBsa()>0) {
interf2.add(groupAsa.getGroup());
}
}
return new Pair<List<Group>>(interf1, interf2);
}
/**
* Returns the residues belonging to the surface
* @param minAsaForSurface the minimum ASA to consider a residue on the surface
* @return
*/
public Pair<List<Group>> getSurfaceResidues(double minAsaForSurface) {
List<Group> surf1 = new ArrayList<Group>();
List<Group> surf2 = new ArrayList<Group>();
for (GroupAsa groupAsa:groupAsas1.values()) {
if (groupAsa.getAsaU()>minAsaForSurface) {
surf1.add(groupAsa.getGroup());
}
}
for (GroupAsa groupAsa:groupAsas2.values()) {
if (groupAsa.getAsaU()>minAsaForSurface) {
surf2.add(groupAsa.getGroup());
}
}
return new Pair<List<Group>>(surf1, surf2);
}
public StructureInterfaceCluster getCluster() {
return cluster;
}
public void setCluster(StructureInterfaceCluster cluster) {
this.cluster = cluster;
}
/**
* Calculates the contact overlap score between this StructureInterface and
* the given one.
* The two sides of the given StructureInterface need to match this StructureInterface
* in the sense that they must come from the same Compound (Entity), i.e.
* their residue numbers need to align with 100% identity, except for unobserved
* density residues. The SEQRES indices obtained through {@link EntityInfo#getAlignedResIndex(Group, Chain)} are
* used to match residues, thus if no SEQRES is present or if {@link FileParsingParameters#setAlignSeqRes(boolean)}
* is not used, this calculation is not guaranteed to work properly.
* @param other
* @param invert if false the comparison will be done first-to-first and second-to-second,
* if true the match will be first-to-second and second-to-first
* @return the contact overlap score, range [0.0,1.0]
*/
public double getContactOverlapScore(StructureInterface other, boolean invert) {
Structure thisStruct = getParentStructure();
Structure otherStruct = other.getParentStructure();
if (thisStruct!=otherStruct) {
// in the current implementation, comparison between different structure doesn't make much sense
// and won't even work since the compounds of both will never match. We warn because it
// really is not what this is intended for at the moment
logger.warn("Comparing interfaces from different structures, contact overlap score will be 0");
return 0;
}
Pair<Chain> thisChains = getParentChains();
Pair<Chain> otherChains = other.getParentChains();
if (thisChains.getFirst().getEntityInfo() == null || thisChains.getSecond().getEntityInfo() == null ||
otherChains.getFirst().getEntityInfo() == null || otherChains.getSecond().getEntityInfo() == null ) {
// this happens in cases like 2uub
logger.warn("Found chains with null compounds while comparing interfaces {} and {}. Contact overlap score for them will be 0.",
this.getId(), other.getId());
return 0;
}
Pair<EntityInfo> thisCompounds = new Pair<EntityInfo>(thisChains.getFirst().getEntityInfo(), thisChains.getSecond().getEntityInfo());
Pair<EntityInfo> otherCompounds = new Pair<EntityInfo>(otherChains.getFirst().getEntityInfo(), otherChains.getSecond().getEntityInfo());
if ( ( (thisCompounds.getFirst() == otherCompounds.getFirst()) &&
(thisCompounds.getSecond() == otherCompounds.getSecond()) ) ||
( (thisCompounds.getFirst() == otherCompounds.getSecond()) &&
(thisCompounds.getSecond() == otherCompounds.getFirst()) ) ) {
int common = 0;
GroupContactSet thisContacts = getGroupContacts();
GroupContactSet otherContacts = other.getGroupContacts();
for (GroupContact thisContact:thisContacts) {
ResidueIdentifier first = null;
ResidueIdentifier second = null;
if (!invert) {
first = new ResidueIdentifier(thisContact.getPair().getFirst());
second = new ResidueIdentifier(thisContact.getPair().getSecond());
} else {
first = new ResidueIdentifier(thisContact.getPair().getSecond());
second = new ResidueIdentifier(thisContact.getPair().getFirst());
}
if (otherContacts.hasContact(first,second)) {
common++;
}
}
return (2.0*common)/(thisContacts.size()+otherContacts.size());
} else {
logger.debug("Chain pairs {},{} and {},{} belong to different compound pairs, contact overlap score will be 0 ",
thisChains.getFirst().getId(),thisChains.getSecond().getId(),
otherChains.getFirst().getId(),otherChains.getSecond().getId());
return 0.0;
}
}
public GroupContactSet getGroupContacts() {
if (groupContacts==null) {
this.groupContacts = new GroupContactSet(contacts);
}
return this.groupContacts;
}
/**
* Tell whether the interface is isologous, i.e. it is formed
* by the same patches of same Compound on both sides.
*
* @return true if isologous, false if heterologous
*/
public boolean isIsologous() {
double scoreInverse = this.getContactOverlapScore(this, true);
logger.debug("Interface {} contact overlap score with itself inverted: {}",
getId(), scoreInverse);
return (scoreInverse>SELF_SCORE_FOR_ISOLOGOUS);
}
/**
* Finds the parent chains by looking up the references of first atom of each side of this interface
* @return
*/
public Pair<Chain> getParentChains() {
Atom[] firstMol = this.molecules.getFirst();
Atom[] secondMol = this.molecules.getSecond();
if (firstMol.length==0 || secondMol.length==0) {
logger.warn("No atoms found in first or second molecule, can't get parent Chains");
return null;
}
return new Pair<Chain>(firstMol[0].getGroup().getChain(), secondMol[0].getGroup().getChain());
}
/**
* Finds the parent compounds by looking up the references of first atom of each side of this interface
* @return
*/
public Pair<EntityInfo> getParentCompounds() {
Pair<Chain> chains = getParentChains();
if (chains == null) {
logger.warn("Could not find parents chains, compounds will be null");
return null;
}
return new Pair<EntityInfo>(chains.getFirst().getEntityInfo(), chains.getSecond().getEntityInfo());
}
private Structure getParentStructure() {
Atom[] firstMol = this.molecules.getFirst();
if (firstMol.length==0) {
logger.warn("No atoms found in first molecule, can't get parent Structure");
return null;
}
return firstMol[0].getGroup().getChain().getStructure();
}
/**
* Return a String representing the 2 molecules of this interface in PDB format.
* If the molecule ids (i.e. chain ids) are the same for both molecules, then the second
* one will be replaced by the next letter in alphabet (or A for Z)
* @return
*/
public String toPDB() {
String molecId1 = getMoleculeIds().getFirst();
String molecId2 = getMoleculeIds().getSecond();
if (molecId2.equals(molecId1)) {
// if both chains are named equally we want to still named them differently in the output pdb file
// so that molecular viewers can handle properly the 2 chains as separate entities
char letter = molecId1.charAt(0);
if (letter!='Z' && letter!='z') {
molecId2 = Character.toString((char)(letter+1)); // i.e. next letter in alphabet
} else {
molecId2 = Character.toString((char)(letter-25)); //i.e. 'A' or 'a'
}
}
StringBuilder sb = new StringBuilder();
for (Atom atom:this.molecules.getFirst()) {
sb.append(FileConvert.toPDB(atom, molecId1));
}
sb.append("TER");
sb.append(System.getProperty("line.separator"));
for (Atom atom:this.molecules.getSecond()) {
sb.append(FileConvert.toPDB(atom,molecId2));
}
sb.append("TER");
sb.append(System.getProperty("line.separator"));
sb.append("END");
sb.append(System.getProperty("line.separator"));
return sb.toString();
}
/**
* Return a String representing the 2 molecules of this interface in mmCIF format.
* If the molecule ids (i.e. chain ids) are the same for both molecules, then the second
* one will be written as chainId_operatorId (with operatorId taken from {@link #getTransforms()}
* @return
*/
public String toMMCIF() {
StringBuilder sb = new StringBuilder();
String molecId1 = getMoleculeIds().getFirst();
String molecId2 = getMoleculeIds().getSecond();
if (isSymRelated()) {
// if both chains are named equally we want to still named them differently in the output mmcif file
// so that molecular viewers can handle properly the 2 chains as separate entities
molecId2 = molecId2 + "_" +getTransforms().getSecond().getTransformId();
}
sb.append(SimpleMMcifParser.MMCIF_TOP_HEADER).append("BioJava_interface_").append(getId()).append(System.getProperty("line.separator"));
sb.append(FileConvert.getAtomSiteHeader());
// we reassign atom ids if sym related (otherwise atom ids would be duplicated and some molecular viewers can't cope with that)
int atomId = 1;
List<AtomSite> atomSites = new ArrayList<>();
for (Atom atom:this.molecules.getFirst()) {
if (isSymRelated()) {
atomSites.add(MMCIFFileTools.convertAtomToAtomSite(atom, 1, molecId1, molecId1, atomId));
} else {
atomSites.add(MMCIFFileTools.convertAtomToAtomSite(atom, 1, molecId1, molecId1));
}
atomId++;
}
for (Atom atom:this.molecules.getSecond()) {
if (isSymRelated()) {
atomSites.add(MMCIFFileTools.convertAtomToAtomSite(atom, 1, molecId2, molecId2, atomId));
} else {
atomSites.add(MMCIFFileTools.convertAtomToAtomSite(atom, 1, molecId2, molecId2));
}
atomId++;
}
sb.append(MMCIFFileTools.toMMCIF(atomSites,AtomSite.class));
return sb.toString();
}
@Override
public int compareTo(StructureInterface o) {
// this will sort descending on interface areas
return (Double.compare(o.totalArea,this.totalArea));
}
@Override
public String toString() {
return String.format("StructureInterface %d (%s, %.0f A, <%s; %s>)", id, moleculeIds,totalArea,transforms.getFirst().toXYZString(),transforms.getSecond().toXYZString());
}
}