1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """The N-state model or structural ensemble analysis."""
24
25
26 from copy import deepcopy
27 from minfx.generic import generic_minimise
28 from minfx.grid import grid
29 from numpy import dot, float64, zeros
30 from re import search
31 from warnings import warn
32
33
34 import lib.arg_check
35 from lib.errors import RelaxError, RelaxInfError, RelaxNaNError, RelaxNoModelError
36 from lib.float import isNaN, isInf
37 from lib.warnings import RelaxWarning
38 from pipe_control import align_tensor, pcs, rdc
39 from pipe_control.align_tensor import opt_uses_align_data, opt_uses_tensor
40 from pipe_control.interatomic import interatomic_loop
41 from pipe_control.mol_res_spin import spin_loop
42 from specific_analyses.api_base import API_base
43 from specific_analyses.api_common import API_common
44 from specific_analyses.n_state_model.data import base_data_types, calc_ave_dist, num_data_points
45 from specific_analyses.n_state_model.optimisation import minimise_bc_data, target_fn_setup
46 from specific_analyses.n_state_model.parameter_object import N_state_params
47 from specific_analyses.n_state_model.parameters import assemble_param_vector, disassemble_param_vector, linear_constraints, param_num
48 from target_functions.potential import quad_pot
49
50
52 """Class containing functions for the N-state model."""
53
54
55 instance = None
56
70
71
73 """Loop over the base data of the spins - RDCs, PCSs, and NOESY data.
74
75 This loop iterates for each data point (RDC, PCS, NOESY) for each spin or interatomic data container, returning the identification information.
76
77 @return: A list of the spin or interatomic data container, the data type ('rdc', 'pcs', 'noesy'), and the alignment ID if required.
78 @rtype: list of [SpinContainer instance, str, str] or [InteratomContainer instance, str, str]
79 """
80
81
82 for interatom in interatomic_loop():
83
84 if not interatom.select:
85 continue
86
87
88 data = [interatom, None, None]
89
90
91 if hasattr(interatom, 'rdc'):
92 data[1] = 'rdc'
93
94
95 for id in cdp.rdc_ids:
96
97 data[2] = id
98
99
100 yield data
101
102
103 if hasattr(interatom, 'noesy'):
104 data[1] = 'noesy'
105
106
107 for id in cdp.noesy_ids:
108
109 data[2] = id
110
111
112 yield data
113
114
115 for spin in spin_loop():
116
117 if not spin.select:
118 continue
119
120
121 data = [spin, None, None]
122
123
124 if hasattr(spin, 'pcs'):
125 data[1] = 'pcs'
126
127
128 for id in cdp.pcs_ids:
129
130 data[2] = id
131
132
133 yield data
134
135
136 - def calculate(self, spin_id=None, scaling_matrix=None, verbosity=1, sim_index=None):
137 """Calculation function.
138
139 Currently this function simply calculates the NOESY flat-bottom quadratic energy potential,
140 if NOE restraints are available.
141
142 @keyword spin_id: The spin identification string (unused).
143 @type spin_id: None or str
144 @keyword scaling_matrix: The per-model list of diagonal and square scaling matrices.
145 @type scaling_matrix: list of numpy rank-2, float64 array or list of None
146 @keyword verbosity: The amount of information to print. The higher the value, the greater the verbosity.
147 @type verbosity: int
148 @keyword sim_index: The MC simulation index (unused).
149 @type sim_index: None
150 """
151
152
153 if scaling_matrix != None:
154 scaling_matrix = scaling_matrix[0]
155 model, param_vector, data_types = target_fn_setup(scaling_matrix=scaling_matrix, verbosity=verbosity)
156
157
158 if model:
159
160 chi2 = model.func(param_vector)
161
162
163 cdp.chi2 = chi2
164
165
166 minimise_bc_data(model, sim_index=sim_index)
167
168
169 if 'rdc' in data_types:
170 rdc.q_factors(sim_index=sim_index, verbosity=verbosity)
171
172
173 if 'pcs' in data_types:
174 pcs.q_factors(sim_index=sim_index, verbosity=verbosity)
175
176
177 if hasattr(cdp, 'noe_restraints'):
178
179 num_restraints = len(cdp.noe_restraints)
180 dist = zeros(num_restraints, float64)
181 pot = zeros(num_restraints, float64)
182 lower = zeros(num_restraints, float64)
183 upper = zeros(num_restraints, float64)
184
185
186 for i in range(num_restraints):
187
188 lower[i] = cdp.noe_restraints[i][2]
189 upper[i] = cdp.noe_restraints[i][3]
190
191
192 dist[i] = calc_ave_dist(cdp.noe_restraints[i][0], cdp.noe_restraints[i][1], exp=-6)
193
194
195 quad_pot(dist, pot, lower, upper)
196
197
198 cdp.ave_dist = []
199 cdp.quad_pot = []
200 for i in range(num_restraints):
201 cdp.ave_dist.append([cdp.noe_restraints[i][0], cdp.noe_restraints[i][1], dist[i]])
202 cdp.quad_pot.append([cdp.noe_restraints[i][0], cdp.noe_restraints[i][1], pot[i]])
203
204
206 """Create the Monte Carlo data by back-calculation.
207
208 @keyword data_id: The list of spin ID, data type, and alignment ID, as yielded by the base_data_loop() generator method.
209 @type data_id: str
210 @return: The Monte Carlo Ri data.
211 @rtype: list of floats
212 """
213
214
215 mc_data = []
216
217
218 container = data_id[0]
219
220
221 if data_id[1] == 'rdc' and hasattr(container, 'rdc'):
222
223 if not hasattr(container, 'rdc_bc'):
224 self.calculate()
225
226
227 if not hasattr(container, 'rdc_bc') or not data_id[2] in container.rdc_bc:
228 data = None
229 else:
230 data = container.rdc_bc[data_id[2]]
231
232
233 mc_data.append(data)
234
235
236 elif data_id[1] == 'noesy' and hasattr(container, 'noesy'):
237
238 if not hasattr(container, 'noesy_bc'):
239 self.calculate()
240
241
242 mc_data.append(container.noesy_bc)
243
244
245 elif data_id[1] == 'pcs' and hasattr(container, 'pcs'):
246
247 if not hasattr(container, 'pcs_bc'):
248 self.calculate()
249
250
251 if not hasattr(container, 'pcs_bc') or not data_id[2] in container.pcs_bc:
252 data = None
253 else:
254 data = container.pcs_bc[data_id[2]]
255
256
257 mc_data.append(data)
258
259
260 return mc_data
261
262
264 """Return a vector of parameter names.
265
266 @keyword model_info: The model information from model_loop(). This is unused.
267 @type model_info: None
268 @return: The vector of parameter names.
269 @rtype: list of str
270 """
271
272
273 param_names = []
274
275
276 if opt_uses_align_data():
277 for i in range(len(cdp.align_tensors)):
278
279 if not opt_uses_tensor(cdp.align_tensors[i]):
280 continue
281
282
283 param_names += ['Axx', 'Ayy', 'Axy', 'Axz', 'Ayz']
284
285
286 if cdp.model in ['2-domain', 'population']:
287 for i in range(cdp.N - 1):
288 param_names.append('probs')
289
290
291 if cdp.model == '2-domain':
292 for i in range(cdp.N):
293 param_names.append('alpha')
294 param_names.append('beta')
295 param_names.append('gamma')
296
297
298 if hasattr(cdp, 'paramag_centre_fixed') and not cdp.paramag_centre_fixed:
299 for i in range(3):
300 param_names.append('paramagnetic_centre')
301
302
303 return param_names
304
305
307 """Return a vector of parameter values.
308
309 @keyword model_info: The model information from model_loop(). This is unused.
310 @type model_info: None
311 @keyword sim_index: The optional Monte Carlo simulation index.
312 @type sim_index: int
313 @return: The vector of parameter values.
314 @rtype: list of str
315 """
316
317
318 return assemble_param_vector(sim_index=sim_index)
319
320
321 - def grid_search(self, lower=None, upper=None, inc=None, scaling_matrix=None, constraints=False, verbosity=0, sim_index=None):
322 """The grid search function.
323
324 @param lower: The lower bounds of the grid search which must be equal to the number of parameters in the model.
325 @type lower: list of lists of floats
326 @param upper: The upper bounds of the grid search which must be equal to the number of parameters in the model.
327 @type upper: list of lists of floats
328 @param 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.
329 @type inc: list of lists of int
330 @keyword scaling_matrix: The per-model list of diagonal and square scaling matrices.
331 @type scaling_matrix: list of numpy rank-2, float64 array or list of None
332 @param constraints: If True, constraints are applied during the grid search (elinating parts of the grid). If False, no constraints are used.
333 @type constraints: bool
334 @param verbosity: A flag specifying the amount of information to print. The higher the value, the greater the verbosity.
335 @type verbosity: int
336 """
337
338
339 if not hasattr(cdp, 'model'):
340 raise RelaxNoModelError('N-state')
341
342
343 n = param_num()
344
345
346 data_types = base_data_types()
347
348
349 tensor_num = align_tensor.num_tensors(skip_fixed=True)
350
351
352 if cdp.model == 'fixed' and tensor_num > 1 and ('rdc' in data_types or 'pcs' in data_types) and not align_tensor.all_tensors_fixed() and hasattr(cdp, 'paramag_centre_fixed') and cdp.paramag_centre_fixed:
353
354 print("Optimising each alignment tensor separately.")
355
356
357 fixed_flags = []
358 for i in range(len(cdp.align_ids)):
359
360 tensor = align_tensor.return_tensor(index=i, skip_fixed=False)
361
362
363 fixed_flags.append(tensor.fixed)
364
365
366 tensor.set('fixed', True)
367
368
369 for i in range(len(cdp.align_ids)):
370
371 if fixed_flags[i]:
372 continue
373
374
375 tensor = align_tensor.return_tensor(index=i, skip_fixed=False)
376
377
378 tensor.set('fixed', False)
379
380
381 lower_sub = lower[0][i*5:i*5+5]
382 upper_sub = upper[0][i*5:i*5+5]
383 inc_sub = inc[0][i*5:i*5+5]
384
385
386 self.minimise(min_algor='grid', lower=[lower_sub], upper=[upper_sub], inc=[inc_sub], scaling_matrix=[None], constraints=constraints, verbosity=verbosity, sim_index=sim_index)
387
388
389 tensor.set('fixed', True)
390
391
392 for i in range(len(cdp.align_ids)):
393
394 tensor = align_tensor.return_tensor(index=i, skip_fixed=False)
395
396
397 tensor.set('fixed', fixed_flags[i])
398
399
400 else:
401 self.minimise(min_algor='grid', lower=lower, upper=upper, inc=inc, scaling_matrix=scaling_matrix, constraints=constraints, verbosity=verbosity, sim_index=sim_index)
402
403
405 """Determine whether the given parameter is spin specific.
406
407 @param name: The name of the parameter.
408 @type name: str
409 @return: False
410 @rtype: bool
411 """
412
413
414 if name in ['r', 'heteronuc_type', 'proton_type']:
415 return True
416
417
418 return False
419
420
422 """Create bounds for the OpenDX mapping function.
423
424 @param param: The name of the parameter to return the lower and upper bounds of.
425 @type param: str
426 @param spin_id: The spin identification string (unused).
427 @type spin_id: None
428 @return: The upper and lower bounds of the parameter.
429 @rtype: list of float
430 """
431
432
433 if search('^paramag_[xyz]$', param):
434 return [-100.0, 100.0]
435
436
437 - 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):
438 """Minimisation function.
439
440 @param min_algor: The minimisation algorithm to use.
441 @type min_algor: str
442 @param min_options: An array of options to be used by the minimisation algorithm.
443 @type min_options: array of str
444 @param func_tol: The function tolerance which, when reached, terminates optimisation. Setting this to None turns of the check.
445 @type func_tol: None or float
446 @param grad_tol: The gradient tolerance which, when reached, terminates optimisation. Setting this to None turns of the check.
447 @type grad_tol: None or float
448 @param max_iterations: The maximum number of iterations for the algorithm.
449 @type max_iterations: int
450 @param constraints: If True, constraints are used during optimisation.
451 @type constraints: bool
452 @keyword scaling_matrix: The per-model list of diagonal and square scaling matrices.
453 @type scaling_matrix: list of numpy rank-2, float64 array or list of None
454 @param verbosity: A flag specifying the amount of information to print. The higher the value, the greater the verbosity.
455 @type verbosity: int
456 @param sim_index: The index of the simulation to optimise. This should be None if normal optimisation is desired.
457 @type sim_index: None or int
458 @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.
459 @type lower: list of lists of numbers
460 @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.
461 @type upper: list of lists of numbers
462 @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.
463 @type inc: list of lists of int
464 """
465
466
467 model, param_vector, data_types = target_fn_setup(sim_index=sim_index, scaling_matrix=scaling_matrix[0], verbosity=verbosity)
468
469
470 if not len(param_vector):
471 warn(RelaxWarning("The model has no parameters, minimisation cannot be performed."))
472 return
473
474
475 if constraints and cdp.model == 'fixed':
476 if verbosity:
477 warn(RelaxWarning("Turning constraints off. These cannot be used for the 'fixed' model."))
478 constraints = False
479
480
481 if min_algor == 'Method of Multipliers':
482 min_algor = min_options[0]
483 min_options = min_options[1:]
484
485
486 if not constraints and cdp.model == 'population':
487 warn(RelaxWarning("Turning constraints on. These absolutely must be used for the 'population' model."))
488 constraints = True
489
490
491 min_options = (min_algor,) + min_options
492 min_algor = 'Method of Multipliers'
493
494
495 if hasattr(cdp, 'paramag_centre_fixed') and not cdp.paramag_centre_fixed:
496 if min_algor in ['newton']:
497 raise RelaxError("For the paramagnetic centre position, as the Hessians are not yet implemented Newton optimisation cannot be performed.")
498
499
500 A, b = None, None
501 if constraints:
502 A, b = linear_constraints(data_types=data_types, scaling_matrix=scaling_matrix[0])
503
504
505 if search('^[Gg]rid', min_algor):
506
507 results = grid(func=model.func, args=(), num_incs=inc[0], lower=lower[0], upper=upper[0], A=A, b=b, verbosity=verbosity)
508
509
510 param_vector, func, iter_count, warning = results
511 f_count = iter_count
512 g_count = 0.0
513 h_count = 0.0
514
515
516 else:
517 results = generic_minimise(func=model.func, dfunc=model.dfunc, d2func=model.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=1, print_flag=verbosity)
518
519
520 if results == None:
521 return
522 param_vector, func, iter_count, f_count, g_count, h_count, warning = results
523
524
525 if isInf(func):
526 raise RelaxInfError('chi-squared')
527
528
529 if isNaN(func):
530 raise RelaxNaNError('chi-squared')
531
532
533 chi2 = model.func(param_vector)
534
535
536 if scaling_matrix[0] is not None:
537 param_vector = dot(scaling_matrix[0], param_vector)
538
539
540 disassemble_param_vector(param_vector=param_vector, data_types=data_types, sim_index=sim_index)
541
542
543 if sim_index != None:
544
545 cdp.chi2_sim[sim_index] = func
546
547
548 cdp.iter_sim[sim_index] = iter_count
549
550
551 cdp.f_count_sim[sim_index] = f_count
552
553
554 cdp.g_count_sim[sim_index] = g_count
555
556
557 cdp.h_count_sim[sim_index] = h_count
558
559
560 cdp.warning_sim[sim_index] = warning
561
562
563 else:
564
565 cdp.chi2 = func
566
567
568 cdp.iter = iter_count
569
570
571 cdp.f_count = f_count
572
573
574 cdp.g_count = g_count
575
576
577 cdp.h_count = h_count
578
579
580 cdp.warning = warning
581
582
583 if 'rdc' in data_types or 'pcs' in data_types:
584
585 minimise_bc_data(model, sim_index=sim_index)
586
587
588 if 'rdc' in data_types:
589 rdc.q_factors(sim_index=sim_index, verbosity=verbosity)
590
591
592 if 'pcs' in data_types:
593 pcs.q_factors(sim_index=sim_index, verbosity=verbosity)
594
595
597 """Return the k, n, and chi2 model statistics.
598
599 k - number of parameters.
600 n - number of data points.
601 chi2 - the chi-squared value.
602
603
604 @keyword model_info: The model information from model_loop(). This is unused.
605 @type model_info: None
606 @keyword spin_id: The spin identification string. This is ignored in the N-state model.
607 @type spin_id: None or str
608 @keyword global_stats: A parameter which determines if global or local statistics are returned. For the N-state model, this argument is ignored.
609 @type global_stats: None or bool
610 @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).
611 @rtype: tuple of (int, int, float)
612 """
613
614
615 return param_num(), num_data_points(), cdp.chi2
616
617
659
660
662 """Create and return the spin specific Monte Carlo Ri error structure.
663
664 @keyword data_id: The list of spin ID, data type, and alignment ID, as yielded by the base_data_loop() generator method.
665 @type data_id: str
666 @return: The Monte Carlo simulation data errors.
667 @rtype: list of float
668 """
669
670
671 mc_errors = []
672
673
674 container = data_id[0]
675
676
677 if data_id[1] == 'pcs' and not container.select:
678 return
679
680
681 if data_id[1] == 'rdc' and hasattr(container, 'rdc'):
682
683 if not hasattr(container, 'rdc_err'):
684 raise RelaxError("The RDC errors are missing for the spin pair '%s' and '%s'." % (container.spin_id1, container.spin_id2))
685
686
687 if data_id[2] not in container.rdc_err:
688 err = None
689 else:
690 err = container.rdc_err[data_id[2]]
691
692
693 mc_errors.append(err)
694
695
696 elif data_id[1] == 'noesy' and hasattr(container, 'noesy'):
697
698 if not hasattr(container, 'noesy_err'):
699 raise RelaxError("The NOESY errors are missing for the spin pair '%s' and '%s'." % (container.spin_id1, container.spin_id2))
700
701
702 mc_errors.append(container.noesy_err)
703
704
705 elif data_id[1] == 'pcs' and hasattr(container, 'pcs'):
706
707 if not hasattr(container, 'pcs_err'):
708 raise RelaxError("The PCS errors are missing for spin '%s'." % data_id[0])
709
710
711 if data_id[2] not in container.pcs_err:
712 err = None
713 else:
714 err = container.pcs_err[data_id[2]]
715
716
717 mc_errors.append(err)
718
719
720 return mc_errors
721
722
723 - def set_error(self, index, error, model_info=None):
724 """Set the parameter errors.
725
726 @param index: The index of the parameter to set the errors for.
727 @type index: int
728 @param error: The error value.
729 @type error: float
730 @keyword model_info: The model information from model_loop(). This is unused.
731 @type model_info: None
732 """
733
734
735 names = ['Axx', 'Ayy', 'Axy', 'Axz', 'Ayz']
736
737
738 if index < len(cdp.align_ids)*5:
739
740 param_index = index % 5
741 tensor_index = (index - index % 5) / 5
742
743
744 tensor = align_tensor.return_tensor(index=tensor_index, skip_fixed=True)
745 tensor.set(param=names[param_index], value=error, category='err')
746
747
748 return getattr(tensor, names[param_index]+'_err')
749
750
751 - def set_param_values(self, param=None, value=None, index=None, spin_id=None, error=False, force=True):
752 """Set the N-state model parameter values.
753
754 @keyword param: The parameter name list.
755 @type param: list of str
756 @keyword value: The parameter value list.
757 @type value: list
758 @keyword index: The index for parameters which are of the list-type (probs, alpha, beta, and gamma). This is ignored for all other types.
759 @type index: None or int
760 @keyword spin_id: The spin identification string (unused).
761 @type spin_id: None
762 @keyword error: A flag which if True will allow the parameter errors to be set instead of the values.
763 @type error: bool
764 @keyword force: A flag which if True will cause current values to be overwritten. If False, a RelaxError will raised if the parameter value is already set.
765 @type force: bool
766 """
767
768
769 lib.arg_check.is_str_list(param, 'parameter name')
770 lib.arg_check.is_list(value, 'parameter value')
771
772
773 for i in range(len(param)):
774
775 if not param[i]:
776 raise RelaxError("The parameter '%s' is not valid for this data pipe type." % param[i])
777
778
779 if error:
780 param[i] += '_err'
781
782
783 if param[i] in ['probs', 'alpha', 'beta', 'gamma']:
784 obj = getattr(cdp, param[i])
785 obj[index] = value[i]
786
787
788 else:
789 for spin in spin_loop(spin_id):
790 setattr(spin, param[i], value[i])
791
792
794 """Initialise the Monte Carlo parameter values."""
795
796
797 sim_names = self.data_names(set='min')
798
799
800 if hasattr(cdp, 'paramag_centre_fixed') and not cdp.paramag_centre_fixed:
801 sim_names += ['paramagnetic_centre']
802
803
804 if hasattr(cdp, 'align_tensors'):
805
806 names = ['Axx', 'Ayy', 'Axy', 'Axz', 'Ayz']
807
808
809 for i in range(len(cdp.align_tensors)):
810
811 if not opt_uses_tensor(cdp.align_tensors[i]):
812 continue
813
814
815 cdp.align_tensors[i].set_sim_num(cdp.sim_number)
816
817
818 for object_name in names:
819 for j in range(cdp.sim_number):
820 cdp.align_tensors[i].set(param=object_name, value=deepcopy(getattr(cdp.align_tensors[i], object_name)), category='sim', sim_index=j)
821
822
823 for object_name in sim_names:
824
825 sim_object_name = object_name + '_sim'
826
827
828 setattr(cdp, sim_object_name, [])
829
830
831 sim_object = getattr(cdp, sim_object_name)
832
833
834 for j in range(cdp.sim_number):
835
836 sim_object.append(None)
837
838
839 if hasattr(cdp, 'paramag_centre_fixed') and not cdp.paramag_centre_fixed:
840 for j in range(cdp.sim_number):
841 cdp.paramagnetic_centre_sim[j] = deepcopy(cdp.paramagnetic_centre)
842
843
845 """Pack the Monte Carlo simulation data.
846
847 @keyword data_id: The list of spin ID, data type, and alignment ID, as yielded by the base_data_loop() generator method.
848 @type data_id: list of str
849 @param sim_data: The Monte Carlo simulation data.
850 @type sim_data: list of float
851 """
852
853
854 container = data_id[0]
855
856
857 if data_id[1] == 'rdc' and hasattr(container, 'rdc'):
858
859 if not hasattr(container, 'rdc_sim'):
860 container.rdc_sim = {}
861
862
863 container.rdc_sim[data_id[2]] = []
864 for i in range(cdp.sim_number):
865 container.rdc_sim[data_id[2]].append(sim_data[i][0])
866
867
868 elif data_id[1] == 'noesy' and hasattr(container, 'noesy'):
869
870 container.noesy_sim = []
871 for i in range(cdp.sim_number):
872 container.noesy_sim[data_id[2]].append(sim_data[i][0])
873
874
875 elif data_id[1] == 'pcs' and hasattr(container, 'pcs'):
876
877 if not hasattr(container, 'pcs_sim'):
878 container.pcs_sim = {}
879
880
881 container.pcs_sim[data_id[2]] = []
882 for i in range(cdp.sim_number):
883 container.pcs_sim[data_id[2]].append(sim_data[i][0])
884
885
887 """Return the array of simulation parameter values.
888
889 @param index: The index of the parameter to return the array of values for.
890 @type index: int
891 @keyword model_info: The model information from model_loop(). This is unused.
892 @type model_info: None
893 @return: The array of simulation parameter values.
894 @rtype: list of float
895 """
896
897
898 names = ['Axx', 'Ayy', 'Axy', 'Axz', 'Ayz']
899
900
901 if index < align_tensor.num_tensors(skip_fixed=True)*5:
902
903 param_index = index % 5
904 tensor_index = (index - index % 5) / 5
905
906
907 tensor = align_tensor.return_tensor(index=tensor_index, skip_fixed=True)
908 return getattr(tensor, names[param_index]+'_sim')
909