1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.algorithms.commands;
39
40 import ffx.algorithms.cli.AlgorithmsCommand;
41 import ffx.algorithms.cli.MinimizeOptions;
42 import ffx.algorithms.optimize.CrystalMinimize;
43 import ffx.algorithms.optimize.Minimize;
44 import ffx.numerics.Potential;
45 import ffx.potential.ForceFieldEnergy;
46 import ffx.potential.MolecularAssembly.FractionalMode;
47 import ffx.potential.XtalEnergy;
48 import ffx.potential.cli.AtomSelectionOptions;
49 import ffx.potential.parsers.PDBFilter;
50 import ffx.potential.parsers.SystemFilter;
51 import ffx.potential.parsers.XYZFilter;
52 import ffx.utilities.FFXBinding;
53 import ffx.utilities.FileUtils;
54 import org.apache.commons.io.FilenameUtils;
55 import picocli.CommandLine.Command;
56 import picocli.CommandLine.Mixin;
57 import picocli.CommandLine.Option;
58 import picocli.CommandLine.Parameters;
59
60 import java.io.File;
61 import java.util.Collections;
62 import java.util.List;
63
64 import static java.lang.String.format;
65 import static org.apache.commons.math3.util.FastMath.abs;
66
67
68
69
70
71
72
73
74
75 @Command(description = " Minimize crystal unit cell parameters.", name = "MinimizeCrystals")
76 public class MinimizeCrystals extends AlgorithmsCommand {
77
78 @Mixin
79 private MinimizeOptions minimizeOptions;
80
81 @Mixin
82 private AtomSelectionOptions atomSelectionOptions;
83
84
85
86
87 @Option(names = {"-c", "--coords"}, paramLabel = "false", defaultValue = "false",
88 description = "Cycle between lattice and coordinate optimization instead of optimizing both together.")
89 private boolean coords;
90
91
92
93
94 @Option(names = {"-f", "--fractional"}, paramLabel = "OFF", defaultValue = "OFF",
95 description = "Maintain fractional coordinates during lattice optimization [OFF/MOLECULE/ATOM].")
96 private String fractional;
97
98
99
100
101 @Option(names = {"-t", "--tensor"}, paramLabel = "false", defaultValue = "false",
102 description = "Compute partial derivatives of the energy with respect to unit cell parameters.")
103 private boolean tensor;
104
105
106
107
108 @Option(names = {"--et", "--energyTolerance"}, paramLabel = "1.0e-10", defaultValue = "1.0e-10",
109 description = "End minimization if new energy deviates less than this tolerance.")
110 private double tolerance;
111
112
113
114
115 @Option(names = {"--mi", "--minimumIterations"}, paramLabel = "-1", defaultValue = "-1",
116 description = "End minimization if it starts to cycle between small coordinate and lattice parameter fluctuations.")
117 private int minIterations;
118
119
120
121
122 @Option(names = {"--cy", "--cycles"}, paramLabel = "-1", defaultValue = "-1",
123 description = "End minimization if it has cycled between lattice parameters and coordinates more than this value.")
124 private int cycles;
125
126
127
128
129 @Parameters(arity = "1", paramLabel = "file", description = "Atomic coordinate files in PDB or XYZ format.")
130 private String filename = null;
131
132 private XtalEnergy xtalEnergy;
133 private CrystalMinimize crystalMinimize;
134
135
136
137
138 public MinimizeCrystals() {
139 super();
140 }
141
142
143
144
145
146
147 public MinimizeCrystals(FFXBinding binding) {
148 super(binding);
149 }
150
151
152
153
154
155
156 public MinimizeCrystals(String[] args) {
157 super(args);
158 }
159
160
161
162
163 @Override
164 public MinimizeCrystals run() {
165
166
167 if (!init()) {
168 return this;
169 }
170
171
172 activeAssembly = getActiveAssembly(filename);
173 if (activeAssembly == null) {
174 logger.info(helpString());
175 return this;
176 }
177
178
179 filename = activeAssembly.getFile().getAbsolutePath();
180
181 logger.info("\n Running CrystalMinimize on " + filename);
182 logger.info("\n RMS gradient convergence criteria: " + minimizeOptions.getEps());
183
184 atomSelectionOptions.setActiveAtoms(activeAssembly);
185
186 ForceFieldEnergy forceFieldEnergy = activeAssembly.getPotentialEnergy();
187 xtalEnergy = new XtalEnergy(forceFieldEnergy, activeAssembly, coords);
188 xtalEnergy.setFractionalCoordinateMode(FractionalMode.MOLECULE);
189 SystemFilter systemFilter = algorithmFunctions.getFilter();
190
191
192 if (fractional != null && !fractional.isEmpty()) {
193 try {
194 FractionalMode mode = FractionalMode.valueOf(fractional.toUpperCase());
195 xtalEnergy.setFractionalCoordinateMode(mode);
196 } catch (Exception ignored) {
197 logger.info(" Unrecognized fractional coordinate mode: " + fractional);
198 logger.info(" Fractional coordinate mode is set to MOLECULE.");
199 }
200 }
201
202 runMinimize();
203
204 if (tensor) {
205 crystalMinimize.printTensor();
206 }
207
208
209 if (baseDir == null || !baseDir.exists() || !baseDir.isDirectory() || !baseDir.canWrite()) {
210 baseDir = new File(FilenameUtils.getFullPath(filename));
211 }
212
213 String dirName = baseDir.toString() + File.separator;
214 String name = FilenameUtils.getName(filename);
215 String ext = FilenameUtils.getExtension(name);
216 name = FilenameUtils.removeExtension(name);
217 File saveFile;
218 PDBFilter pdbFilter = null;
219 XYZFilter xyzFilter = null;
220
221 if (ext.toUpperCase().contains("XYZ")) {
222 saveFile = new File(dirName + name + ".xyz");
223 xyzFilter = new XYZFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
224 activeAssembly.getProperties());
225 algorithmFunctions.saveAsXYZ(activeAssembly, saveFile);
226 } else if (ext.toUpperCase().contains("ARC")) {
227 saveFile = new File(dirName + name + ".arc");
228 saveFile = algorithmFunctions.versionFile(saveFile);
229 xyzFilter = new XYZFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
230 activeAssembly.getProperties());
231 algorithmFunctions.saveAsXYZ(activeAssembly, saveFile);
232 } else {
233 saveFile = new File(dirName + name + ".pdb");
234 saveFile = algorithmFunctions.versionFile(saveFile);
235 pdbFilter = new PDBFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
236 activeAssembly.getProperties());
237 int numModels = systemFilter.countNumModels();
238 if (numModels > 1) {
239 pdbFilter.setModelNumbering(0);
240 }
241 pdbFilter.writeFile(saveFile, true, false, false);
242 }
243
244 if (systemFilter instanceof XYZFilter || systemFilter instanceof PDBFilter) {
245 while (systemFilter.readNext()) {
246 if (systemFilter instanceof PDBFilter) {
247 FileUtils.append(saveFile, "ENDMDL\n");
248 runMinimize();
249 pdbFilter.writeFile(saveFile, true, false, false);
250 } else if (systemFilter instanceof XYZFilter) {
251 runMinimize();
252 xyzFilter.writeFile(saveFile, true);
253 }
254 }
255
256 if (systemFilter instanceof PDBFilter) {
257 FileUtils.append(saveFile, "END\n");
258 }
259 }
260
261 return this;
262 }
263
264 private void runMinimize() {
265 logger.info("\n Crystal Minimizing " + activeAssembly.getName());
266 crystalMinimize = new CrystalMinimize(activeAssembly, xtalEnergy, algorithmListener);
267 crystalMinimize.minimize(minimizeOptions.getNBFGS(), minimizeOptions.getEps(), minimizeOptions.getIterations());
268 logger.info("\n Crystal Minimization Complete.");
269 double energy = crystalMinimize.getEnergy();
270
271
272 if (coords) {
273 ForceFieldEnergy forceFieldEnergy = activeAssembly.getPotentialEnergy();
274 Minimize minimize = new Minimize(activeAssembly, forceFieldEnergy, algorithmListener);
275 int numCycles = 0;
276 while (true) {
277
278 minimize.minimize(minimizeOptions.getNBFGS(), minimizeOptions.getEps(), minimizeOptions.getIterations());
279 double newEnergy = minimize.getEnergy();
280 int status = minimize.getStatus();
281 if (status != 0) {
282 break;
283 }
284 if (abs(newEnergy - energy) <= tolerance) {
285 break;
286 }
287 energy = newEnergy;
288
289
290 crystalMinimize.minimize(minimizeOptions.getNBFGS(), minimizeOptions.getEps(), minimizeOptions.getIterations());
291 newEnergy = crystalMinimize.getEnergy();
292 status = crystalMinimize.getStatus();
293 if (status != 0) {
294 break;
295 }
296 if (abs(newEnergy - energy) <= tolerance) {
297 break;
298 }
299 energy = newEnergy;
300 if (minIterations > 0) {
301 int coordIters = minimize.getIterations();
302 int latticeIters = crystalMinimize.getIterations();
303 if (coordIters < minIterations && latticeIters < minIterations) {
304
305 logger.info(format(" Current iteration (coords: %3d, lattice: %3d) has exceeded maximum allowable iterations (%3d).", coordIters, latticeIters, minIterations));
306 break;
307 }
308 }
309
310 if (cycles > 0 && ++numCycles >= cycles) {
311 logger.info(format(" Current cycle (%3d) has exceeded maximum allowable cycles (%3d).", numCycles, cycles));
312 break;
313 }
314 }
315 }
316 updateTitle(energy);
317 }
318
319
320
321
322 @Override
323 public List<Potential> getPotentials() {
324 List<Potential> potentials;
325 if (xtalEnergy == null) {
326 potentials = Collections.emptyList();
327 } else {
328 potentials = Collections.singletonList((Potential) xtalEnergy);
329 }
330 return potentials;
331 }
332
333 }