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