1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """The frame order API object."""
24
25
26 from copy import deepcopy
27 from math import pi
28 from minfx.generic import generic_minimise
29 from minfx.grid import grid_point_array
30 from numpy import float64, zeros
31 from re import search
32
33
34 from lib.errors import RelaxError, RelaxNoModelError
35 from pipe_control import pipes
36 from pipe_control.interatomic import interatomic_loop, return_interatom
37 from pipe_control.mol_res_spin import return_spin, spin_loop
38 from pipe_control.rdc import check_rdcs
39 from specific_analyses.api_base import API_base
40 from specific_analyses.api_common import API_common
41 from specific_analyses.frame_order.data import domain_moving
42 from specific_analyses.frame_order.optimisation import grid_row, store_bc_data, target_fn_setup, unpack_opt_results
43 from specific_analyses.frame_order.parameter_object import Frame_order_params
44 from specific_analyses.frame_order.parameters import assemble_param_vector, linear_constraints, param_num, update_model
45
46
48 """Class containing the specific methods of the Frame Order theories."""
49
50
51 instance = None
52
67
68
70 """Generator method for looping over the base data - RDCs and PCSs.
71
72 This loop yields the following:
73
74 - The RDC identification data for the interatomic data container and alignment.
75 - The PCS identification data for the spin data container and alignment.
76
77 @return: The base data type ('rdc' or 'pcs'), the spin or interatomic data container information (either one or two spin IDs), and the alignment ID string.
78 @rtype: list of str
79 """
80
81
82 for interatom in interatomic_loop(selection1=domain_moving()):
83
84 if not check_rdcs(interatom):
85 continue
86
87
88 for align_id in cdp.rdc_ids:
89
90 yield ['rdc', interatom.spin_id1, interatom.spin_id2, align_id]
91
92
93 for spin, spin_id in spin_loop(selection=domain_moving(), return_id=True):
94
95 if not spin.select:
96 continue
97
98
99 if not hasattr(spin, 'pcs'):
100 continue
101
102
103 for align_id in cdp.pcs_ids:
104
105 yield ['pcs', spin_id, align_id]
106
107
108 - def calculate(self, spin_id=None, scaling_matrix=None, verbosity=1, sim_index=None):
109 """Calculate the chi-squared value for the current parameter values.
110
111 @keyword spin_id: The spin identification string (unused).
112 @type spin_id: None
113 @keyword scaling_matrix: The per-model list of diagonal and square scaling matrices.
114 @type scaling_matrix: list of numpy rank-2, float64 array or list of None
115 @keyword verbosity: The amount of information to print. The higher the value, the greater the verbosity.
116 @type verbosity: int
117 @keyword sim_index: The optional MC simulation index (unused).
118 @type sim_index: None or int
119 """
120
121
122 model, param_vector = target_fn_setup(sim_index=sim_index, verbosity=verbosity, scaling_matrix=scaling_matrix[0])
123
124
125 chi2 = model.func(param_vector)
126
127
128 cdp.chi2 = chi2
129
130
131 store_bc_data(model)
132
133
134 print("Chi2: %s" % chi2)
135
136
138 """Return the 'Log barrier' optimisation constraint algorithm.
139
140 @return: The 'Log barrier' constraint algorithm.
141 @rtype: str
142 """
143
144
145 return 'Log barrier'
146
147
149 """Create the Monte Carlo data by back calculating the RDCs or PCSs.
150
151 @keyword data_id: The data set as yielded by the base_data_loop() generator method.
152 @type data_id: list of str
153 @return: The Monte Carlo simulation data.
154 @rtype: list of floats
155 """
156
157
158 mc_data = []
159
160
161 if data_id[0] == 'rdc':
162
163 data_type, spin_id1, spin_id2, align_id = data_id
164
165
166 interatom = return_interatom(spin_id1, spin_id2)
167
168
169 if not hasattr(interatom, 'rdc_bc'):
170 self.calculate()
171
172
173 if not hasattr(interatom, 'rdc_bc') or align_id not in interatom.rdc_bc:
174 data = None
175 else:
176 data = interatom.rdc_bc[align_id]
177
178
179 mc_data.append(data)
180
181
182 elif data_id[0] == 'pcs':
183
184 data_type, spin_id, align_id = data_id
185
186
187 spin = return_spin(spin_id)
188
189
190 if not hasattr(spin, 'pcs_bc'):
191 self.calculate()
192
193
194 if not hasattr(spin, 'pcs_bc') or align_id not in spin.pcs_bc:
195 data = None
196 else:
197 data = spin.pcs_bc[align_id]
198
199
200 mc_data.append(data)
201
202
203 return mc_data
204
205
206 - def duplicate_data(self, pipe_from=None, pipe_to=None, model_info=None, global_stats=False, verbose=True):
207 """Duplicate the data specific to a single frame order data pipe.
208
209 @keyword pipe_from: The data pipe to copy the data from.
210 @type pipe_from: str
211 @keyword pipe_to: The data pipe to copy the data to.
212 @type pipe_to: str
213 @keyword model_info: The model information from model_loop(). This is unused.
214 @type model_info: None
215 @keyword global_stats: The global statistics flag.
216 @type global_stats: bool
217 @keyword verbose: Unused.
218 @type verbose: bool
219 """
220
221
222 if pipes.has_pipe(pipe_to):
223 raise RelaxError("The data pipe '%s' already exists." % pipe_to)
224
225
226 pipes.copy(pipe_from=pipe_from, pipe_to=pipe_to)
227
228
229 - def eliminate(self, name, value, args, sim=None, model_info=None):
230 """Model elimination method.
231
232 @param name: The parameter name.
233 @type name: str
234 @param value: The parameter value.
235 @type value: float
236 @param args: The elimination constant overrides.
237 @type args: None or tuple of float
238 @keyword sim: The Monte Carlo simulation index.
239 @type sim: int
240 @keyword model_info: The model information from model_loop(). This is unused.
241 @type model_info: None
242 @return: True if the model is to be eliminated, False otherwise.
243 @rtype: bool
244 """
245
246
247 text = "The %s parameter of %.5g is %s than %.5g, eliminating "
248 if sim == None:
249 text += "the model."
250 else:
251 text += "simulation %i." % sim
252
253
254 if name == 'cone_s1' and hasattr(cdp, 'cone_s1'):
255 if cdp.cone_s1 > 1.0:
256 print(text % ("cone S1 order", cdp.cone_s1, "greater", 1.0))
257 return True
258 if cdp.cone_s1 < -0.125:
259 print(text % ("cone S1 order", cdp.cone_s1, "less", -0.125))
260 return True
261
262
263 if name == 'cone_theta' and hasattr(cdp, 'cone_theta'):
264 if cdp.cone_theta >= pi:
265 print(text % ("cone opening angle theta", cdp.cone_theta, "greater", pi))
266 return True
267 if cdp.cone_theta < 0.0:
268 print(text % ("cone opening angle theta", cdp.cone_theta, "less", 0))
269 return True
270
271
272 if name == 'cone_theta_x' and hasattr(cdp, 'cone_theta_x'):
273 if cdp.cone_theta_x >= pi:
274 print(text % ("cone opening angle theta x", cdp.cone_theta_x, "greater", pi))
275 return True
276 if cdp.cone_theta_x < 0.001:
277 print(text % ("cone opening angle theta x", cdp.cone_theta_x, "less", 0.001))
278 return True
279 if name == 'cone_theta_y' and hasattr(cdp, 'cone_theta_y'):
280 if cdp.cone_theta_y >= pi:
281 print(text % ("cone opening angle theta y", cdp.cone_theta_y, "greater", pi))
282 return True
283 if cdp.cone_theta_y < 0.001:
284 print(text % ("cone opening angle theta y", cdp.cone_theta_y, "less", 0.001))
285 return True
286
287
288 if name == 'cone_sigma_max' and hasattr(cdp, 'cone_sigma_max'):
289 if cdp.cone_sigma_max >= pi:
290 print(text % ("torsion angle sigma_max", cdp.cone_sigma_max, "greater", pi))
291 return True
292 if cdp.cone_sigma_max < 0.0:
293 print(text % ("torsion angle sigma_max", cdp.cone_sigma_max, "less", 0.0))
294 return True
295
296
297 return False
298
299
301 """Return a vector of parameter names.
302
303 @keyword model_info: The model information from model_loop(). This is unused.
304 @type model_info: None
305 @return: The vector of parameter names.
306 @rtype: list of str
307 """
308
309
310 update_model()
311
312
313 return cdp.params
314
315
317 """Return a vector of parameter values.
318
319 @keyword model_info: The model information from model_loop(). This is unused.
320 @type model_info: None
321 @keyword sim_index: The Monte Carlo simulation index.
322 @type sim_index: int
323 @return: The vector of parameter values.
324 @rtype: list of str
325 """
326
327
328 return assemble_param_vector(sim_index=sim_index)
329
330
331 - def grid_search(self, lower=None, upper=None, inc=None, scaling_matrix=None, constraints=False, verbosity=0, sim_index=None):
332 """Perform a grid search.
333
334 @keyword lower: The per-model lower bounds of the grid search which must be equal to the number of parameters in the model.
335 @type lower: list of lists of numbers
336 @keyword upper: The per-model upper bounds of the grid search which must be equal to the number of parameters in the model.
337 @type upper: list of lists of numbers
338 @keyword inc: The per-model increments for each dimension of the space for the grid search. The number of elements in the array must equal to the number of parameters in the model.
339 @type inc: list of lists of int
340 @keyword scaling_matrix: The per-model list of diagonal and square scaling matrices.
341 @type scaling_matrix: list of numpy rank-2, float64 array or list of None
342 @keyword constraints: If True, constraints are applied during the grid search (eliminating parts of the grid). If False, no constraints are used.
343 @type constraints: bool
344 @keyword verbosity: A flag specifying the amount of information to print. The higher the value, the greater the verbosity.
345 @type verbosity: int
346 @keyword sim_index: The Monte Carlo simulation index.
347 @type sim_index: None or int
348 """
349
350
351 if not hasattr(cdp, 'model'):
352 raise RelaxNoModelError('Frame Order')
353
354
355 n = param_num()
356
357
358 lower = lower[0]
359 upper = upper[0]
360 inc = inc[0]
361
362
363 grid = []
364 """This structure is a list of lists. The first dimension corresponds to the model
365 parameter. The second dimension are the grid node positions."""
366
367
368 for i in range(n):
369
370 if inc[i] == None:
371 grid.append(None)
372 continue
373
374
375 dist_type = None
376 end_point = True
377
378
379 if cdp.params[i] in ['ave_pos_beta', 'eigen_beta', 'axis_theta']:
380
381 if not isinstance(inc, list):
382 inc[i] = int(inc[i] / 2) + 1
383
384
385 dist_type = 'acos'
386 end_point = False
387
388
389 row = grid_row(inc[i], lower, upper, dist_type=dist_type, end_point=end_point)
390 grid.append(row)
391
392
393 if not end_point:
394 inc[i] -= 1
395
396
397 total_pts = 1
398 for i in range(n):
399
400 if grid[i] == None:
401 continue
402
403 total_pts = total_pts * len(grid[i])
404
405
406 max_pts = 50e6
407 if total_pts > max_pts:
408 raise RelaxError("The total number of grid points '%s' exceeds the maximum of '%s'." % (total_pts, int(max_pts)))
409
410
411 pts = zeros((total_pts, n), float64)
412 indices = zeros(n, int)
413 for i in range(total_pts):
414
415 for j in range(n):
416
417 if grid[j] == None:
418
419 pts[i, j] = getattr(cdp, cdp.params[j]) / scaling_matrix[0][j, j]
420
421
422 else:
423 pts[i, j] = grid[j][indices[j]] / scaling_matrix[0][j, j]
424
425
426 for j in range(n):
427 if inc[j] != None and indices[j] < inc[j]-1:
428 indices[j] += 1
429 break
430 else:
431 indices[j] = 0
432
433
434 self.minimise(min_algor='grid', min_options=pts, scaling_matrix=scaling_matrix, constraints=constraints, verbosity=verbosity, sim_index=sim_index)
435
436
438 """Create bounds for the OpenDX mapping function.
439
440 @param param: The name of the parameter to return the lower and upper bounds of.
441 @type param: str
442 @param spin_id: The spin identification string (unused).
443 @type spin_id: None
444 @return: The upper and lower bounds of the parameter.
445 @rtype: list of float
446 """
447
448
449 if param in ['ave_pos_x', 'ave_pos_y', 'ave_pos_z']:
450 return [-100.0, 100]
451 if param in ['ave_pos_alpha', 'ave_pos_beta', 'ave_pos_gamma']:
452 return [0.0, 2*pi]
453
454
455 if param == 'axis_theta':
456 return [0.0, pi]
457
458
459 if param == 'axis_phi':
460 return [0.0, 2*pi]
461
462
463 if param == 'axis_alpha':
464 return [0.0, 2*pi]
465
466
467 if param == 'cone_theta':
468 return [0.0, pi]
469
470
471 - def minimise(self, min_algor=None, min_options=None, func_tol=None, grad_tol=None, max_iterations=None, constraints=False, scaling_matrix=None, verbosity=0, sim_index=None, lower=None, upper=None, inc=None):
472 """Minimisation function.
473
474 @param min_algor: The minimisation algorithm to use.
475 @type min_algor: str
476 @param min_options: An array of options to be used by the minimisation algorithm.
477 @type min_options: array of str
478 @param func_tol: The function tolerance which, when reached, terminates optimisation. Setting this to None turns of the check.
479 @type func_tol: None or float
480 @param grad_tol: The gradient tolerance which, when reached, terminates optimisation. Setting this to None turns of the check.
481 @type grad_tol: None or float
482 @param max_iterations: The maximum number of iterations for the algorithm.
483 @type max_iterations: int
484 @param constraints: If True, constraints are used during optimisation.
485 @type constraints: bool
486 @keyword scaling_matrix: The per-model list of diagonal and square scaling matrices.
487 @type scaling_matrix: list of numpy rank-2, float64 array or list of None
488 @param verbosity: A flag specifying the amount of information to print. The higher the value, the greater the verbosity.
489 @type verbosity: int
490 @param sim_index: The index of the simulation to optimise. This should be None if normal optimisation is desired.
491 @type sim_index: None or int
492 @keyword lower: The per-model lower bounds of the grid search which must be equal to the number of parameters in the model. This optional argument is only used when doing a grid search.
493 @type lower: list of lists of numbers
494 @keyword upper: The per-model upper bounds of the grid search which must be equal to the number of parameters in the model. This optional argument is only used when doing a grid search.
495 @type upper: list of lists of numbers
496 @keyword inc: The per-model increments for each dimension of the space for the grid search. The number of elements in the array must equal to the number of parameters in the model. This argument is only used when doing a grid search.
497 @type inc: list of lists of int
498 """
499
500
501 model, param_vector = target_fn_setup(sim_index=sim_index, verbosity=verbosity, scaling_matrix=scaling_matrix[0])
502
503
504 A, b = None, None
505 if constraints:
506 A, b = linear_constraints(scaling_matrix=scaling_matrix[0])
507
508
509 if search('^[Gg]rid', min_algor):
510 results = grid_point_array(func=model.func, args=(), points=min_options, verbosity=verbosity)
511
512
513 else:
514 results = generic_minimise(func=model.func, args=(), x0=param_vector, min_algor=min_algor, min_options=min_options, func_tol=func_tol, grad_tol=grad_tol, maxiter=max_iterations, A=A, b=b, full_output=True, print_flag=verbosity)
515
516
517 unpack_opt_results(results, scaling_matrix[0], sim_index)
518
519
520 store_bc_data(model)
521
522
524 """Return a description of the model.
525
526 @keyword model_info: The model information from model_loop(). This is unused.
527 @type model_info: None
528 @return: The model description.
529 @rtype: str
530 """
531
532 return ""
533
534
536 """Return the k, n, and chi2 model statistics.
537
538 k - number of parameters.
539 n - number of data points.
540 chi2 - the chi-squared value.
541
542
543 @keyword model_info: The model information from model_loop(). This is unused.
544 @type model_info: None
545 @keyword spin_id: Unused.
546 @type spin_id: None
547 @keyword global_stats: Unused.
548 @type global_stats: None
549 @return: The optimisation statistics, in tuple format, of the number of parameters (k), the number of data points (n), and the chi-squared value (chi2).
550 @rtype: tuple of (int, int, float)
551 """
552
553
554 k = len(cdp.params)
555
556
557 n = 0
558 for data in self.base_data_loop():
559 n += 1
560
561
562 if not hasattr(cdp, 'chi2'):
563 raise RelaxError("Statistics are not available, most likely because the model has not been optimised.")
564
565
566 return k, n, cdp.chi2
567
568
570 """Return the RDC or PCS error structure.
571
572 @param data_id: The data set as yielded by the base_data_loop() generator method.
573 @type data_id: list of str
574 @return: The array of RDC or PCS error values.
575 @rtype: list of float
576 """
577
578
579 mc_errors = []
580
581
582 if data_id[0] == 'rdc':
583
584 data_type, spin_id1, spin_id2, align_id = data_id
585
586
587 interatom = return_interatom(spin_id1, spin_id2)
588
589
590 if not hasattr(interatom, 'rdc_err'):
591 raise RelaxError("The RDC errors are missing for interatomic data container between spins '%s' and '%s'." % (spin_id1, spin_id2))
592
593
594 if align_id not in interatom.rdc_err:
595 mc_errors.append(None)
596
597
598 else:
599 mc_errors.append(interatom.rdc_err[align_id])
600
601
602 elif data_id[0] == 'pcs':
603
604 data_type, spin_id, align_id = data_id
605
606
607 spin = return_spin(spin_id)
608
609
610 if not hasattr(spin, 'pcs_err'):
611 raise RelaxError("The PCS errors are missing for spin '%s'." % spin_id)
612
613
614 if align_id not in spin.pcs_err:
615 mc_errors.append(None)
616
617
618 else:
619 mc_errors.append(spin.pcs_err[align_id])
620
621
622 return mc_errors
623
624
625 - def set_error(self, index, error, model_info=None):
626 """Set the parameter errors.
627
628 @param index: The index of the parameter to set the errors for.
629 @type index: int
630 @param error: The error value.
631 @type error: float
632 @keyword model_info: The model information from model_loop(). This is unused.
633 @type model_info: None
634 """
635
636
637 inc = 0
638
639
640 for param in self.data_names(set='params'):
641
642 if param not in cdp.params:
643 continue
644
645
646 if index == inc:
647 setattr(cdp, param + "_err", error)
648
649
650 inc = inc + 1
651
652
653 if cdp.model == 'iso cone, free rotor' and inc == index:
654 setattr(cdp, 'cone_theta_err', error)
655
656
658 """Set the simulation selection flag for the spin.
659
660 @param select_sim: The selection flag for the simulations.
661 @type select_sim: bool
662 @keyword model_info: The model information from model_loop(). This is unused.
663 @type model_info: None
664 """
665
666
667 cdp.select_sim = deepcopy(select_sim)
668
669
671 """Initialise the Monte Carlo parameter values."""
672
673
674 param_names = self.data_names(set='params')
675
676
677 model_params = deepcopy(cdp.params)
678
679
680 if cdp.model == 'iso cone, free rotor':
681 param_names.append('cone_theta')
682 model_params.append('cone_theta')
683
684
685 min_names = self.data_names(set='min')
686
687
688
689
690
691
692 for object_name in param_names:
693
694 if object_name not in model_params:
695 continue
696
697
698 sim_object_name = object_name + '_sim'
699
700
701 if hasattr(cdp, sim_object_name):
702 raise RelaxError("Monte Carlo parameter values have already been set.")
703
704
705
706
707
708
709 for object_name in param_names:
710
711 if object_name not in model_params:
712 continue
713
714
715 sim_object_name = object_name + '_sim'
716
717
718 setattr(cdp, sim_object_name, [])
719
720
721 sim_object = getattr(cdp, sim_object_name)
722
723
724 for j in range(cdp.sim_number):
725
726 sim_object.append(deepcopy(getattr(cdp, object_name)))
727
728
729 for object_name in min_names:
730
731 sim_object_name = object_name + '_sim'
732
733
734 setattr(cdp, sim_object_name, [])
735
736
737 sim_object = getattr(cdp, sim_object_name)
738
739
740 for j in range(cdp.sim_number):
741
742 sim_object.append(deepcopy(getattr(cdp, object_name)))
743
744
746 """Pack the Monte Carlo simulation data.
747
748 @param data_id: The data set as yielded by the base_data_loop() generator method.
749 @type data_id: list of str
750 @param sim_data: The Monte Carlo simulation data.
751 @type sim_data: list of float
752 """
753
754
755 if data_id[0] == 'rdc':
756
757 data_type, spin_id1, spin_id2, align_id = data_id
758
759
760 interatom = return_interatom(spin_id1, spin_id2)
761
762
763 if not hasattr(interatom, 'rdc_sim'):
764 interatom.rdc_sim = {}
765
766
767 interatom.rdc_sim[align_id] = []
768 for i in range(cdp.sim_number):
769 interatom.rdc_sim[align_id].append(sim_data[i][0])
770
771
772 elif data_id[0] == 'pcs':
773
774 data_type, spin_id, align_id = data_id
775
776
777 spin = return_spin(spin_id)
778
779
780 if not hasattr(spin, 'pcs_sim'):
781 spin.pcs_sim = {}
782
783
784 spin.pcs_sim[data_id[2]] = []
785 for i in range(cdp.sim_number):
786 spin.pcs_sim[data_id[2]].append(sim_data[i][0])
787
788
790 """Return the array of simulation parameter values.
791
792 @param index: The index of the parameter to return the array of values for.
793 @type index: int
794 @keyword model_info: The model information from model_loop(). This is unused.
795 @type model_info: None
796 @return: The array of simulation parameter values.
797 @rtype: list of float
798 """
799
800
801 inc = 0
802
803
804 param_names = self.data_names(set='params')
805
806
807 for param in param_names:
808
809 if param not in cdp.params:
810 continue
811
812
813 if index == inc:
814 return getattr(cdp, param + "_sim")
815
816
817 inc = inc + 1
818
819
820 if cdp.model == 'iso cone, free rotor' and inc == index:
821 return getattr(cdp, 'cone_theta_sim')
822
823
825 """Return the array of selected simulation flags for the spin.
826
827 @keyword model_info: The model information from model_loop(). This is unused.
828 @type model_info: None
829 @return: The array of selected simulation flags.
830 @rtype: list of int
831 """
832
833
834 return cdp.select_sim
835