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.xray;
39
40 import edu.rit.pj.ParallelTeam;
41 import ffx.algorithms.Terminatable;
42 import ffx.crystal.Crystal;
43 import ffx.crystal.HKL;
44 import ffx.crystal.ReflectionList;
45 import ffx.numerics.optimization.LBFGS;
46 import ffx.numerics.optimization.LineSearch.LineSearchResult;
47 import ffx.numerics.optimization.OptimizationListener;
48 import ffx.xray.solvent.SolventModel;
49
50 import javax.annotation.Nullable;
51 import java.util.logging.Level;
52 import java.util.logging.Logger;
53
54 import static ffx.numerics.math.ScalarMath.b2u;
55 import static ffx.numerics.math.ScalarMath.u2b;
56 import static ffx.utilities.Constants.NS2SEC;
57 import static java.lang.Double.isNaN;
58 import static java.lang.Math.max;
59 import static java.lang.String.format;
60 import static java.lang.System.arraycopy;
61 import static java.util.Arrays.fill;
62 import static org.apache.commons.math3.util.FastMath.exp;
63 import static org.apache.commons.math3.util.FastMath.log;
64
65
66
67
68
69
70
71 public class ScaleBulkMinimize implements OptimizationListener, Terminatable {
72
73 private static final Logger logger = Logger.getLogger(ScaleBulkMinimize.class.getName());
74
75 private final ReflectionList reflectionList;
76 private final DiffractionRefinementData refinementData;
77 private final Crystal crystal;
78 private final CrystalReciprocalSpace crystalReciprocalSpace;
79 private final ScaleBulkEnergy bulkSolventEnergy;
80 private final int solventN;
81 private final int n;
82 private final double[] x;
83 private final double[] grad;
84 private final double[] scaling;
85 private boolean done = false;
86 private boolean terminate = false;
87 private long time;
88 private double grms;
89 private int nSteps;
90
91
92
93
94
95
96
97
98
99 public ScaleBulkMinimize(ReflectionList reflectionList,
100 DiffractionRefinementData refinementData,
101 CrystalReciprocalSpace crystalReciprocalSpace,
102 ParallelTeam parallelTeam) {
103 this.reflectionList = reflectionList;
104 this.refinementData = refinementData;
105 this.crystal = reflectionList.crystal;
106 this.crystalReciprocalSpace = crystalReciprocalSpace;
107
108 if (crystalReciprocalSpace.getSolventModel() == SolventModel.NONE ||
109 refinementData.bulkSolventFixed) {
110 solventN = 1;
111 } else {
112 solventN = 3;
113 }
114 n = solventN + refinementData.nScale;
115 bulkSolventEnergy = new ScaleBulkEnergy(reflectionList, refinementData, n, parallelTeam);
116
117 x = new double[n];
118 grad = new double[n];
119 scaling = new double[n];
120 fill(scaling, 1.0);
121
122
123 if (refinementData.modelScaleK == 0.0) {
124 x[0] = getInitialKOverall(x);
125 logger.info(format("\n Initial K_Overall (Fc to Fo Scale): %4.2f", exp(0.25 * x[0])));
126 } else {
127 x[0] = refinementData.modelScaleK;
128 }
129
130
131 if (solventN > 1) {
132 x[1] = refinementData.bulkSolventK;
133 x[2] = refinementData.bulkSolventUeq;
134 }
135 for (int i = 0; i < 6; i++) {
136 if (crystal.scaleB[i] >= 0) {
137 x[solventN + crystal.scaleB[i]] = refinementData.modelAnisoB[i];
138 }
139 }
140 }
141
142
143
144
145
146
147
148 public double[] getCoordinates(@Nullable double[] x) {
149 if (x == null) {
150 x = new double[this.x.length];
151 }
152 arraycopy(this.x, 0, x, 0, this.x.length);
153 return x;
154 }
155
156
157
158
159
160
161 public int getNumberOfVariables() {
162 return x.length;
163 }
164
165
166
167
168
169
170
171 public ScaleBulkEnergy minimize(double eps) {
172 return minimize(7, eps);
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186 public ScaleBulkEnergy minimize(int m, double eps) {
187 bulkSolventEnergy.setScaling(scaling);
188 double e = bulkSolventEnergy.energyAndGradient(x, grad);
189
190 long mtime = -System.nanoTime();
191 time = -System.nanoTime();
192 done = false;
193 int status = LBFGS.minimize(n, m, x, e, grad, eps, bulkSolventEnergy, this);
194 done = true;
195 switch (status) {
196 case 0:
197 logger.info(format("\n Optimization achieved convergence criteria: %10.5f\n", grms));
198 break;
199 case 1:
200 logger.info(format("\n Optimization terminated at step %d.\n", nSteps));
201 break;
202 default:
203 logger.warning("\n Optimization failed.\n");
204 }
205 refinementData.modelScaleK = x[0] / scaling[0];
206 if (solventN > 1) {
207 refinementData.bulkSolventK = x[1] / scaling[1];
208 refinementData.bulkSolventUeq = x[2] / scaling[2];
209 }
210 for (int i = 0; i < 6; i++) {
211 int offset = crystal.scaleB[i];
212 if (offset >= 0) {
213 int index = solventN + offset;
214 refinementData.modelAnisoB[i] = x[index] / scaling[index];
215 }
216 }
217
218 mtime += System.nanoTime();
219 logger.info(format(" Optimization time: %8.3f (sec)\n", mtime * NS2SEC));
220
221 bulkSolventEnergy.setScaling(null);
222
223 return bulkSolventEnergy;
224 }
225
226
227
228
229 @Override
230 public boolean optimizationUpdate(int iter, int nBFGS, int nfun, double grms, double xrms,
231 double f, double df, double angle, LineSearchResult info) {
232 long currentTime = System.nanoTime();
233 Double seconds = (currentTime - time) * NS2SEC;
234 time = currentTime;
235 this.grms = grms;
236 this.nSteps = iter;
237
238 if (iter == 0) {
239 if (nBFGS > 0) {
240 if (solventN > 1) {
241 logger.info("\n Limited Memory BFGS Quasi-Newton Optimization of Scaling and Solvent Parameters\n");
242 } else {
243 logger.info("\n Limited Memory BFGS Quasi-Newton Optimization of Overall Scaling Parameters\n");
244 }
245 } else {
246 if (solventN > 1) {
247 logger.info("\n Steepest Decent Optimization of Scaling and Solvent Parameters\n");
248 } else {
249 logger.info("\n Steepest Decent Optimization of Overall Scaling Parameters\n");
250 }
251 }
252 logger.info(" Cycle Energy G RMS Delta E Delta X Angle Evals Time R Rfree");
253 }
254 if (info == null) {
255 logger.info(format("%6d %12.5f %10.6f", iter, f, grms));
256 } else {
257 if (info == LineSearchResult.Success) {
258 double R = bulkSolventEnergy.getR();
259 double Rfree = bulkSolventEnergy.getRfree();
260 logger.info(format("%6d %12.5f %10.6f %10.6f %9.5f %8.2f %6d %8.3f %5.3f %5.3f",
261 iter, f, grms, df, xrms, angle, nfun, seconds, R, Rfree));
262 } else {
263 logger.info(format("%6d %12.5f %10.6f %10.6f %9.5f %8.2f %6d %8s",
264 iter, f, grms, df, xrms, angle, nfun, info));
265 }
266 }
267 if (terminate) {
268 logger.info(" The optimization received a termination request.");
269
270 return false;
271 }
272 return true;
273 }
274
275
276
277
278 @Override
279 public void terminate() {
280 terminate = true;
281 while (!done) {
282 synchronized (this) {
283 try {
284 wait(1);
285 } catch (Exception e) {
286 logger.log(Level.WARNING, "Exception terminating minimization.\n", e);
287 }
288 }
289 }
290 }
291
292
293
294
295
296
297 ScaleBulkEnergy getScaleBulkEnergy() {
298 return bulkSolventEnergy;
299 }
300
301
302
303
304
305
306
307
308 private double getInitialKOverall(double[] x) {
309 double[][] fc = refinementData.fc;
310 double[][] fSigf = refinementData.fSigF;
311 double[] grad = new double[x.length];
312
313 bulkSolventEnergy.setScaling(scaling);
314 double e = bulkSolventEnergy.energyAndGradient(x, grad);
315 bulkSolventEnergy.setScaling(null);
316
317 double sumfofc = 0.0;
318 double sumfc = 0.0;
319 for (HKL ih : reflectionList.hklList) {
320 int i = ih.getIndex();
321 if (isNaN(fc[i][0]) || isNaN(fSigf[i][0]) || fSigf[i][1] <= 0.0) {
322 continue;
323 }
324
325 double fcTotF = refinementData.fcTotF(i);
326 sumfofc += fSigf[i][0] * fcTotF;
327 sumfc += fcTotF * fcTotF;
328 }
329
330
331
332
333
334
335
336
337 return 4.0 * log(sumfofc / sumfc);
338 }
339
340
341
342
343
344
345
346 public void gridOptimizeBulkSolventModel() {
347 if (crystalReciprocalSpace == null) {
348 return;
349 }
350
351 bulkSolventEnergy.setScaling(scaling);
352
353
354 crystalReciprocalSpace.setDefaultSolventAB();
355
356
357 crystalReciprocalSpace.computeDensity(refinementData.fs);
358 double initialTarget = bulkSolventEnergy.energy(x);
359 double min = initialTarget;
360 double R = bulkSolventEnergy.getR();
361 double Rfree = bulkSolventEnergy.getRfree();
362 double minR = R;
363 double minRfree = Rfree;
364
365 double solventA = crystalReciprocalSpace.getSolventA();
366 double solventB = crystalReciprocalSpace.getSolventB();
367 double amin = solventA - 2.0;
368 double amax = solventA + 2.0;
369 double astep = 0.25;
370 double bmin = solventB - 0.2;
371 double bmax = solventB + 0.2;
372 double bstep = 0.05;
373 if (crystalReciprocalSpace.getSolventModel() == SolventModel.BINARY) {
374 amin = solventA - 0.4;
375 amax = solventA + 0.4;
376 astep = 0.05;
377 }
378
379 logger.info(" Grid Search for Bulk Solvent Model Parameters");
380 logger.info(" A B Target R Rfree");
381 logger.info(format(" Initial: %6.3f %6.3f %9.6f %6.3f %6.3f ",
382 solventA, solventB, min, R, Rfree));
383
384 int index = 0;
385 for (double a = amin; a <= amax; a += astep) {
386 for (double b = bmin; b <= bmax; b += bstep) {
387 crystalReciprocalSpace.setSolventAB(a, b);
388 crystalReciprocalSpace.computeDensity(refinementData.fs);
389 double sum = bulkSolventEnergy.energy(x);
390 R = bulkSolventEnergy.getR();
391 Rfree = bulkSolventEnergy.getRfree();
392
393 if (logger.isLoggable(Level.FINE)) {
394 logger.fine(format(" %8d %6.3f %6.3f %9.6f %6.3f %6.3f ", index, a, b, sum, R, Rfree));
395 } else if (sum < min) {
396 logger.info(format(" %8d %6.3f %6.3f %9.6f %6.3f %6.3f ", index, a, b, sum, R, Rfree));
397 }
398
399 index++;
400 if (sum < min) {
401 min = sum;
402 minR = R;
403 minRfree = Rfree;
404 solventA = a;
405 solventB = b;
406 }
407 }
408 }
409
410 logger.info(format(" Minimum: %6.3f %6.3f %9.6f %6.3f %6.3f ", solventA, solventB, min, minR, minRfree));
411 if (min < initialTarget) {
412 crystalReciprocalSpace.setSolventAB(solventA, solventB);
413 crystalReciprocalSpace.computeDensity(refinementData.fs);
414 }
415
416 bulkSolventEnergy.setScaling(null);
417 }
418
419
420
421
422
423
424 public void gridOptimizeKsBs() {
425 if (solventN < 3) {
426 return;
427 }
428
429 bulkSolventEnergy.setScaling(scaling);
430
431 double bulkSolventK = refinementData.bulkSolventK;
432 double bulkSolventUeq = refinementData.bulkSolventUeq;
433
434
435 x[0] = refinementData.modelScaleK;
436 x[1] = bulkSolventK;
437 x[2] = bulkSolventUeq;
438 double initialSum = bulkSolventEnergy.energy(x);
439 double initialR = bulkSolventEnergy.getR();
440 double initialRfree = bulkSolventEnergy.getRfree();
441 double min = initialSum;
442
443
444
445 if (bulkSolventK < 0.3) {
446 bulkSolventK = 0.3;
447 }
448
449 if (u2b(bulkSolventUeq) < 40.0) {
450 bulkSolventUeq = b2u(40.0);
451 }
452
453 double kmin = bulkSolventK - 0.3;
454 double kmax = bulkSolventK + 0.3;
455 double kstep = 0.02;
456 double initialB = u2b(bulkSolventUeq);
457 double umin = b2u(max(0.0, initialB - 40.0));
458 double umax = b2u(initialB + 40.0);
459 double ustep = b2u(2.0);
460
461 logger.info("\n Grid Search for Bulk Solvent Ks and Bs");
462 logger.info(" Ks Bs Target R Rfree");
463 logger.info(format(" Initial: %5.3f %8.3f %8.5f %5.3f %5.3f",
464 bulkSolventK, u2b(bulkSolventUeq), initialSum, initialR, initialRfree));
465
466 int index = 0;
467 for (double ks = kmin; ks <= kmax; ks += kstep) {
468 for (double ku = umin; ku <= umax; ku += ustep) {
469 x[1] = ks;
470 x[2] = ku;
471 double sum = bulkSolventEnergy.energy(x);
472 double R = bulkSolventEnergy.getR();
473 double Rfree = bulkSolventEnergy.getRfree();
474 if (logger.isLoggable(Level.FINE)) {
475 logger.fine(format(" %8d %5.3f %8.3f %8.5f %5.3f %5.3f", index, ks, u2b(ku), sum, R, Rfree));
476 } else if (sum < min) {
477 logger.info(format(" %8d %5.3f %8.3f %8.5f %5.3f %5.3f", index, ks, u2b(ku), sum, R, Rfree));
478 }
479 index++;
480 if (sum < min) {
481 min = sum;
482 bulkSolventK = ks;
483 bulkSolventUeq = ku;
484 }
485 }
486 }
487 x[1] = bulkSolventK;
488 x[2] = bulkSolventUeq;
489 double sum = bulkSolventEnergy.energy(x);
490 double R = bulkSolventEnergy.getR();
491 double Rfree = bulkSolventEnergy.getRfree();
492 logger.info(format(" Minimum: %5.3f %8.3f %8.5f %5.3f %5.3f",
493 bulkSolventK, u2b(bulkSolventUeq), sum, R, Rfree));
494 if (sum < initialSum) {
495 refinementData.bulkSolventK = bulkSolventK;
496 refinementData.bulkSolventUeq = bulkSolventUeq;
497 }
498
499 bulkSolventEnergy.setScaling(null);
500 }
501 }