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.cli;
39
40 import ffx.algorithms.AlgorithmFunctions;
41 import ffx.algorithms.AlgorithmListener;
42 import ffx.algorithms.optimize.RotamerOptimization;
43 import ffx.crystal.CrystalPotential;
44 import ffx.numerics.Potential;
45 import ffx.potential.DualTopologyEnergy;
46 import ffx.potential.MolecularAssembly;
47 import ffx.potential.QuadTopologyEnergy;
48 import ffx.potential.bonded.LambdaInterface;
49 import ffx.potential.bonded.Polymer;
50 import ffx.potential.bonded.Residue;
51 import ffx.potential.bonded.RotamerLibrary;
52 import ffx.potential.cli.AlchemicalOptions;
53 import ffx.potential.cli.TopologyOptions;
54 import org.apache.commons.configuration2.CompositeConfiguration;
55 import picocli.CommandLine.ArgGroup;
56 import picocli.CommandLine.Option;
57
58 import java.io.File;
59 import java.util.ArrayList;
60 import java.util.List;
61 import java.util.logging.Logger;
62 import java.util.regex.Matcher;
63 import java.util.regex.Pattern;
64
65 import static java.lang.String.format;
66
67
68
69
70
71
72
73
74
75
76 public class MultiDynamicsOptions {
77
78 private static final Logger logger = Logger.getLogger(MultiDynamicsOptions.class.getName());
79
80
81
82
83 @ArgGroup(heading = "%n Multiple Walker Options for MPI Simulations%n", validate = false)
84 private final MultiDynamicsOptionGroup group = new MultiDynamicsOptionGroup();
85
86
87
88
89
90
91
92
93
94
95
96
97 public void distribute(MolecularAssembly[] molecularAssemblies, Potential[] potentials,
98 CrystalPotential crystalPotential, AlgorithmFunctions algorithmFunctions, int rank,
99 int worldSize) {
100 if (!group.distributeWalkersString.equalsIgnoreCase("AUTO")
101 && !group.distributeWalkersString.equalsIgnoreCase("OFF")) {
102 logger.info(" Distributing walker conformations.");
103 int nSys = molecularAssemblies.length;
104 assert nSys == potentials.length;
105 switch (nSys) {
106 case 1 -> optStructure(molecularAssemblies[0], crystalPotential, algorithmFunctions, rank,
107 worldSize);
108 case 2 -> {
109 DualTopologyEnergy dte = (DualTopologyEnergy) crystalPotential;
110 if (dte.getNumSharedVariables() == dte.getNumberOfVariables()) {
111 logger.info(" Generating starting structures based on dual-topology:");
112 optStructure(molecularAssemblies[0], dte, algorithmFunctions, rank, worldSize);
113 } else {
114 logger.info(
115 " Generating separate starting structures for each topology of the dual topology:");
116 optStructure(molecularAssemblies[0], potentials[0], algorithmFunctions, rank, worldSize);
117 optStructure(molecularAssemblies[1], potentials[1], algorithmFunctions, rank, worldSize);
118 }
119 }
120 case 4 -> {
121 QuadTopologyEnergy qte = (QuadTopologyEnergy) crystalPotential;
122 optStructure(molecularAssemblies[0], qte.getDualTopA(), algorithmFunctions, rank,
123 worldSize);
124 optStructure(molecularAssemblies[3], qte.getDualTopB(), algorithmFunctions, rank,
125 worldSize);
126 }
127
128 default -> logger.severe(" First: must have 1, 2, or 4 topologies.");
129 }
130 } else {
131 logger.finer(" Skipping RO-based distribution of initial configurations.");
132 }
133 }
134
135
136
137
138
139
140
141
142
143
144
145 public void distribute(MolecularAssembly[] molecularAssemblies, CrystalPotential crystalPotential,
146 AlgorithmFunctions algorithmFunctions, int rank, int worldSize) {
147 int ntops = molecularAssemblies.length;
148 Potential[] energies = new Potential[ntops];
149 for (int i = 0; i < ntops; i++) {
150 energies[i] = molecularAssemblies[i].getPotentialEnergy();
151 }
152 distribute(molecularAssemblies, energies, crystalPotential, algorithmFunctions, rank, worldSize);
153 }
154
155
156
157
158
159
160
161
162 public boolean isSynchronous() {
163 return group.synchronous;
164 }
165
166 public void setSynchronous(boolean synchronous) {
167 group.synchronous = synchronous;
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184 public MolecularAssembly openFile(AlgorithmFunctions algorithmFunctions,
185 TopologyOptions topologyOptions, int threadsPer, String toOpen, int topNum,
186 AlchemicalOptions alchemicalOptions, File structureFile, int rank) {
187 boolean autoDist = group.distributeWalkersString.equalsIgnoreCase("AUTO");
188
189 if (autoDist) {
190 String openName = format("%s_%d", toOpen, rank + 1);
191 File testFile = new File(openName);
192 if (testFile.exists()) {
193 toOpen = openName;
194 } else {
195 logger.warning(format(" File %s does not exist; using default %s", openName, toOpen));
196 }
197 }
198 MolecularAssembly assembly = alchemicalOptions.openFile(algorithmFunctions, topologyOptions,
199 threadsPer, toOpen, topNum);
200 assembly.setFile(structureFile);
201 return assembly;
202 }
203
204
205
206
207
208
209 private String[] parseDistributed() {
210 String distributeWalkersString = group.distributeWalkersString;
211 if (distributeWalkersString.equalsIgnoreCase("OFF")
212 || distributeWalkersString.equalsIgnoreCase("AUTO") || distributeWalkersString.isEmpty()) {
213 return null;
214 }
215 return distributeWalkersString.split("\\.");
216 }
217
218
219
220
221
222
223
224
225 public String getDistributeWalkersString() {
226 return group.distributeWalkersString;
227 }
228
229
230
231
232
233
234
235 private void optStructure(MolecularAssembly molecularAssembly, Potential potential,
236 AlgorithmFunctions algorithmFunctions, int rank, int worldSize) {
237 RotamerLibrary rLib = new RotamerLibrary(false);
238 String[] distribRes = parseDistributed();
239
240 if (distribRes == null || distribRes.length == 0) {
241 throw new IllegalArgumentException(
242 " Programming error: Must have list of residues to split on!");
243 }
244
245 LambdaInterface lambdaInterface =
246 (potential instanceof LambdaInterface) ? (LambdaInterface) potential : null;
247 double initLam = -1.0;
248 if (lambdaInterface != null) {
249 initLam = lambdaInterface.getLambda();
250 lambdaInterface.setLambda(0.5);
251 }
252
253 Pattern chainMatcher = Pattern.compile("^([a-zA-Z])?([0-9]+)$");
254
255 List<Residue> residueList = new ArrayList<>(distribRes.length);
256
257 for (String ts : distribRes) {
258 Matcher m = chainMatcher.matcher(ts);
259 Character chainID;
260 int resNum;
261 if (m.find()) {
262 if (m.groupCount() == 2) {
263 chainID = m.group(1).charAt(0);
264 resNum = Integer.parseInt(m.group(2));
265 } else {
266 chainID = ' ';
267 resNum = Integer.parseInt(m.group(1));
268 }
269 } else {
270 logger.warning(format(" Could not parse %s as a valid residue!", ts));
271 continue;
272 }
273 logger.info(format(" Looking for chain %c residue %d", chainID, resNum));
274
275 for (Polymer p : molecularAssembly.getChains()) {
276 if (p.getChainID() == chainID) {
277 for (Residue r : p.getResidues()) {
278 if (r.getResidueNumber() == resNum && r.setRotamers(rLib) != null) {
279 residueList.add(r);
280 }
281 }
282 }
283 }
284 }
285
286 if (residueList.isEmpty()) {
287 throw new IllegalArgumentException(" No valid entries for distWalkers!");
288 }
289
290 AlgorithmListener alist = algorithmFunctions.getDefaultListener();
291 RotamerOptimization ropt = new RotamerOptimization(molecularAssembly, potential, alist);
292 ropt.setRotamerLibrary(rLib);
293
294 ropt.setThreeBodyEnergy(false);
295
296 CompositeConfiguration properties = molecularAssembly.getProperties();
297 if (!properties.containsKey("ro-ensembleNumber") && !properties.containsKey(
298 "ro-ensembleEnergy")) {
299 logger.info(format(" Setting ensemble to default of number of walkers %d", worldSize));
300 ropt.setEnsemble(worldSize);
301 }
302
303 ropt.setPrintFiles(false);
304 ropt.setResiduesIgnoreNull(residueList);
305
306 RotamerLibrary.measureRotamers(residueList, false);
307 ropt.optimize(RotamerOptimization.Algorithm.ALL);
308 ropt.setCoordinatesToEnsemble(rank);
309
310
311
312 double[] xyz = new double[potential.getNumberOfVariables()];
313 potential.getCoordinates(xyz);
314 logger.info(" Final Optimized Energy:");
315 potential.energy(xyz, true);
316
317 if (lambdaInterface != null) {
318 lambdaInterface.setLambda(initLam);
319 }
320 }
321
322 public void setDistributeWalkersString(String distributeWalkersString) {
323 group.distributeWalkersString = distributeWalkersString;
324 }
325
326 public int getFirstDir() {
327 return group.firstDir;
328 }
329
330 public void setFirstDir(int firstDir) {
331 group.firstDir = firstDir;
332 }
333
334
335
336
337 private static class MultiDynamicsOptionGroup {
338
339
340 @Option(names = {"--firstDir"}, defaultValue = "0", paramLabel = "0",
341 description = "The first directory to use for multiple walker jobs.")
342 private int firstDir = 0;
343
344
345 @Option(names = {"-y", "--synchronous"}, defaultValue = "false",
346 description = "Walker communication is synchronous")
347 private boolean synchronous = false;
348
349
350
351
352
353
354 @Option(names = {"--dw", "--distributeWalkers"}, paramLabel = "OFF", defaultValue = "OFF",
355 description = "AUTO: Pick up per-walker configurations as [filename.pdb]_[num], or specify a residue to distribute on.")
356 private String distributeWalkersString = "OFF";
357 }
358 }