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