View Javadoc
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.potential.commands;
39  
40  import ffx.potential.bonded.Atom;
41  import ffx.potential.bonded.Residue;
42  import ffx.potential.bonded.Residue.ResidueType;
43  import ffx.potential.cli.PotentialCommand;
44  import ffx.potential.utils.PotentialsUtils;
45  import ffx.utilities.FFXBinding;
46  import ffx.utilities.FilePathInfo;
47  import picocli.CommandLine.Command;
48  import picocli.CommandLine.Parameters;
49  
50  import java.util.ArrayList;
51  import java.util.List;
52  
53  import static ffx.numerics.math.DoubleMath.dist;
54  import static java.lang.String.format;
55  import static org.apache.commons.math3.util.FastMath.abs;
56  
57  /**
58   * Fix chain breaks in a PDB file.
59   * <p>
60   * Usage:
61   * ffxc ChainBreaks &lt;filename&gt;
62   */
63  @Command(name = "ChainBreaks", description = " Fix chain breaks in a pdb file.")
64  public class ChainBreaks extends PotentialCommand {
65  
66    /**
67     * The final argument(s) should be one filename.
68     */
69    @Parameters(arity = "1", paramLabel = "file",
70        description = "The atomic coordinate file in PDB or XYZ format.")
71    private String filename = null;
72  
73    private List<double[]> newCoordinates;
74    private final PotentialsUtils potentialsUtils = new PotentialsUtils();
75  
76    public ChainBreaks() {
77      super();
78    }
79  
80    public ChainBreaks(FFXBinding binding) {
81      super(binding);
82    }
83  
84    public ChainBreaks(String[] args) {
85      super(args);
86    }
87  
88    @Override
89    public ChainBreaks run() {
90      if (!init()) {
91        return null;
92      }
93  
94      // Load the MolecularAssembly.
95      activeAssembly = getActiveAssembly(filename);
96      if (activeAssembly == null) {
97        logger.info(helpString());
98        return null;
99      }
100 
101     // Set the filename.
102     filename = activeAssembly.getFile().getAbsolutePath();
103 
104     // Get the base name of the file and its extension.
105     FilePathInfo fileInfo = parseFilePath(filename);
106 
107     if (!fileInfo.extension().toLowerCase().contains("pdb")) {
108       logger.info(format(" The file extension does not include 'pdb': %s", filename));
109       return null;
110     }
111 
112     logger.info(format("\n Fixing Chain Breaks for %s", filename));
113     List<Residue> residues = activeAssembly.getResidueList();
114     List<String> chainBreaks = findChainBreaks(residues, 3.0);
115     saveByOriginalExtension(activeAssembly, filename);
116     return this;
117   }
118 
119   private List<String> findChainBreaks(List<Residue> residues, double cutoff) {
120     List<List<Residue>> subChains = new ArrayList<>();
121     List<String> chainBreaks = new ArrayList<>();
122 
123     // Chain-start atom: N (amino) / O5* (nucleic)
124     // Chain-end atom:   C (amino) / O3* (nucleic)
125     ResidueType rType = residues.get(0).getResidueType();
126     String startAtName;
127     String endAtName;
128     switch (rType) {
129       case AA:
130         startAtName = "N";
131         endAtName = "C";
132         break;
133       case NA:
134         boolean namedStar = residues.stream()
135             .flatMap((Residue r) -> r.getAtomList().stream())
136             .anyMatch((Atom a) -> a.getName().equals("O5*"));
137         if (namedStar) {
138           startAtName = "O5*";
139           endAtName = "O3*";
140         } else {
141           startAtName = "O5'";
142           endAtName = "O3'";
143         }
144         break;
145       case UNK:
146       default:
147         logger.fine(" Not attempting to find chain breaks for chain with residue " + residues.get(0));
148         return null;
149     }
150 
151     List<Residue> subChain = null;
152     Residue previousResidue = null;
153     Atom priorEndAtom = null;
154 
155     for (Residue residue : residues) {
156       List<Atom> resAtoms = residue.getAtomList();
157       if (priorEndAtom == null) {
158         // Initialization.
159         subChain = new ArrayList<>();
160         subChain.add(residue);
161         subChains.add(subChain);
162       } else {
163         // Find the start atom of the current residue.
164         Atom startAtom = null;
165         for (Atom a : resAtoms) {
166           if (a.getName().equalsIgnoreCase(startAtName)) {
167             startAtom = a;
168             break;
169           }
170         }
171         if (startAtom == null) {
172           subChain.add(residue);
173           continue;
174         }
175         // Compute the distance between the previous carbonyl carbon and the current nitrogen.
176         double r = dist(priorEndAtom.getXYZ(null), startAtom.getXYZ(null));
177 
178         if (r > cutoff) {
179           // Start a new chain.
180           subChain = new ArrayList<>();
181           subChain.add(residue);
182           subChains.add(subChain);
183           chainBreaks.add("C " + previousResidue + " N " + residue);
184           fixChainBreaks(priorEndAtom.getXYZ(null), startAtom.getXYZ(null));
185           priorEndAtom.setXYZ(newCoordinates.get(0));
186           startAtom.setXYZ(newCoordinates.get(1));
187         } else {
188           // Continue the current chain.
189           subChain.add(residue);
190         }
191       }
192 
193       // Save the carbonyl carbon.
194       for (Atom a : resAtoms) {
195         if (a.getName().equalsIgnoreCase(endAtName)) {
196           priorEndAtom = a;
197           break;
198         }
199       }
200       previousResidue = residue;
201     }
202 
203     return chainBreaks;
204   }
205 
206   private void fixChainBreaks(double[] cCoordinates, double[] nCoordinates) {
207     logger.info(" Generating new coordinates.");
208     newCoordinates = new ArrayList<>();
209     double distance = dist(cCoordinates, nCoordinates);
210     while (distance > 3.0) {
211       double dx = abs((cCoordinates[0] - nCoordinates[0]) / 4.0);
212       double dy = abs((cCoordinates[1] - nCoordinates[1]) / 4.0);
213       double dz = abs((cCoordinates[2] - nCoordinates[2]) / 4.0);
214 
215       if (cCoordinates[0] > nCoordinates[0]) {
216         cCoordinates[0] = cCoordinates[0] - dx;
217         nCoordinates[0] = nCoordinates[0] + dx;
218       } else if (cCoordinates[0] < nCoordinates[0]) {
219         cCoordinates[0] = cCoordinates[0] + dx;
220         nCoordinates[0] = nCoordinates[0] - dx;
221       }
222 
223       if (cCoordinates[1] > nCoordinates[1]) {
224         cCoordinates[1] = cCoordinates[1] - dy;
225         nCoordinates[1] = nCoordinates[1] + dy;
226       } else if (cCoordinates[1] < nCoordinates[1]) {
227         cCoordinates[1] = cCoordinates[1] + dy;
228         nCoordinates[1] = nCoordinates[1] - dy;
229       }
230 
231       if (cCoordinates[2] > nCoordinates[2]) {
232         cCoordinates[2] = cCoordinates[2] - dz;
233         nCoordinates[2] = nCoordinates[2] + dz;
234       } else if (cCoordinates[2] < nCoordinates[0]) {
235         cCoordinates[2] = cCoordinates[2] + dz;
236         nCoordinates[2] = nCoordinates[2] - dz;
237       }
238 
239       distance = dist(cCoordinates, nCoordinates);
240     }
241     newCoordinates.add(cCoordinates);
242     newCoordinates.add(nCoordinates);
243   }
244 }