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.parsers;
39  
40  import ffx.potential.parameters.AngleTorsionType;
41  import ffx.potential.parameters.AngleType;
42  import ffx.potential.parameters.AtomType;
43  import ffx.potential.parameters.BioType;
44  import ffx.potential.parameters.BondType;
45  import ffx.potential.parameters.ForceField;
46  import ffx.potential.parameters.MultipoleType;
47  import ffx.potential.parameters.OutOfPlaneBendType;
48  import ffx.potential.parameters.PiOrbitalTorsionType;
49  import ffx.potential.parameters.StretchBendType;
50  import ffx.potential.parameters.StretchTorsionType;
51  import ffx.potential.parameters.TorsionTorsionType;
52  import ffx.potential.parameters.TorsionType;
53  import ffx.potential.parameters.UreyBradleyType;
54  import ffx.potential.parameters.VDWType;
55  import org.w3c.dom.Document;
56  import org.w3c.dom.Element;
57  
58  import javax.xml.parsers.DocumentBuilder;
59  import javax.xml.parsers.DocumentBuilderFactory;
60  import javax.xml.transform.OutputKeys;
61  import javax.xml.transform.Transformer;
62  import javax.xml.transform.TransformerException;
63  import javax.xml.transform.TransformerFactory;
64  import javax.xml.transform.dom.DOMSource;
65  import javax.xml.transform.stream.StreamResult;
66  import java.text.SimpleDateFormat;
67  import java.util.ArrayList;
68  import java.util.Date;
69  import java.util.HashMap;
70  import java.util.List;
71  import java.util.Map;
72  import java.util.logging.Logger;
73  
74  /**
75   * The OpenMMXmlFilter class creates an OpenMM-style XML file from a ForceField object.
76   *
77   * Work is underway to parse OpenMM XML files into ForceField objects (AMBER and CHARMM).
78   *
79   * @author Jacob M. Miller
80   */
81  public class OpenMMXmlFilter {
82  
83    /**
84     * A Logger for the XmlFilter class.
85     */
86    private static final Logger logger = Logger.getLogger(OpenMMXmlFilter.class.getName());
87  
88    /**
89     * The ForceField object that will be written to an XML.
90     */
91    private final ForceField forceField;
92  
93    /**
94     * The path to write the XML file to. Should not include '.xml' on end.
95     */
96    private String outputName;
97  
98    /**
99     * Constructor for outputting XML.
100    *
101    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
102    */
103   public OpenMMXmlFilter(ForceField forceField) {
104     this.forceField = forceField;
105   }
106 
107   /**
108    * Constructor for outputting XML with output path specified.
109    *
110    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
111    * @param saveName   a String with the output path.
112    */
113   public OpenMMXmlFilter(ForceField forceField, String saveName) {
114     this.forceField = forceField;
115     this.outputName = saveName;
116   }
117 
118   /**
119    * Create an OpenMM XML file for the given force field.
120    *
121    * @throws Exception
122    */
123   public void toXML() throws Exception {
124     // CREATE XML ROOT NODES AND FILL IN BASIC INFO
125     // Instantiate Document building objects
126     DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
127     DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
128     Document doc = dBuilder.newDocument();
129 
130     // Create Root Node "ForceField"
131     Element rootElement = doc.createElement("ForceField");
132     doc.appendChild(rootElement);
133 
134     // Create Info Section
135     Element infoNode = doc.createElement("Info");
136     rootElement.appendChild(infoNode);
137 
138     // Set Source node to the force field name
139     Element srcNode = doc.createElement("Source");
140     srcNode.setTextContent(forceField.getString("forcefield", "UNKNOWN"));
141     infoNode.appendChild(srcNode);
142 
143     // Set the date
144     Element dateNode = doc.createElement("DateGenerated");
145     SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd");
146     String date = ft.format(new Date());
147     dateNode.setTextContent(date);
148     infoNode.appendChild(dateNode);
149 
150     // Add Atom Types
151     Element atomTypes = AtomType.getXMLAtomTypes(doc, forceField);
152     if (atomTypes != null) {
153       rootElement.appendChild(atomTypes);
154     } else {
155       throw new Exception(" The force field defines no atom types.");
156     }
157 
158     // Add Residues
159     Map<String, BioType> bioTypes = forceField.getBioTypeMap();
160     if (!bioTypes.values().isEmpty()) {
161       Map<String, ArrayList<BioType>> moleculeDict = new HashMap<>();
162       for (BioType bioType : bioTypes.values()) {
163         if (!moleculeDict.containsKey(bioType.moleculeName)) {
164           moleculeDict.put(bioType.moleculeName, new ArrayList<>());
165         }
166         ArrayList<BioType> moleculeBioTypes = moleculeDict.get(bioType.moleculeName);
167         moleculeBioTypes.add(bioType);
168       }
169       Element residuesNode = doc.createElement("Residues");
170       buildExtraResidues(moleculeDict, doc, residuesNode);
171       rootElement.appendChild(residuesNode);
172     } else {
173       logger.info("WARNING: No BioTypes defined for force field, therefore no OpenMM residues are created.");
174     }
175 
176     // Add Bond Types
177     Element bondForce = BondType.getXMLForce(doc, forceField);
178     if (bondForce != null) {
179       rootElement.appendChild(bondForce);
180     }
181 
182     // Add Angle Types
183     Element angleForce = AngleType.getXMLForce(doc, forceField);
184     if (angleForce != null) {
185       rootElement.appendChild(angleForce);
186     }
187 
188     // Add OutOfPlaneBend Types
189     Element outOfPlaneBendForce = OutOfPlaneBendType.getXMLForce(doc, forceField);
190     if (outOfPlaneBendForce != null) {
191       rootElement.appendChild(outOfPlaneBendForce);
192     }
193 
194     // Add Torsion Types
195     Element torsionForce = TorsionType.getXMLForce(doc, forceField);
196     if (torsionForce != null) {
197       rootElement.appendChild(torsionForce);
198     }
199 
200     // Add PiOrbitalTorsion Types
201     Element piOrbitalTorsionForce = PiOrbitalTorsionType.getXMLElement(doc, forceField);
202     if (piOrbitalTorsionForce != null) {
203       rootElement.appendChild(piOrbitalTorsionForce);
204     }
205 
206     // Add StretchTorsion Types
207     Element stretchTorsionForce = StretchTorsionType.getXMLForce(doc, forceField);
208     if (stretchTorsionForce != null) {
209       rootElement.appendChild(stretchTorsionForce);
210     }
211 
212     // Add AngleTorsion Types
213     Element angleTorsionForce = AngleTorsionType.getXMLForce(doc, forceField);
214     if (angleTorsionForce != null) {
215       rootElement.appendChild(angleTorsionForce);
216     }
217 
218     // Add StretchBend Types
219     Element stretchBendForce = StretchBendType.getXMLForce(doc, forceField);
220     if (stretchBendForce != null) {
221       rootElement.appendChild(stretchBendForce);
222     }
223 
224     // Add TorsionTorsion Type
225     Element torsionTorsionForce = TorsionTorsionType.getXMLForce(doc, forceField);
226     if (torsionTorsionForce != null) {
227       rootElement.appendChild(torsionTorsionForce);
228     }
229 
230     // Add VDW and VDWPair Types
231     Element vdwForce = VDWType.getXMLForce(doc, forceField);
232     if (vdwForce != null) {
233       rootElement.appendChild(vdwForce);
234     }
235 
236     // Add Multipole and Polarize Types
237     Element multipoleForce = MultipoleType.getXMLForce(doc, forceField);
238     if (multipoleForce != null) {
239       rootElement.appendChild(multipoleForce);
240     }
241 
242     // Add UreyBradley Types
243     Element ureyBradleyForce = UreyBradleyType.getXMLForce(doc, forceField);
244     if (ureyBradleyForce != null) {
245       rootElement.appendChild(ureyBradleyForce);
246     }
247 
248     // Write XML to 'force field name'.xml
249     writeXML(doc);
250   }
251 
252   /**
253    * Build residues not found in OpenMM finalResidues.xml
254    *
255    * @param moleculeDict Map containing molecules and their BioTypes.
256    * @param doc          Document with XML Nodes.
257    * @param residuesNode Residues Node that will contain individual Residue nodes with Atoms and Bonds.
258    */
259   private static void buildExtraResidues(Map<String, ArrayList<BioType>> moleculeDict, Document doc, Element residuesNode) {
260     // Loop over molecules
261     for (String mol : moleculeDict.keySet()) {
262       // Create Residue node for each molecule
263       Element resNode = doc.createElement("Residue");
264       resNode.setAttribute("name", mol);
265       residuesNode.appendChild(resNode);
266       List<String> order = new ArrayList<>();
267       List<String> froms = new ArrayList<>();
268       List<String> tos = new ArrayList<>();
269 
270       // Go through all atoms inside the molecule (keys)
271       int atomCount = 0;
272       int bondCount = 0;
273       for (BioType bioType : moleculeDict.get(mol)) {
274         String atom = bioType.atomName;
275         Element atomNode = doc.createElement("Atom");
276         atomNode.setAttribute("name", atom);
277         atomNode.setAttribute("type", String.valueOf(bioType.atomType));
278         resNode.appendChild(atomNode);
279         atomCount++;
280         String[] bonds = bioType.bonds;
281         // Go through all atoms (bonds) that the defined atom is bonded to
282         for (String bond : bonds) {
283           BioType bondBioType = null;
284           for (BioType bioType2 : moleculeDict.get(mol)) {
285             if (bioType2.atomName.equals(bond)) {
286               bondBioType = bioType2;
287               break;
288             }
289           }
290 
291           // Record each bond and avoid duplicates by only adding the bond if the atomType is less than the bondAtomType.
292           if (bioType.index < bondBioType.index) {
293             froms.add(atom);
294             tos.add(bond);
295             bondCount++;
296           }
297         }
298         // Add each atom in the order they have been added to the Residue node.
299         order.add(atom);
300       }
301 
302       // add Bond nodes using the order the Atom nodes were placed
303       for (int i = 0; i < froms.size(); i++) {
304         Element bondNode = doc.createElement("Bond");
305         bondNode.setAttribute("from", String.valueOf(order.indexOf(froms.get(i))));
306         bondNode.setAttribute("to", String.valueOf(order.indexOf(tos.get(i))));
307         resNode.appendChild(bondNode);
308       }
309       logger.info(" Added " + atomCount + " atoms and "
310           + bondCount + " bonds to residue " + mol + ".");
311     }
312   }
313 
314   /**
315    * Create an OpenMM-style XML file from the Document object that was created in toXML().
316    *
317    * @param doc Document object containing XML nodes.
318    * @throws TransformerException
319    */
320   private void writeXML(Document doc) throws TransformerException {
321     String saveName;
322     if (outputName != null) {
323       saveName = outputName;
324     } else {
325       saveName = forceField.getString("forcefield", "UNKNOWN");
326     }
327 
328     TransformerFactory tfFactory = TransformerFactory.newInstance();
329     Transformer transformer = tfFactory.newTransformer();
330 
331     transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); // if want to hide first line XML version/encoding
332     transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // print with indentation
333 
334     DOMSource src = new DOMSource(doc);
335     StreamResult result = new StreamResult(saveName + ".xml");
336     transformer.transform(src, result);
337   }
338 }