1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """The relaxation curve fitting specific code."""
25
26
27 from minfx.generic import generic_minimise
28 from minfx.grid import grid
29 from numpy import array, average, dot, float64, identity, zeros
30 from numpy.linalg import inv
31 from re import match, search
32 from warnings import warn
33
34
35 from api_base import API_base
36 from api_common import API_common
37 from dep_check import C_module_exp_fn
38 from generic_fns import pipes
39 from generic_fns.mol_res_spin import exists_mol_res_spin_data, generate_spin_id, return_spin, spin_loop
40 from relax_errors import RelaxError, RelaxFuncSetupError, RelaxLenError, RelaxNoModelError, RelaxNoSequenceError
41 from relax_warnings import RelaxDeselectWarning
42 from user_functions.data import Uf_tables; uf_tables = Uf_tables()
43 from user_functions.objects import Desc_container
44
45
46 if C_module_exp_fn:
47 from maths_fns.relax_fit import setup, func, dfunc, d2func, back_calc_I
48
49
51 """Class containing functions for relaxation curve fitting."""
52
54 """Initialise the class by placing API_common methods into the API."""
55
56
57 super(Relax_fit, self).__init__()
58
59
60 self.base_data_loop = self._base_data_loop_spin
61 self.model_loop = self._model_loop_spin
62 self.return_conversion_factor = self._return_no_conversion_factor
63 self.return_value = self._return_value_general
64 self.set_error = self._set_error_spin
65 self.set_param_values = self._set_param_values_spin
66 self.set_selected_sim = self._set_selected_sim_spin
67 self.sim_init_values = self._sim_init_values_spin
68 self.sim_return_param = self._sim_return_param_spin
69 self.sim_return_selected = self._sim_return_selected_spin
70
71
72 self.PARAMS.add('intensities', scope='spin', py_type=list, grace_string='\\qPeak intensities\\Q')
73 self.PARAMS.add('relax_times', scope='spin', py_type=list, grace_string='\\qRelaxation time period (s)\\Q')
74 self.PARAMS.add('rx', scope='spin', default=8.0, desc='Either the R1 or R2 relaxation rate', set='params', py_type=float, grace_string='\\qR\\sx\\Q', err=True, sim=True)
75 self.PARAMS.add('i0', scope='spin', default=10000.0, desc='The initial intensity', py_type=float, set='params', grace_string='\\qI\\s0\\Q', err=True, sim=True)
76 self.PARAMS.add('iinf', scope='spin', default=0.0, desc='The intensity at infinity', py_type=float, set='params', grace_string='\\qI\\sinf\\Q', err=True, sim=True)
77 self.PARAMS.add('params', scope='spin', desc='The model parameters', py_type=list)
78
79
80 self.PARAMS.add_min_data(min_stats_global=False, min_stats_spin=True)
81
82
84 """Assemble the exponential curve parameter vector (as a numpy array).
85
86 @keyword spin: The spin data container.
87 @type spin: SpinContainer instance
88 @keyword sim_index: The optional MC simulation index.
89 @type sim_index: int
90 @return: An array of the parameter values of the exponential model.
91 @rtype: numpy array
92 """
93
94
95 param_vector = []
96
97
98 for i in xrange(len(spin.params)):
99
100 if spin.params[i] == 'Rx':
101 if sim_index != None:
102 param_vector.append(spin.rx_sim[sim_index])
103 elif spin.rx == None:
104 param_vector.append(0.0)
105 else:
106 param_vector.append(spin.rx)
107
108
109 elif spin.params[i] == 'I0':
110 if sim_index != None:
111 param_vector.append(spin.i0_sim[sim_index])
112 elif spin.i0 == None:
113 param_vector.append(0.0)
114 else:
115 param_vector.append(spin.i0)
116
117
118 elif spin.params[i] == 'Iinf':
119 if sim_index != None:
120 param_vector.append(spin.iinf_sim[sim_index])
121 elif spin.iinf == None:
122 param_vector.append(0.0)
123 else:
124 param_vector.append(spin.iinf)
125
126
127 return array(param_vector, float64)
128
129
131 """Create and return the scaling matrix.
132
133 @keyword spin: The spin data container.
134 @type spin: SpinContainer instance
135 @keyword scaling: A flag which if false will cause the identity matrix to be returned.
136 @type scaling: bool
137 @return: The diagonal and square scaling matrix.
138 @rtype: numpy diagonal matrix
139 """
140
141
142 scaling_matrix = identity(len(spin.params), float64)
143 i = 0
144
145
146 if not scaling:
147 return scaling_matrix
148
149
150 for i in xrange(len(spin.params)):
151
152 if spin.params[i] == 'Rx':
153 pass
154
155
156 elif search('^i', spin.params[i]):
157
158 pos = cdp.relax_times.index(min(cdp.relax_times))
159
160
161 scaling_matrix[i, i] = 1.0 / average(spin.intensities[pos])
162
163
164 i = i + 1
165
166
167 return scaling_matrix
168
169
170 - def _back_calc(self, spin=None, relax_time_id=None):
171 """Back-calculation of peak intensity for the given relaxation time.
172
173 @keyword spin: The spin container.
174 @type spin: SpinContainer instance
175 @keyword relax_time_id: The ID string for the desired relaxation time.
176 @type relax_time_id: str
177 @return: The peak intensity for the desired relaxation time.
178 @rtype: float
179 """
180
181
182 param_vector = self._assemble_param_vector(spin=spin)
183
184
185 scaling_matrix = self._assemble_scaling_matrix(spin=spin, scaling=False)
186
187
188 keys = spin.intensities.keys()
189
190
191 values = []
192 errors = []
193 times = []
194 for key in keys:
195 values.append(spin.intensities[key])
196 errors.append(spin.intensity_err[key])
197 times.append(cdp.relax_times[key])
198
199
200 setup(num_params=len(spin.params), num_times=len(cdp.relax_times), values=values, sd=errors, relax_times=times, scaling_matrix=scaling_matrix)
201
202
203 self._func(param_vector)
204
205
206 results = back_calc_I()
207
208
209 return results[keys.index(relax_time_id)]
210
211
213 """Disassemble the parameter vector.
214
215 @keyword param_vector: The parameter vector.
216 @type param_vector: numpy array
217 @keyword spin: The spin data container.
218 @type spin: SpinContainer instance
219 @keyword sim_index: The optional MC simulation index.
220 @type sim_index: int
221 """
222
223
224 if sim_index != None:
225
226 spin.rx_sim[sim_index] = param_vector[0]
227
228
229 spin.i0_sim[sim_index] = param_vector[1]
230
231
232 if cdp.curve_type == 'inv':
233 spin.iinf_sim[sim_index] = param_vector[2]
234
235
236 else:
237
238 spin.rx = param_vector[0]
239
240
241 spin.i0 = param_vector[1]
242
243
244 if cdp.curve_type == 'inv':
245 spin.iinf = param_vector[2]
246
247
248 - def _func(self, params):
249 """Wrapper function for the C module, for converting numpy arrays.
250
251 @param params: The parameter array from the minimisation code.
252 @type params: numpy array
253 @return: The function value generated by the C module.
254 @rtype: float
255 """
256
257
258 chi2 = func(params.tolist())
259
260
261 return chi2
262
263
265 """Wrapper function for the C module, for converting numpy arrays.
266
267 The currently does nothing.
268 """
269
270
272 """Wrapper function for the C module, for converting numpy arrays.
273
274 The currently does nothing.
275 """
276
277
278 - def _grid_search_setup(self, spin=None, param_vector=None, lower=None, upper=None, inc=None, scaling_matrix=None):
279 """The grid search setup function.
280
281 @keyword spin: The spin data container.
282 @type spin: SpinContainer instance
283 @keyword param_vector: The parameter vector.
284 @type param_vector: numpy array
285 @keyword lower: The lower bounds of the grid search which must be equal to the
286 number of parameters in the model. This optional argument is
287 only used when doing a grid search.
288 @type lower: array of numbers
289 @keyword upper: The upper bounds of the grid search which must be equal to the
290 number of parameters in the model. This optional argument is
291 only used when doing a grid search.
292 @type upper: array of numbers
293 @keyword inc: The increments for each dimension of the space for the grid
294 search. The number of elements in the array must equal to the
295 number of parameters in the model. This argument is only used
296 when doing a grid search.
297 @type inc: array of int
298 @keyword scaling_matrix: The scaling matrix.
299 @type scaling_matrix: numpy diagonal matrix
300 @return: A tuple of the grid size and the minimisation options. For the
301 minimisation options, the first dimension corresponds to the
302 model parameter. The second dimension is a list of the number
303 of increments, the lower bound, and upper bound.
304 @rtype: (int, list of lists [int, float, float])
305 """
306
307
308 n = len(param_vector)
309
310
311 if n == 0:
312 raise RelaxError("Cannot run a grid search on a model with zero parameters.")
313
314
315 if lower != None and len(lower) != n:
316 raise RelaxLenError('lower bounds', n)
317
318
319 if upper != None and len(upper) != n:
320 raise RelaxLenError('upper bounds', n)
321
322
323 if isinstance(inc, list) and len(inc) != n:
324 raise RelaxLenError('increment', n)
325 elif isinstance(inc, int):
326 inc = [inc]*n
327
328
329 if not lower:
330
331 lower = []
332 upper = []
333
334
335 for i in range(n):
336
337 if spin.params[i] == 'Rx':
338 lower.append(0.0)
339 upper.append(20.0)
340
341
342 elif search('^I', spin.params[i]):
343
344 min_time = min(cdp.relax_times.values())
345 for key in cdp.relax_times.keys():
346 if cdp.relax_times[key] == min_time:
347 id = key
348 break
349
350
351 lower.append(0.0)
352 upper.append(average(spin.intensities[id]))
353
354
355 for i in range(n):
356 lower[i] = lower[i] / scaling_matrix[i, i]
357 upper[i] = upper[i] / scaling_matrix[i, i]
358
359 return inc, lower, upper
360
361
363 """Set up the relaxation curve fitting linear constraint matrices A and b.
364
365 Standard notation
366 =================
367
368 The relaxation rate constraints are::
369
370 Rx >= 0
371
372 The intensity constraints are::
373
374 I0 >= 0
375 Iinf >= 0
376
377
378 Matrix notation
379 ===============
380
381 In the notation A.x >= b, where A is an matrix of coefficients, x is an array of parameter
382 values, and b is a vector of scalars, these inequality constraints are::
383
384 | 1 0 0 | | Rx | | 0 |
385 | | | | | |
386 | 1 0 0 | . | I0 | >= | 0 |
387 | | | | | |
388 | 1 0 0 | | Iinf | | 0 |
389
390
391 @keyword spin: The spin data container.
392 @type spin: SpinContainer instance
393 @keyword scaling_matrix: The diagonal, square scaling matrix.
394 @type scaling_matrix: numpy diagonal matrix
395 """
396
397
398 A = []
399 b = []
400 n = len(spin.params)
401 zero_array = zeros(n, float64)
402 i = 0
403 j = 0
404
405
406 for k in xrange(len(spin.params)):
407
408 if spin.params[k] == 'Rx':
409
410 A.append(zero_array * 0.0)
411 A[j][i] = 1.0
412 b.append(0.0)
413 j = j + 1
414
415
416 elif search('^I', spin.params[k]):
417
418 A.append(zero_array * 0.0)
419 A[j][i] = 1.0
420 b.append(0.0)
421 j = j + 1
422
423
424 i = i + 1
425
426
427 A = array(A, float64)
428 b = array(b, float64)
429
430 return A, b
431
432
434 """Update various model specific data structures.
435
436 @param model: The exponential curve type.
437 @type model: str
438 @param params: A list consisting of the model parameters.
439 @type params: list of str
440 """
441
442
443 cdp.curve_type = model
444
445
446 for spin in spin_loop():
447
448 if not spin.select:
449 continue
450
451
452 self.data_init(spin)
453
454
455 spin.model = model
456 spin.params = params
457
458
460 """Set the relaxation time period associated with a given spectrum.
461
462 @keyword time: The time, in seconds, of the relaxation period.
463 @type time: float
464 @keyword spectrum_id: The spectrum identification string.
465 @type spectrum_id: str
466 """
467
468
469 if spectrum_id not in cdp.spectrum_ids:
470 raise RelaxError("The peak heights corresponding to spectrum id '%s' have not been loaded." % spectrum_id)
471
472
473 if not hasattr(cdp, 'relax_times'):
474 cdp.relax_times = {}
475
476
477 cdp.relax_times[spectrum_id] = time
478
479
481 """Function for selecting the model of the exponential curve.
482
483 @keyword model: The exponential curve type. Can be one of 'exp' or 'inv'.
484 @type model: str
485 """
486
487
488 pipes.test()
489
490
491 function_type = cdp.pipe_type
492 if function_type != 'relax_fit':
493 raise RelaxFuncSetupError(specific_setup.get_string(function_type))
494
495
496 if not exists_mol_res_spin_data():
497 raise RelaxNoSequenceError
498
499
500 if model == 'exp':
501 print("Two parameter exponential fit.")
502 params = ['Rx', 'I0']
503
504
505 elif model == 'inv':
506 print("Three parameter inversion recovery fit.")
507 params = ['Rx', 'I0', 'Iinf']
508
509
510 else:
511 raise RelaxError("The model '" + model + "' is invalid.")
512
513
514 self._model_setup(model, params)
515
516
518 """Create the Monte Carlo peak intensity data.
519
520 @keyword data_id: The spin identification string, as yielded by the base_data_loop() generator method.
521 @type data_id: str
522 @return: The Monte Carlo simulation data.
523 @rtype: list of floats
524 """
525
526
527 mc_data = {}
528
529
530 spin = return_spin(data_id)
531
532
533 if not spin.select:
534 return
535
536
537 if not hasattr(spin, 'intensities'):
538 return
539
540
541 if not hasattr(spin, 'model') or not spin.model:
542 raise RelaxNoModelError
543
544
545 for id in cdp.relax_times.keys():
546
547 value = self._back_calc(spin=spin, relax_time_id=id)
548
549
550 mc_data[id] = value
551
552
553 return mc_data
554
555
557 """Initialise the spin specific data structures.
558
559 @param data_cont: The spin container.
560 @type data_cont: SpinContainer instance
561 @keyword sim: The Monte Carlo simulation flag, which if true will initialise the simulation data structure.
562 @type sim: bool
563 """
564
565
566 for name in self.data_names(set='params'):
567
568 list_data = [ 'params' ]
569 if name in list_data:
570 init_data = []
571
572
573 else:
574 init_data = None
575
576
577 if not hasattr(data_cont, name):
578 setattr(data_cont, name, init_data)
579
580
581 default_value_doc = Desc_container("Relaxation curve fitting default values")
582 default_value_doc.add_paragraph("These values are completely arbitrary as peak heights (or volumes) are extremely variable and the Rx value is a compensation for both the R1 and R2 values.")
583 _table = uf_tables.add_table(label="table: curve-fit default values", caption="Relaxation curve fitting default values.")
584 _table.add_headings(["Data type", "Object name", "Value"])
585 _table.add_row(["Relaxation rate", "'rx'", "8.0"])
586 _table.add_row(["Initial intensity", "'i0'", "10000.0"])
587 _table.add_row(["Intensity at infinity", "'iinf'", "0.0"])
588 default_value_doc.add_table(_table.label)
589
590
591 - def grid_search(self, lower=None, upper=None, inc=None, constraints=True, verbosity=1, sim_index=None):
592 """The exponential curve fitting grid search method.
593
594 @keyword lower: The lower bounds of the grid search which must be equal to the number of parameters in the model.
595 @type lower: array of numbers
596 @keyword upper: The upper bounds of the grid search which must be equal to the number of parameters in the model.
597 @type upper: array of numbers
598 @keyword inc: The 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.
599 @type inc: array of int
600 @keyword constraints: If True, constraints are applied during the grid search (eliminating parts of the grid). If False, no constraints are used.
601 @type constraints: bool
602 @keyword verbosity: A flag specifying the amount of information to print. The higher the value, the greater the verbosity.
603 @type verbosity: int
604 @keyword sim_index: The index of the simulation to apply the grid search to. If None, the normal model is optimised.
605 @type sim_index: int
606 """
607
608
609 self.minimise(min_algor='grid', lower=lower, upper=upper, inc=inc, constraints=constraints, verbosity=verbosity, sim_index=sim_index)
610
611
612 - def minimise(self, min_algor=None, min_options=None, func_tol=None, grad_tol=None, max_iterations=None, constraints=False, scaling=True, verbosity=0, sim_index=None, lower=None, upper=None, inc=None):
613 """Relaxation curve fitting minimisation method.
614
615 @keyword min_algor: The minimisation algorithm to use.
616 @type min_algor: str
617 @keyword min_options: An array of options to be used by the minimisation algorithm.
618 @type min_options: array of str
619 @keyword func_tol: The function tolerance which, when reached, terminates optimisation. Setting this to None turns of the check.
620 @type func_tol: None or float
621 @keyword grad_tol: The gradient tolerance which, when reached, terminates optimisation. Setting this to None turns of the check.
622 @type grad_tol: None or float
623 @keyword max_iterations: The maximum number of iterations for the algorithm.
624 @type max_iterations: int
625 @keyword constraints: If True, constraints are used during optimisation.
626 @type constraints: bool
627 @keyword scaling: If True, diagonal scaling is enabled during optimisation to allow the problem to be better conditioned.
628 @type scaling: bool
629 @keyword verbosity: The amount of information to print. The higher the value, the greater the verbosity.
630 @type verbosity: int
631 @keyword sim_index: The index of the simulation to optimise. This should be None if normal optimisation is desired.
632 @type sim_index: None or int
633 @keyword lower: The 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.
634 @type lower: array of numbers
635 @keyword upper: The 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.
636 @type upper: array of numbers
637 @keyword inc: The 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.
638 @type inc: array of int
639 """
640
641
642 if not exists_mol_res_spin_data():
643 raise RelaxNoSequenceError
644
645
646 for spin, mol_name, res_num, res_name in spin_loop(full_info=True):
647
648 if not spin.select:
649 continue
650
651
652 if not hasattr(spin, 'intensities'):
653 continue
654
655
656 param_vector = self._assemble_param_vector(spin=spin)
657
658
659 scaling_matrix = self._assemble_scaling_matrix(spin=spin, scaling=scaling)
660 if len(scaling_matrix):
661 param_vector = dot(inv(scaling_matrix), param_vector)
662
663
664 if match('^[Gg]rid', min_algor):
665 inc, lower, upper = self._grid_search_setup(spin=spin, param_vector=param_vector, lower=lower, upper=upper, inc=inc, scaling_matrix=scaling_matrix)
666
667
668 if constraints:
669 A, b = self._linear_constraints(spin=spin, scaling_matrix=scaling_matrix)
670 else:
671 A, b = None, None
672
673
674 if verbosity >= 1:
675
676 spin_id = generate_spin_id(mol_name, res_num, res_name, spin.num, spin.name)
677
678
679 if verbosity >= 2:
680 print("\n\n")
681
682 string = "Fitting to spin " + repr(spin_id)
683 print(("\n\n" + string))
684 print((len(string) * '~'))
685
686
687
688
689
690
691 keys = spin.intensities.keys()
692
693
694 values = []
695 errors = []
696 times = []
697 for key in keys:
698
699 if sim_index == None:
700 values.append(spin.intensities[key])
701 else:
702 values.append(spin.sim_intensities[sim_index][key])
703
704
705 errors.append(spin.intensity_err[key])
706
707
708 times.append(cdp.relax_times[key])
709
710 setup(num_params=len(spin.params), num_times=len(cdp.relax_times), values=values, sd=errors, relax_times=times, scaling_matrix=scaling_matrix.tolist())
711
712
713
714
715
716 if constraints and not match('^[Gg]rid', min_algor):
717 algor = min_options[0]
718 else:
719 algor = min_algor
720
721
722
723
724
725 if match('[Ll][Mm]$', algor) or match('[Ll]evenburg-[Mm]arquardt$', algor):
726
727 lm_error = zeros(len(spin.relax_times), float64)
728 index = 0
729 for k in xrange(len(spin.relax_times)):
730 lm_error[index:index+len(relax_error[k])] = relax_error[k]
731 index = index + len(relax_error[k])
732
733 min_options = min_options + (self.relax_fit.lm_dri, lm_error)
734
735
736
737
738
739
740 if search('^[Gg]rid', min_algor):
741 results = grid(func=self._func, args=(), num_incs=inc, lower=lower, upper=upper, A=A, b=b, verbosity=verbosity)
742
743
744 param_vector, chi2, iter_count, warning = results
745 f_count = iter_count
746 g_count = 0.0
747 h_count = 0.0
748
749
750 else:
751 results = generic_minimise(func=self._func, dfunc=self._dfunc, d2func=self._d2func, 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)
752
753
754 if results == None:
755 return
756 param_vector, chi2, iter_count, f_count, g_count, h_count, warning = results
757
758
759 if scaling:
760 param_vector = dot(scaling_matrix, param_vector)
761
762
763 self._disassemble_param_vector(param_vector=param_vector, spin=spin, sim_index=sim_index)
764
765
766 if sim_index != None:
767
768 spin.chi2_sim[sim_index] = chi2
769
770
771 spin.iter_sim[sim_index] = iter_count
772
773
774 spin.f_count_sim[sim_index] = f_count
775
776
777 spin.g_count_sim[sim_index] = g_count
778
779
780 spin.h_count_sim[sim_index] = h_count
781
782
783 spin.warning_sim[sim_index] = warning
784
785
786
787 else:
788
789 spin.chi2 = chi2
790
791
792 spin.iter = iter_count
793
794
795 spin.f_count = f_count
796
797
798 spin.g_count = g_count
799
800
801 spin.h_count = h_count
802
803
804 spin.warning = warning
805
806
832
833
835 """Function for returning the peak intensity data structure.
836
837 @param spin: The spin container.
838 @type spin: SpinContainer instance
839 @return: The peak intensity data structure.
840 @rtype: list of float
841 """
842
843 return spin.intensities
844
845
846 return_data_name_doc = Desc_container("Relaxation curve fitting data type string matching patterns")
847 _table = uf_tables.add_table(label="table: curve-fit data type patterns", caption="Relaxation curve fitting data type string matching patterns.")
848 _table.add_headings(["Data type", "Object name"])
849 _table.add_row(["Relaxation rate", "'rx'"])
850 _table.add_row(["Peak intensities (series)", "'intensities'"])
851 _table.add_row(["Initial intensity", "'i0'"])
852 _table.add_row(["Intensity at infinity", "'iinf'"])
853 _table.add_row(["Relaxation period times (series)", "'relax_times'"])
854 return_data_name_doc.add_table(_table.label)
855
856
858 """Return the standard deviation data structure.
859
860 @param data_id: The spin identification string, as yielded by the base_data_loop() generator
861 method.
862 @type data_id: str
863 @return: The standard deviation data structure.
864 @rtype: list of float
865 """
866
867
868 spin = return_spin(data_id)
869
870
871 return spin.intensity_err
872
873
875 """Dummy method which returns None as the stats have no units.
876
877 @param param: The name of the parameter to return the units string for.
878 @type param: str
879 @return: Nothing.
880 @rtype: None
881 """
882
883
884 return None
885
886
887 set_doc = Desc_container("Relaxation curve fitting set details")
888 set_doc.add_paragraph("Only three parameters can be set, the relaxation rate (Rx), the initial intensity (I0), and the intensity at infinity (Iinf). Setting the parameter Iinf has no effect if the chosen model is that of the exponential curve which decays to zero.")
889
890
892 """Pack the Monte Carlo simulation data.
893
894 @param data_id: The spin identification string, as yielded by the base_data_loop() generator method.
895 @type data_id: str
896 @param sim_data: The Monte Carlo simulation data.
897 @type sim_data: list of float
898 """
899
900
901 spin = return_spin(data_id)
902
903
904 spin.sim_intensities = sim_data
905