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.xtal; 022 023 024import org.biojava.nbio.structure.*; 025import org.biojava.nbio.structure.contact.AtomContactSet; 026import org.biojava.nbio.structure.contact.StructureInterface; 027import org.biojava.nbio.structure.contact.StructureInterfaceList; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031import javax.vecmath.Matrix4d; 032import javax.vecmath.Point3i; 033import javax.vecmath.Vector3d; 034import java.util.*; 035 036 037/** 038 * A class containing methods to find interfaces in a given crystallographic Structure by 039 * reconstructing the crystal lattice through application of symmetry operators 040 * 041 * @author Jose Duarte 042 * 043 */ 044 045public class CrystalBuilder { 046 047 public static final String NCS_CHAINID_SUFFIX_CHAR = "n"; 048 049 // Default number of cell neighbors to try in interface search (in 3 directions of space). 050 // In the search, only bounding box overlaps are tried, thus there's not so much overhead in adding 051 // more cells. We actually tested it and using numCells from 1 to 10 didn't change runtimes at all. 052 // Examples with interfaces in distant neighbor cells: 053 // 2nd neighbors: 3hz3, 1wqj, 2de3, 1jcd 054 // 3rd neighbors: 3bd3, 1men, 2gkp, 1wui 055 // 5th neighbors: 2ahf, 2h2z 056 // 6th neighbors: 1was (in fact interfaces appear only at 5th neighbors for it) 057 // Maybe this could be avoided by previously translating the given molecule to the first cell, 058 // BUT! some bona fide cases exist, e.g. 2d3e: it is properly placed at the origin but the molecule 059 // is enormously long in comparison with the dimensions of the unit cell, some interfaces come at the 7th neighbor. 060 // After a scan of the whole PDB (Oct 2013) using numCells=50, the highest one was 4jgc with 061 // interfaces up to the 11th neighbor. Other high ones (9th neighbors) are 4jbm and 4k3t. 062 // We set the default value to 20 to be on the safe side. Runtime does not seem to be affected at all - JD 2020-01-12 063 // Some good examples in this posting in CCP4: https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=CCP4BB;45b2755d.2001 064 // in any case the 5m3h example in the posting seems to have contacts only up to the 11th neighbor. 065 public static final int DEF_NUM_CELLS = 20; 066 067 /** 068 * Default maximum distance between two chains to be considered an interface. 069 * @see #getUniqueInterfaces(double) 070 */ 071 public static final double DEFAULT_INTERFACE_DISTANCE_CUTOFF = 5.5; 072 073 public static final Matrix4d IDENTITY = new Matrix4d(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1); 074 075 076 /** 077 * Whether to consider HETATOMs in contact calculations 078 */ 079 private static final boolean INCLUDE_HETATOMS = true; 080 081 private Structure structure; 082 private PDBCrystallographicInfo crystallographicInfo; 083 private int numPolyChainsAu; 084 private int numOperatorsSg; 085 private Map<String,Matrix4d> chainNcsOps = null; 086 private Map<String,String> chainOrigNames = null; 087 088 private static final Logger logger = LoggerFactory.getLogger(CrystalBuilder.class); 089 090 private int numCells; 091 092 private ArrayList<CrystalTransform> visitedCrystalTransforms; 093 private Map<String,Map<Matrix4d,StructureInterface>> visitedNcsChainPairs = null; 094 095 private boolean searchBeyondAU; 096 private Matrix4d[] ops; 097 098 /** 099 * Special constructor for NCS-aware CrystalBuilder. 100 * The output list of interfaces will be pre-clustered by NCS-equivalence. 101 * Run {@link CrystalBuilder#expandNcsOps(Structure, Map, Map)} first to extend the AU 102 * and get the equivalence information. 103 * @param structure 104 * NCS-extended structure 105 * @param chainOrigNames 106 * chain names mapped to the original chain names (pre-NCS extension) 107 * @param chainNcsOps 108 * chain names mapped to the ncs operators that was used to generate them 109 * @since 5.0.0 110 */ 111 public CrystalBuilder(Structure structure, Map<String,String> chainOrigNames, Map<String,Matrix4d> chainNcsOps) { 112 this(structure); 113 this.chainOrigNames = chainOrigNames; 114 this.chainNcsOps = chainNcsOps; 115 } 116 117 public CrystalBuilder(Structure structure) { 118 this.structure = structure; 119 this.crystallographicInfo = structure.getCrystallographicInfo(); 120 this.numPolyChainsAu = structure.getPolyChains().size(); 121 122 this.searchBeyondAU = false; 123 if (structure.isCrystallographic()) { 124 125 this.searchBeyondAU = true; 126 127 // we need to check space group not null for the cases where the entry is crystallographic but 128 // the space group is not a standard one recognized by biojava, e.g. 1mnk (SG: 'I 21') 129 if (this.crystallographicInfo.isNonStandardSg()) { 130 logger.warn("Space group is non-standard, will only calculate asymmetric unit interfaces."); 131 this.searchBeyondAU = false; 132 } 133 134 // just in case we still check for space group null (a user pdb file could potentially be crystallographic and have no space group) 135 if (this.crystallographicInfo.getSpaceGroup() == null) { 136 logger.warn("Space group is null, will only calculate asymmetric unit interfaces."); 137 this.searchBeyondAU = false; 138 } 139 140 // we need to check crystal cell not null for the rare cases where the entry is crystallographic but 141 // the crystal cell is not given, e.g. 2i68, 2xkm, 4bpq 142 if (this.crystallographicInfo.getCrystalCell() == null) { 143 logger.warn("Could not find a crystal cell definition, will only calculate asymmetric unit interfaces."); 144 this.searchBeyondAU = false; 145 } 146 147 // check for cases like 4hhb that are in a non-standard coordinate frame convention, see https://github.com/eppic-team/owl/issues/4 148 if (this.crystallographicInfo.isNonStandardCoordFrameConvention()) { 149 logger.warn("Non-standard coordinate frame convention, will only calculate asymmetric unit interfaces."); 150 this.searchBeyondAU = false; 151 } 152 } 153 154 if (this.searchBeyondAU) { 155 // explore the crystal 156 this.numOperatorsSg = this.crystallographicInfo.getSpaceGroup().getMultiplicity(); 157 this.ops = this.crystallographicInfo.getTransformationsOrthonormal(); 158 } else { 159 // look for contacts within structure as given 160 this.numOperatorsSg = 1; 161 this.ops = new Matrix4d[1]; 162 this.ops[0] = new Matrix4d(IDENTITY); 163 } 164 165 this.numCells = DEF_NUM_CELLS; 166 167 } 168 169 170 /** 171 * @return true if this CrystalBuilder is NCS-aware. 172 * @since 5.0.0 173 */ 174 public boolean hasNcsOps() { 175 return chainNcsOps != null; 176 } 177 178 /** 179 * Set the number of neighboring crystal cells that will be used in the search for contacts 180 * @param numCells 181 */ 182 public void setNumCells(int numCells) { 183 this.numCells = numCells; 184 } 185 186 private void initialiseVisited() { 187 visitedCrystalTransforms = new ArrayList<>(); 188 if(this.hasNcsOps()) { 189 visitedNcsChainPairs = new HashMap<>(); 190 } 191 } 192 193 /** 194 * Returns the list of unique interfaces that the given Structure has upon 195 * generation of all crystal symmetry mates. An interface is defined as any pair of chains 196 * that contact, i.e. for which there is at least a pair of atoms (one from each chain) within 197 * the default cutoff distance. 198 * @return 199 * @see #DEFAULT_INTERFACE_DISTANCE_CUTOFF 200 */ 201 public StructureInterfaceList getUniqueInterfaces() { 202 return getUniqueInterfaces(DEFAULT_INTERFACE_DISTANCE_CUTOFF); 203 } 204 205 /** 206 * Returns the list of unique interfaces that the given Structure has upon 207 * generation of all crystal symmetry mates. An interface is defined as any pair of chains 208 * that contact, i.e. for which there is at least a pair of atoms (one from each chain) within 209 * the given cutoff distance. 210 * @param cutoff the distance cutoff for 2 chains to be considered in contact 211 * @return 212 */ 213 public StructureInterfaceList getUniqueInterfaces(double cutoff) { 214 215 216 StructureInterfaceList set = new StructureInterfaceList(); 217 218 // certain structures in the PDB are not macromolecules (contain no polymeric chains at all), e.g. 1ao2 219 // with the current mmCIF parsing, those will be empty since purely non-polymeric chains are removed 220 // see commit e9562781f23da0ebf3547146a307d7edd5741090 221 if (numPolyChainsAu==0) { 222 logger.warn("No chains present in the structure! No interfaces will be calculated"); 223 return set; 224 } 225 226 // pass the chainOrigNames map in NCS case so that StructureInterfaceList can deal with original to NCS chain names conversion 227 if (chainOrigNames!=null) { 228 set.setChainOrigNamesMap(chainOrigNames); 229 } 230 231 // initialising the visited ArrayList for keeping track of symmetry redundancy 232 initialiseVisited(); 233 234 235 236 // the isCrystallographic() condition covers 3 cases: 237 // a) entries with expMethod X-RAY/other diffraction and defined crystalCell (most usual case) 238 // b) entries with expMethod null but defined crystalCell (e.g. PDB file with CRYST1 record but no expMethod annotation) 239 // c) entries with expMethod not X-RAY (e.g. NMR) and defined crystalCell (NMR entries do have a dummy CRYST1 record "1 1 1 90 90 90 P1") 240 // d) isCrystallographic will be false if the structure is crystallographic but the space group was not recognized 241 242 243 calcInterfacesCrystal(set, cutoff); 244 245 return set; 246 } 247 248 /** 249 * Calculate interfaces between original asymmetric unit and neighboring 250 * whole unit cells, including the original full unit cell i.e. i=0,j=0,k=0 251 * @param set 252 * @param cutoff 253 */ 254 private void calcInterfacesCrystal(StructureInterfaceList set, double cutoff) { 255 256 257 // initialising debugging vars 258 long start = -1; 259 long end = -1; 260 int trialCount = 0; 261 int skippedRedundant = 0; 262 int skippedAUsNoOverlap = 0; 263 int skippedChainsNoOverlap = 0; 264 int skippedSelfEquivalent = 0; 265 266 // The bounding boxes of all AUs of the unit cell 267 UnitCellBoundingBox bbGrid = new UnitCellBoundingBox(numOperatorsSg, numPolyChainsAu);; 268 // we calculate all the bounds of each of the asym units, those will then be reused and translated 269 bbGrid.setBbs(structure, ops, INCLUDE_HETATOMS); 270 271 272 // if not crystallographic there's no search to do in other cells, only chains within "AU" will be checked 273 if (!searchBeyondAU) numCells = 0; 274 275 boolean verbose = logger.isDebugEnabled(); 276 277 if (verbose) { 278 trialCount = 0; 279 start= System.currentTimeMillis(); 280 int neighbors = (2*numCells+1)*(2*numCells+1)*(2*numCells+1)-1; 281 int auTrials = (numPolyChainsAu*(numPolyChainsAu-1))/2; 282 int trials = numPolyChainsAu*numOperatorsSg*numPolyChainsAu*neighbors; 283 logger.debug("Chain clash trials within original AU: "+auTrials); 284 logger.debug( 285 "Chain clash trials between the original AU and the neighbouring "+neighbors+ 286 " whole unit cells ("+numCells+" neighbours)" + 287 "(2x"+numPolyChainsAu+"chains x "+numOperatorsSg+"AUs x "+neighbors+"cells) : "+trials); 288 logger.debug("Total trials: "+(auTrials+trials)); 289 } 290 291 List<Chain> polyChains = structure.getPolyChains(); 292 293 for (int a=-numCells;a<=numCells;a++) { 294 for (int b=-numCells;b<=numCells;b++) { 295 for (int c=-numCells;c<=numCells;c++) { 296 297 Point3i trans = new Point3i(a,b,c); 298 Vector3d transOrth = new Vector3d(a,b,c); 299 if (a!=0 || b!=0 || c!=0) { 300 // we avoid doing the transformation for 0,0,0 (in case it's not crystallographic) 301 this.crystallographicInfo.getCrystalCell().transfToOrthonormal(transOrth); 302 } 303 304 UnitCellBoundingBox bbGridTrans = bbGrid.getTranslatedBbs(transOrth); 305 306 for (int n=0;n<numOperatorsSg;n++) { 307 308 // short-cut strategies 309 // 1) we skip first of all if the bounding boxes of the AUs don't overlap 310 if (!bbGrid.getAuBoundingBox(0).overlaps(bbGridTrans.getAuBoundingBox(n), cutoff)) { 311 skippedAUsNoOverlap++; 312 continue; 313 } 314 315 // 2) we check if we didn't already see its equivalent symmetry operator partner 316 CrystalTransform tt = new CrystalTransform(this.crystallographicInfo.getSpaceGroup(), n); 317 tt.translate(trans); 318 if (isRedundantTransform(tt)) { 319 skippedRedundant++; 320 continue; 321 } 322 addVisitedTransform(tt); 323 324 325 boolean selfEquivalent = false; 326 327 // 3) an operator can be "self redundant" if it is the inverse of itself (involutory, e.g. all pure 2-folds with no translation) 328 if (tt.isEquivalent(tt)) { 329 logger.debug("Transform {} is equivalent to itself, will skip half of i-chains to j-chains comparisons", tt.toString()); 330 // in this case we can't skip the operator, but we can skip half of the matrix comparisons e.g. j>i 331 // we set a flag and do that within the loop below 332 selfEquivalent = true; 333 } 334 335 StringBuilder builder = null; 336 if (verbose) builder = new StringBuilder(String.valueOf(tt)).append(" "); 337 338 // Now that we know that boxes overlap and operator is not redundant, we have to go to the details 339 int contactsFound = 0; 340 341 for (int j=0;j<numPolyChainsAu;j++) { 342 343 for (int i=0;i<numPolyChainsAu;i++) { // we only have to compare the original asymmetric unit to every full cell around 344 345 if(selfEquivalent && (j>i)) { 346 // in case of self equivalency of the operator we can safely skip half of the matrix 347 skippedSelfEquivalent++; 348 continue; 349 } 350 // special case of original AU, we don't compare a chain to itself 351 if (n==0 && a==0 && b==0 && c==0 && i==j) continue; 352 353 // before calculating the AtomContactSet we check for overlap, then we save putting atoms into the grid 354 if (!bbGrid.getChainBoundingBox(0,i).overlaps(bbGridTrans.getChainBoundingBox(n,j),cutoff)) { 355 skippedChainsNoOverlap++; 356 if (verbose) { 357 builder.append("."); 358 } 359 continue; 360 } 361 362 trialCount++; 363 364 // finally we've gone through all short-cuts and the 2 chains seem to be close enough: 365 // we do the calculation of contacts 366 Chain chaini = polyChains.get(i); 367 Chain chainj = polyChains.get(j); 368 369 if (n!=0 || a!=0 || b!=0 || c!=0) { 370 Matrix4d mJCryst = new Matrix4d(ops[n]); 371 translate(mJCryst, transOrth); 372 chainj = (Chain)chainj.clone(); 373 Calc.transform(chainj,mJCryst); 374 } 375 376 StructureInterface interf = calcContacts(chaini, chainj, cutoff, tt, builder); 377 if (interf == null) { 378 continue; 379 } 380 381 contactsFound++; 382 if(this.hasNcsOps()) { 383 StructureInterface interfNcsRef = findNcsRef(interf); 384 set.addNcsEquivalent(interf,interfNcsRef); 385 } else { 386 set.add(interf); 387 } 388 } 389 } 390 391 if( verbose ) { 392 if (a==0 && b==0 && c==0 && n==0) 393 builder.append(" "+contactsFound+"("+(numPolyChainsAu*(numPolyChainsAu-1))/2+")"); 394 else if (selfEquivalent) 395 builder.append(" "+contactsFound+"("+(numPolyChainsAu*(numPolyChainsAu+1))/2+")"); 396 else 397 builder.append(" "+contactsFound+"("+numPolyChainsAu*numPolyChainsAu+")"); 398 399 logger.debug(builder.toString()); 400 } 401 } 402 } 403 } 404 } 405 406 end = System.currentTimeMillis(); 407 logger.debug("\n"+trialCount+" chain-chain clash trials done. Time "+(end-start)/1000+"s"); 408 logger.debug(" skipped (not overlapping AUs) : "+skippedAUsNoOverlap); 409 logger.debug(" skipped (not overlapping chains) : "+skippedChainsNoOverlap); 410 logger.debug(" skipped (sym redundant op pairs) : "+skippedRedundant); 411 logger.debug(" skipped (sym redundant self op) : "+skippedSelfEquivalent); 412 logger.debug("Found "+set.size()+" interfaces."); 413 } 414 415 416 /** 417 * Checks whether given interface is NCS-redundant, i.e., an identical interface between NCS copies of 418 * these molecules has already been seen, and returns this (reference) interface. 419 * 420 * @param interf 421 * StructureInterface 422 * @return already seen interface that is NCS-equivalent to interf, 423 * null if such interface is not found. 424 */ 425 private StructureInterface findNcsRef(StructureInterface interf) { 426 if (!this.hasNcsOps()) { 427 return null; 428 } 429 String chainIName = interf.getMoleculeIds().getFirst(); 430 String iOrigName = chainOrigNames.get(chainIName); 431 432 String chainJName = interf.getMoleculeIds().getSecond(); 433 String jOrigName = chainOrigNames.get(chainJName); 434 435 Matrix4d mJCryst; 436 if(this.searchBeyondAU) { 437 mJCryst = interf.getTransforms().getSecond().getMatTransform(); 438 mJCryst = crystallographicInfo.getCrystalCell().transfToOrthonormal(mJCryst); 439 } else { 440 mJCryst = IDENTITY; 441 } 442 443 // Let X1,...Xn be the original coords, before NCS transforms (M1...Mk) 444 // current chain i: M_i * X_i 445 // current chain j: Cn * M_j * X_j 446 447 // transformation to bring chain j near X_i: M_i^(-1) * Cn * M_j 448 // transformation to bring chain i near X_j: (Cn * M_j)^(-1) * M_i = (M_i^(-1) * Cn * M_j)^(-1) 449 450 Matrix4d mChainIInv = new Matrix4d(chainNcsOps.get(chainIName)); 451 mChainIInv.invert(); 452 453 Matrix4d mJNcs = new Matrix4d(chainNcsOps.get(chainJName)); 454 455 Matrix4d j2iNcsOrigin = new Matrix4d(mChainIInv); 456 j2iNcsOrigin.mul(mJCryst); 457 //overall transformation to bring current chainj from its NCS origin to i's 458 j2iNcsOrigin.mul(mJNcs); 459 460 //overall transformation to bring current chaini from its NCS origin to j's 461 Matrix4d i2jNcsOrigin = new Matrix4d(j2iNcsOrigin); 462 i2jNcsOrigin.invert(); 463 464 String matchChainIdsIJ = iOrigName + jOrigName; 465 String matchChainIdsJI = jOrigName + iOrigName; 466 467 // same original chain names 468 Optional<Matrix4d> matchDirect = 469 visitedNcsChainPairs.computeIfAbsent(matchChainIdsIJ, k-> new HashMap<>()).entrySet().stream(). 470 map(r->r.getKey()). 471 filter(r->r.epsilonEquals(j2iNcsOrigin,0.01)). 472 findFirst(); 473 474 Matrix4d matchMatrix = matchDirect.orElse(null); 475 String matchChainIds = matchChainIdsIJ; 476 477 if(matchMatrix == null) { 478 // reversed original chain names with inverted transform 479 Optional<Matrix4d> matchInverse = 480 visitedNcsChainPairs.computeIfAbsent(matchChainIdsJI, k-> new HashMap<>()).entrySet().stream(). 481 map(r->r.getKey()). 482 filter(r->r.epsilonEquals(i2jNcsOrigin,0.01)). 483 findFirst(); 484 matchMatrix = matchInverse.orElse(null); 485 matchChainIds = matchChainIdsJI; 486 } 487 488 StructureInterface matchInterface = null; 489 490 if (matchMatrix == null) { 491 visitedNcsChainPairs.get(matchChainIdsIJ).put(j2iNcsOrigin,interf); 492 } else { 493 matchInterface = visitedNcsChainPairs.get(matchChainIds).get(matchMatrix); 494 } 495 496 return matchInterface; 497 } 498 499 private StructureInterface calcContacts(Chain chaini, Chain chainj, double cutoff, CrystalTransform tt, StringBuilder builder) { 500 // note that we don't consider hydrogens when calculating contacts 501 AtomContactSet graph = StructureTools.getAtomsInContact(chaini, chainj, cutoff, INCLUDE_HETATOMS); 502 503 if (graph.size()>0) { 504 if (builder != null) builder.append("x"); 505 506 CrystalTransform transf = new CrystalTransform(this.crystallographicInfo.getSpaceGroup()); 507 StructureInterface interf = new StructureInterface( 508 StructureTools.getAllAtomArray(chaini), StructureTools.getAllAtomArray(chainj), 509 chaini.getName(), chainj.getName(), 510 graph, 511 transf, tt); 512 513 return interf; 514 515 } else { 516 if (builder != null) builder.append("o"); 517 return null; 518 } 519 } 520 521 private void addVisitedTransform(CrystalTransform tt) { 522 visitedCrystalTransforms.add(tt); 523 } 524 525 /** 526 * Checks whether given transformId/translation is symmetry redundant 527 * Two transformations are symmetry redundant if their matrices (4d) multiplication gives the identity, i.e. 528 * if one is the inverse of the other. 529 * @param tt 530 * @return 531 */ 532 private boolean isRedundantTransform(CrystalTransform tt) { 533 534 Iterator<CrystalTransform> it = visitedCrystalTransforms.iterator(); 535 while (it.hasNext()) { 536 CrystalTransform v = it.next(); 537 538 if (tt.isEquivalent(v)) { 539 540 logger.debug("Skipping redundant transformation: "+tt+", equivalent to "+v); 541 542 // there's only 1 possible equivalent partner for each visited matrix 543 // (since the equivalent is its inverse matrix and the inverse matrix is unique) 544 // thus once the partner has been seen, we don't need to check it ever again 545 it.remove(); 546 547 return true; 548 } 549 } 550 551 return false; 552 } 553 554 public void translate(Matrix4d m, Vector3d translation) { 555 m.m03 = m.m03+translation.x; 556 m.m13 = m.m13+translation.y; 557 m.m23 = m.m23+translation.z; 558 } 559 560 /** 561 * Apply the NCS operators in the given Structure adding new chains as needed. 562 * All chains are (re)assigned ids of the form: original_chain_id+ncs_operator_index+{@value #NCS_CHAINID_SUFFIX_CHAR}. 563 * @param structure 564 * the structure to expand 565 * @param chainOrigNames 566 * new chain names mapped to the original chain names 567 * @param chainNcsOps 568 * new chain names mapped to the ncs operators that was used to generate them 569 * @since 5.0.0 570 */ 571 public static void expandNcsOps(Structure structure, Map<String,String> chainOrigNames, Map<String,Matrix4d> chainNcsOps) { 572 PDBCrystallographicInfo xtalInfo = structure.getCrystallographicInfo(); 573 if (xtalInfo ==null) return; 574 575 if (xtalInfo.getNcsOperators()==null || xtalInfo.getNcsOperators().length==0) 576 return; 577 578 List<Chain> chainsToAdd = new ArrayList<>(); 579 580 Matrix4d identity = new Matrix4d(); 581 identity.setIdentity(); 582 583 Matrix4d[] ncsOps = xtalInfo.getNcsOperators(); 584 585 for (Chain c:structure.getChains()) { 586 String cOrigId = c.getId(); 587 String cOrigName = c.getName(); 588 589 for (int iOperator = 0; iOperator < ncsOps.length; iOperator++) { 590 Matrix4d m = ncsOps[iOperator]; 591 592 Chain clonedChain = (Chain)c.clone(); 593 String newChainId = cOrigId+(iOperator+1)+NCS_CHAINID_SUFFIX_CHAR; 594 String newChainName = cOrigName+(iOperator+1)+NCS_CHAINID_SUFFIX_CHAR; 595 clonedChain.setId(newChainId); 596 clonedChain.setName(newChainName); 597 598 setChainIdsInResidueNumbers(clonedChain, newChainName); 599 Calc.transform(clonedChain, m); 600 601 chainsToAdd.add(clonedChain); 602 c.getEntityInfo().addChain(clonedChain); 603 604 chainOrigNames.put(newChainName,cOrigName); 605 chainNcsOps.put(newChainName,m); 606 } 607 608 chainNcsOps.put(cOrigName,identity); 609 chainOrigNames.put(cOrigName,cOrigName); 610 } 611 612 chainsToAdd.forEach(structure::addChain); 613 } 614 615 /** 616 * Auxiliary method to reset chain ids of residue numbers in a chain. 617 * Used when cloning chains and resetting their ids: one needs to take care of 618 * resetting the ids within residue numbers too. 619 * @param c 620 * @param newChainName 621 */ 622 private static void setChainIdsInResidueNumbers(Chain c, String newChainName) { 623 for (Group g:c.getAtomGroups()) { 624 g.setResidueNumber(newChainName, g.getResidueNumber().getSeqNum(), g.getResidueNumber().getInsCode()); 625 } 626 for (Group g:c.getSeqResGroups()) { 627 if (g.getResidueNumber()==null) continue; 628 g.setResidueNumber(newChainName, g.getResidueNumber().getSeqNum(), g.getResidueNumber().getInsCode()); 629 } 630 } 631 632}