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.test;
39
40 import ffx.algorithms.cli.AlgorithmsCommand;
41 import ffx.algorithms.optimize.Minimize;
42 import ffx.crystal.Crystal;
43 import ffx.numerics.Potential;
44 import ffx.potential.ForceFieldEnergy;
45 import ffx.potential.MolecularAssembly;
46 import ffx.potential.bonded.Atom;
47 import ffx.utilities.Constants;
48 import ffx.utilities.FFXBinding;
49 import org.apache.commons.io.FilenameUtils;
50 import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
51 import picocli.CommandLine.Command;
52 import picocli.CommandLine.Option;
53 import picocli.CommandLine.Parameters;
54
55 import java.io.File;
56 import java.util.Arrays;
57 import java.util.Random;
58
59
60
61
62
63
64
65
66 @Command(description = " Search for minimum energy polymoprhs for a given space group.", name = "test.CrystalSearch")
67 public class CrystalSearch extends AlgorithmsCommand {
68
69
70
71
72 @Option(names = {"-e", "--eps"}, paramLabel = "1.0", defaultValue = "1.0",
73 description = "Minimization convergence criteria (Kcal/mol/Ang).")
74 private double eps;
75
76
77
78
79 @Option(names = {"-t", "--trials"}, paramLabel = "10", defaultValue = "10",
80 description = "Number of random trials.")
81 private int nTrials;
82
83
84
85
86 @Parameters(arity = "1", paramLabel = "file",
87 description = "A PDB or XYZ input file.")
88 private String filename;
89
90
91
92
93 public CrystalSearch() {
94 super();
95 }
96
97
98
99
100
101 public CrystalSearch(FFXBinding binding) {
102 super(binding);
103 }
104
105
106
107
108
109 public CrystalSearch(String[] args) {
110 super(args);
111 }
112
113
114
115
116
117
118
119
120
121 private static double getRandomNumber(double maxShift, double minShift, Random rando) {
122 return minShift + (maxShift - minShift) * rando.nextDouble();
123 }
124
125
126
127
128
129
130
131
132 private static double[][] getAtomicCoordinates(Atom[] atoms, int numberOfAtoms) {
133 double[] coords = new double[3];
134 double[][] atomCoords = new double[numberOfAtoms][3];
135 for (int j = 0; j < numberOfAtoms; j++) {
136 coords[0] = atoms[j].getX();
137 coords[1] = atoms[j].getY();
138 coords[2] = atoms[j].getZ();
139 for (int k = 0; k < 3; k++) {
140 atomCoords[j][k] = coords[k];
141 }
142 }
143 return atomCoords;
144 }
145
146
147
148
149
150
151
152
153
154 private static double density(double mass, int nSymm, Crystal unitCell) {
155 return (mass * nSymm / Constants.AVOGADRO) * (1.0e24 / unitCell.volume);
156 }
157
158
159
160
161 @Override
162 public CrystalSearch run() {
163
164 if (!init()) {
165 return this;
166 }
167
168
169 MolecularAssembly molecularAssembly = getActiveAssembly(filename);
170 if (molecularAssembly == null) {
171 logger.info(helpString());
172 return this;
173 }
174
175 Crystal crystal = molecularAssembly.getCrystal().getUnitCell();
176
177 logger.info("\n Searching for " + filename);
178 logger.info(" RMS gradient convergence criteria: " + eps);
179
180 ForceFieldEnergy forceFieldEnergy = molecularAssembly.getPotentialEnergy();
181 Minimize minimize = new Minimize(molecularAssembly, null);
182 Atom[] atoms = molecularAssembly.getAtomArray();
183 int nSymm = 4;
184
185
186 double[] translate = new double[3];
187
188
189 double[] com = new double[3];
190
191
192 int counter = 1;
193
194
195 final double avogadro = Constants.AVOGADRO;
196
197
198 int nAtoms = atoms.length;
199
200
201
202 double mass = 0.0;
203
204
205 Random random = new Random();
206
207
208 double[][] newAtoms = new double[nAtoms][3];
209
210
211 double[] finalCoords = new double[3];
212
213
214 double[][] bestAtoms = new double[nAtoms][3];
215
216 double[][] originalAtoms = new double[nAtoms][3];
217
218 double[] bestCrystalParameters = new double[6];
219
220
221 double a = 0.0;
222 double b = 0.0;
223 double c = 0.0;
224 double alpha = 0.0;
225 double beta = 0.0;
226 double gamma = 0.0;
227
228
229 double max = 0.5;
230 double min = -max;
231
232
233 double minDensity = 0.8;
234 double maxDensity = 1.3;
235
236
237 boolean likelyDensity = false;
238
239
240
241
242
243 com[0] = 0.0;
244 com[1] = 0.0;
245 com[2] = 0.0;
246
247
248 for (Atom atom : atoms) {
249 com[0] = com[0] + atom.getX();
250 com[1] = com[1] + atom.getY();
251 com[2] = com[2] + atom.getZ();
252 }
253
254
255 com[0] = com[0] / nAtoms;
256 com[1] = com[1] / nAtoms;
257 com[2] = com[2] / nAtoms;
258
259
260 crystal.toPrimaryCell(com, translate);
261 translate[0] = translate[0] - com[0];
262 translate[1] = translate[1] - com[1];
263 translate[2] = translate[2] - com[2];
264
265
266 for (Atom atom : atoms) {
267 atom.move(translate);
268 mass += atom.getMass();
269 }
270
271 double energy2 = Double.MAX_VALUE;
272
273
274 for (int i = 0; i < nTrials; i++) {
275
276
277 while (!likelyDensity) {
278 a = getRandomNumber(14, 8, random);
279 b = getRandomNumber(14, 8, random);
280 c = getRandomNumber(14, 8, random);
281
282
283 alpha = 90;
284 beta = getRandomNumber(150, 140, random);
285 gamma = 90;
286
287 crystal.changeUnitCellParameters(a, b, c, alpha, beta, gamma);
288 double den = density(mass, nSymm, crystal);
289 if (den > minDensity && den < maxDensity) {
290 likelyDensity = true;
291 }
292 }
293
294 logger.info("---------------------- Trial Number: " + counter + " ----------------------");
295 counter++;
296 logger.info("Cell parameters: " + a + ' ' + b + ' ' + c + ' ' + beta);
297
298
299
300 likelyDensity = false;
301
302
303 forceFieldEnergy.setCrystal(crystal);
304
305
306
307 double s = random.nextDouble();
308 double σ1 = Math.sqrt(1 - s);
309 double σ2 = Math.sqrt(s);
310 double θ1 = 2 * Math.PI * random.nextDouble();
311 double θ2 = 2 * Math.PI * random.nextDouble();
312 double w = Math.cos(θ2) * σ2;
313 double x = Math.sin(θ1) * σ1;
314 double y = Math.cos(θ1) * σ1;
315 double z = Math.sin(θ2) * σ2;
316
317
318 Rotation rotation = new Rotation(w, x, y, z, true);
319
320
321 for (int j = 0; j < nAtoms; j++) {
322 atoms[j].rotate(rotation.getMatrix());
323 }
324
325
326
327 double d = getRandomNumber(max, min, random);
328 double e = getRandomNumber(max, min, random);
329 double f = getRandomNumber(max, min, random);
330
331 double[] translation = new double[]{d, e, f};
332 logger.info(" Translation vector: " + Arrays.toString(translation));
333
334 for (int k = 0; k < nAtoms; k++) {
335 atoms[k].move(translation);
336 }
337
338
339 Potential g = minimize.minimize(eps);
340
341
342 newAtoms = getAtomicCoordinates(atoms, nAtoms);
343
344
345 double energy = forceFieldEnergy.energy(false, true);
346
347
348 if (i == 0) {
349 energy2 = energy;
350 for (int l = 0; l < nAtoms; l++) {
351 for (int m = 0; m < 3; m++) {
352 originalAtoms[l][m] = newAtoms[l][m];
353 }
354 }
355 } else if (energy < energy2) {
356
357 energy2 = energy;
358 for (int l = 0; l < nAtoms; l++) {
359 for (int m = 0; m < 3; m++) {
360 bestAtoms[l][m] = newAtoms[l][m];
361 }
362 }
363 bestCrystalParameters = new double[]{a, b, c, alpha, beta, gamma};
364 }
365
366 for (int n = 0; n < nAtoms; n++) {
367 finalCoords[0] = originalAtoms[n][0];
368 finalCoords[1] = originalAtoms[n][1];
369 finalCoords[2] = originalAtoms[n][2];
370 atoms[n].moveTo(finalCoords[0], finalCoords[1], finalCoords[2]);
371 }
372
373
374 if (i == nTrials - 1) {
375 for (int n = 0; n < nAtoms; n++) {
376 finalCoords[0] = bestAtoms[n][0];
377 finalCoords[1] = bestAtoms[n][1];
378 finalCoords[2] = bestAtoms[n][2];
379 atoms[n].moveTo(finalCoords[0], finalCoords[1], finalCoords[2]);
380 }
381 crystal.changeUnitCellParameters(bestCrystalParameters[0], bestCrystalParameters[1],
382 bestCrystalParameters[2], bestCrystalParameters[3], bestCrystalParameters[4],
383 bestCrystalParameters[5]);
384 forceFieldEnergy.setCrystal(crystal);
385 logger.info("Final energy is " + energy2);
386 }
387 }
388
389
390 String ext = FilenameUtils.getExtension(filename);
391 filename = FilenameUtils.removeExtension(filename);
392
393 if (ext.toUpperCase().contains("XYZ")) {
394 algorithmFunctions.saveAsXYZ(molecularAssembly, new File(filename + ".xyz"));
395 } else {
396 algorithmFunctions.saveAsPDB(molecularAssembly, new File(filename + ".pdb"));
397 }
398
399 return this;
400 }
401 }