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.realspace.commands.test;
39
40 import edu.rit.pj.Comm;
41 import ffx.algorithms.cli.AlgorithmsCommand;
42 import ffx.algorithms.cli.DynamicsOptions;
43 import ffx.algorithms.cli.LambdaParticleOptions;
44 import ffx.algorithms.dynamics.MolecularDynamics;
45 import ffx.algorithms.dynamics.integrators.IntegratorEnum;
46 import ffx.algorithms.dynamics.thermostats.ThermostatEnum;
47 import ffx.algorithms.thermodynamics.HistogramData;
48 import ffx.algorithms.thermodynamics.LambdaData;
49 import ffx.algorithms.thermodynamics.OrthogonalSpaceTempering;
50 import ffx.algorithms.thermodynamics.OrthogonalSpaceTempering.OptimizationParameters;
51 import ffx.numerics.Potential;
52 import ffx.potential.ForceFieldEnergy;
53 import ffx.potential.MolecularAssembly;
54 import ffx.potential.bonded.Atom;
55 import ffx.potential.bonded.MSNode;
56 import ffx.realspace.cli.RealSpaceOptions;
57 import ffx.utilities.FFXBinding;
58 import ffx.xray.RefinementEnergy;
59 import org.apache.commons.configuration2.CompositeConfiguration;
60 import org.apache.commons.io.FilenameUtils;
61 import picocli.CommandLine.Command;
62 import picocli.CommandLine.Mixin;
63 import picocli.CommandLine.Option;
64 import picocli.CommandLine.Parameters;
65
66 import java.io.File;
67 import java.util.Collections;
68 import java.util.List;
69 import java.util.logging.Logger;
70
71
72
73
74
75
76
77
78 @Command(description = " Alchemical changes on a Real Space target.", name = "realspace.test.Alchemical")
79 public class Alchemical extends AlgorithmsCommand {
80
81 @Mixin
82 private DynamicsOptions dynamicsOptions;
83
84 @Mixin
85 private LambdaParticleOptions lambdaParticleOptions;
86
87 @Mixin
88 private RealSpaceOptions realSpaceOptions;
89
90 private static final Logger logger = Logger.getLogger(RealSpaceOptions.class.getName());
91
92
93
94
95 @Option(names = {"-I", "--onlyions"}, paramLabel = "false",
96 description = "Set to only optimize ions (of a single type).")
97 private boolean onlyIons = false;
98
99
100
101
102 @Option(names = {"--itype", "--iontype"}, paramLabel = "null",
103 description = "Specify which ion to run optimization on. If none is specified, default behavior chooses the first ion found in the PDB file.")
104 private String[] ionType = null;
105
106
107
108
109 @Option(names = {"-N", "--neutralize"}, paramLabel = "false",
110 description = "Neutralize the crystal's charge by adding more of the selected ion")
111 private boolean neutralize = false;
112
113
114
115
116 @Option(names = {"-W", "--onlyWater"}, paramLabel = "false",
117 description = "Set to only optimize water.")
118 private boolean onlyWater = false;
119
120
121
122
123 @Parameters(arity = "1..*", paramLabel = "files", description = "PDB and Real Space input files.")
124 private List<String> filenames;
125
126
127 private double lambda = 0.0;
128
129
130 private ThermostatEnum thermostat = ThermostatEnum.ADIABATIC;
131
132
133 private IntegratorEnum integrator = IntegratorEnum.STOCHASTIC;
134
135
136 private String fileType = "PDB";
137
138
139 private boolean runOST = true;
140
141
142 private boolean initVelocities = true;
143
144 private OrthogonalSpaceTempering orthogonalSpaceTempering;
145
146
147
148
149 public Alchemical() {
150 super();
151 }
152
153
154
155
156
157 public Alchemical(String[] args) {
158 super(args);
159 }
160
161
162
163
164
165 public Alchemical(FFXBinding binding) {
166 super(binding);
167 }
168
169 @Override
170 public Alchemical run() {
171
172 if (!init()) {
173 return this;
174 }
175 dynamicsOptions.init();
176 System.setProperty("lambdaterm", "true");
177
178 String modelFilename;
179 if (filenames != null && filenames.size() > 0) {
180 activeAssembly = algorithmFunctions.open(filenames.get(0));
181 modelFilename = filenames.get(0);
182 } else if (activeAssembly == null) {
183 logger.info(helpString());
184 return this;
185 } else {
186 modelFilename = activeAssembly.getFile().getAbsolutePath();
187 }
188 MolecularAssembly[] assemblies = new MolecularAssembly[]{activeAssembly};
189
190 logger.info("\n Running Alchemical Changes on " + modelFilename);
191
192 File structureFile = new File(FilenameUtils.normalize(modelFilename));
193 structureFile = new File(structureFile.getAbsolutePath());
194 String baseFilename = FilenameUtils.removeExtension(structureFile.getName());
195 File histogramRestart = new File(baseFilename + ".his");
196 File lambdaRestart = new File(baseFilename + ".lam");
197 File dyn = new File(baseFilename + ".dyn");
198
199 Comm world = Comm.world();
200 int size = world.size();
201 int rank = 0;
202 double[] energyArray = new double[world.size()];
203 for (int i = 0; i < world.size(); i++) {
204 energyArray[i] = Double.MAX_VALUE;
205 }
206
207
208 if (size > 1) {
209 rank = world.rank();
210 File rankDirectory = new File(
211 structureFile.getParent() + File.separator + Integer.toString(rank));
212 if (!rankDirectory.exists()) {
213 rankDirectory.mkdir();
214 }
215 lambdaRestart = new File(rankDirectory.getPath() + File.separator + baseFilename + ".lam");
216 dyn = new File(rankDirectory.getPath() + File.separator + baseFilename + ".dyn");
217 structureFile = new File(rankDirectory.getPath() + File.separator + structureFile.getName());
218 }
219 if (!dyn.exists()) {
220 dyn = null;
221 }
222
223
224 Atom[] atoms = activeAssembly.getAtomArray();
225
226
227 ForceFieldEnergy forceFieldEnergy = activeAssembly.getPotentialEnergy();
228 forceFieldEnergy.setPrintOnFailure(false, false);
229
230
231
232
233
234 for (int i = 0; i <= atoms.length; i++) {
235 Atom ai = atoms[i - 1];
236 ai.setUse(true);
237 ai.setActive(false);
238 ai.setApplyLambda(false);
239 }
240
241 double crystalCharge = activeAssembly.getCharge(true);
242 logger.info(" Overall crystal charge: " + crystalCharge);
243 List<MSNode> ions = assemblies[0].getIons();
244 List<MSNode> water = assemblies[0].getWater();
245
246
247 if (!onlyWater) {
248 logger.info("Doing ions.");
249 if (ions == null || ions.size() == 0) {
250 logger.info("\n Please add an ion to the PDB file to scan with.");
251 return this;
252 }
253 for (MSNode msNode : ions) {
254
255 boolean ionSelected = false;
256 if (ionType != null) {
257 logger.info("Selecting ion.");
258 List<Atom> atomList = msNode.getAtomList();
259 if (!atomList.isEmpty()) {
260 String atomName = atomList.get(0).getName();
261 for (String targetIonType : ionType) {
262 if (atomName.equals(targetIonType)) {
263 ionSelected = true;
264 break;
265 }
266 }
267 }
268 }
269
270 if (ionSelected) {
271 logger.info("Ion has been selected.");
272 for (Atom atom : msNode.getAtomList()) {
273 System.out.println("Activating ions");
274 atom.setUse(true);
275 atom.setActive(true);
276 atom.setApplyLambda(true);
277 logger.info(" Alchemical atom: " + atom.toString());
278 }
279 } else {
280 logger.info("Ion has not been selected.");
281 if (neutralize) {
282 logger.info("Neutralizing crystal.");
283 double ionCharge = 0;
284 for (Atom atom : msNode.getAtomList()) {
285 ionCharge += atom.getMultipoleType().getCharge();
286 }
287 logger.info("Ion charge is: " + Double.toString(ionCharge));
288 int numIons = (int) (-1 * (Math.ceil(crystalCharge / ionCharge)));
289 if (numIons > 0) {
290 List<Atom> atomList = msNode.getAtomList();
291 String atomName = atomList.isEmpty() ? "" : atomList.get(0).getName();
292 logger.info(numIons + " " + atomName
293 + " ions needed to neutralize the crystal.");
294 ionType = new String[]{atomName};
295 for (Atom atom : msNode.getAtomList()) {
296 atom.setUse(true);
297 atom.setActive(true);
298 atom.setApplyLambda(true);
299 logger.info(" Alchemical atom: " + atom.toString());
300 }
301 }
302 } else {
303 List<Atom> atomList = msNode.getAtomList();
304 String atomName = atomList.isEmpty() ? "" : atomList.get(0).getName();
305 ionType = new String[]{atomName};
306 for (Atom atom : msNode.getAtomList()) {
307 atom.setUse(true);
308 atom.setActive(true);
309 atom.setApplyLambda(true);
310 logger.info(" Alchemical atom: " + atom.toString());
311 }
312 }
313 }
314 }
315 }
316
317
318 if (!onlyIons) {
319 logger.info(water.size() + " water molecules in this PDB.");
320 if (water == null || water.size() == 0) {
321 logger.info("\n Please add water to the PDB file to scan with.");
322 return this;
323 }
324 for (MSNode msNode : water) {
325 for (Atom atom : msNode.getAtomList()) {
326
327 atom.setUse(true);
328 atom.setActive(true);
329 atom.setApplyLambda(true);
330 logger.info(" Water atom: " + atom.toString());
331 }
332 }
333 }
334
335 RefinementEnergy refinementEnergy = realSpaceOptions.toRealSpaceEnergy(filenames, assemblies);
336
337 refinementEnergy.setLambda(lambda);
338
339 boolean asynchronous = true;
340
341 CompositeConfiguration props = assemblies[0].getProperties();
342
343 HistogramData histogramData = HistogramData.readHistogram(histogramRestart);
344 if (lambdaRestart == null) {
345 String filename = histogramRestart.toString().replaceFirst("\\.his$", ".lam");
346 lambdaRestart = new File(filename);
347 }
348 LambdaData lambdaData = LambdaData.readLambdaData(lambdaRestart);
349
350 orthogonalSpaceTempering = new OrthogonalSpaceTempering(refinementEnergy,
351 refinementEnergy, histogramData, lambdaData, props, dynamicsOptions, lambdaParticleOptions, algorithmListener);
352
353 orthogonalSpaceTempering.setLambda(lambda);
354
355 orthogonalSpaceTempering.getOptimizationParameters().setOptimization(true, activeAssembly);
356
357 MolecularDynamics molDyn = new MolecularDynamics(assemblies[0], orthogonalSpaceTempering,
358 algorithmListener, thermostat, integrator);
359
360 algorithmFunctions.energy(assemblies[0]);
361 molDyn.dynamic(dynamicsOptions.getSteps(), dynamicsOptions.getDt(), dynamicsOptions.getReport(),
362 dynamicsOptions.getWrite(), dynamicsOptions.getTemperature(), true,
363 fileType, dynamicsOptions.getWrite(), dyn);
364 logger.info(" Searching for low energy coordinates");
365 OptimizationParameters opt = orthogonalSpaceTempering.getOptimizationParameters();
366 double[] lowEnergyCoordinates = opt.getOptimumCoordinates();
367 double currentOSTOptimum = opt.getOptimumEnergy();
368 if (lowEnergyCoordinates != null) {
369 forceFieldEnergy.setCoordinates(lowEnergyCoordinates);
370 logger.info("\n Minimum coordinates found: " + lowEnergyCoordinates);
371 } else {
372 logger.info(" OST stage did not succeed in finding a minimum.");
373 }
374
375 return this;
376 }
377
378 @Override
379 public List<Potential> getPotentials() {
380 return orthogonalSpaceTempering == null ? Collections.emptyList() :
381 Collections.singletonList(orthogonalSpaceTempering);
382 }
383 }