5
5
from diffpy .utils .parsers .loaddata import loadData
6
6
7
7
8
- def _top_hat (x , half_slit_width ):
8
+ def _top_hat (z , half_slit_width ):
9
9
"""
10
- create a top-hat function, return 1.0 for values within the specified slit width and 0 otherwise
10
+ Create a top-hat function, return 1.0 for values within the specified slit width and 0 otherwise
11
11
"""
12
- return np .where ((x >= - half_slit_width ) & (x <= half_slit_width ), 1.0 , 0 )
12
+ return np .where ((z >= - half_slit_width ) & (z <= half_slit_width ), 1.0 , 0. 0 )
13
13
14
14
15
- def _model_function (x , diameter , x0 , I0 , mud , slope ):
15
+ def _model_function (z , diameter , z0 , I0 , mud , slope ):
16
16
"""
17
- compute the model function with the following steps:
18
- 1. Recenter x to h by subtracting x0 (so that the circle is centered at 0 and it is easier to compute length l)
17
+ Compute the model function with the following steps:
18
+ 1. Recenter z to x by subtracting z0 (so that the circle is centered at 0 and it is easier to compute length l)
19
19
2. Compute length l that is the effective length for computing intensity I = I0 * e^{-mu * l}:
20
- - For h within the diameter range, l is the chord length of the circle at position h
21
- - For h outside this range, l = 0
22
- 3. Apply a linear adjustment to I0 by taking I0 as I0 - slope * x
20
+ - For x within the diameter range, l is the chord length of the circle at position x
21
+ - For x outside this range, l = 0
22
+ 3. Apply a linear adjustment to I0 by taking I0 as I0 - slope * z
23
23
"""
24
24
min_radius = - diameter / 2
25
25
max_radius = diameter / 2
26
- h = x - x0
26
+ x = z - z0
27
27
length = np .piecewise (
28
- h ,
29
- [h < min_radius , (min_radius <= h ) & (h <= max_radius ), h > max_radius ],
30
- [0 , lambda h : 2 * np .sqrt ((diameter / 2 ) ** 2 - h ** 2 ), 0 ],
28
+ x ,
29
+ [x < min_radius , (min_radius <= x ) & (x <= max_radius ), x > max_radius ],
30
+ [0 , lambda x : 2 * np .sqrt ((diameter / 2 ) ** 2 - x ** 2 ), 0 ],
31
31
)
32
- return (I0 - slope * x ) * np .exp (- mud / diameter * length )
32
+ return (I0 - slope * z ) * np .exp (- mud / diameter * length )
33
33
34
34
35
- def _extend_x_and_convolve ( x , diameter , half_slit_width , x0 , I0 , mud , slope ):
35
+ def _extend_z_and_convolve ( z , diameter , half_slit_width , z0 , I0 , mud , slope ):
36
36
"""
37
- extend x values and I values for padding (so that we don't have tails in convolution), then perform convolution
37
+ extend z values and I values for padding (so that we don't have tails in convolution), then perform convolution
38
38
(note that the convolved I values are the same as modeled I values if slit width is close to 0)
39
39
"""
40
- n_points = len (x )
41
- x_left_pad = np .linspace (x .min () - n_points * (x [1 ] - x [0 ]), x .min (), n_points )
42
- x_right_pad = np .linspace (x .max (), x .max () + n_points * (x [1 ] - x [0 ]), n_points )
43
- x_extended = np .concatenate ([x_left_pad , x , x_right_pad ])
44
- I_extended = _model_function (x_extended , diameter , x0 , I0 , mud , slope )
45
- kernel = _top_hat (x_extended - x_extended .mean (), half_slit_width )
40
+ n_points = len (z )
41
+ z_left_pad = np .linspace (z .min () - n_points * (z [1 ] - z [0 ]), z .min (), n_points )
42
+ z_right_pad = np .linspace (z .max (), z .max () + n_points * (z [1 ] - z [0 ]), n_points )
43
+ z_extended = np .concatenate ([z_left_pad , z , z_right_pad ])
44
+ I_extended = _model_function (z_extended , diameter , z0 , I0 , mud , slope )
45
+ kernel = _top_hat (z_extended - z_extended .mean (), half_slit_width )
46
46
I_convolved = I_extended # this takes care of the case where slit width is close to 0
47
47
if kernel .sum () != 0 :
48
48
kernel /= kernel .sum ()
49
49
I_convolved = convolve (I_extended , kernel , mode = "same" )
50
- padding_length = len (x_left_pad )
50
+ padding_length = len (z_left_pad )
51
51
return I_convolved [padding_length :- padding_length ]
52
52
53
53
54
- def _objective_function (params , x , observed_data ):
54
+ def _objective_function (params , z , observed_data ):
55
55
"""
56
- compute the objective function for fitting a model to the observed/experimental data
56
+ Compute the objective function for fitting a model to the observed/experimental data
57
57
by minimizing the sum of squared residuals between the observed data and the convolved model data
58
58
"""
59
- diameter , half_slit_width , x0 , I0 , mud , slope = params
60
- convolved_model_data = _extend_x_and_convolve ( x , diameter , half_slit_width , x0 , I0 , mud , slope )
59
+ diameter , half_slit_width , z0 , I0 , mud , slope = params
60
+ convolved_model_data = _extend_z_and_convolve ( z , diameter , half_slit_width , z0 , I0 , mud , slope )
61
61
residuals = observed_data - convolved_model_data
62
62
return np .sum (residuals ** 2 )
63
63
64
64
65
- def _compute_single_mud (x_data , I_data ):
65
+ def _compute_single_mud (z_data , I_data ):
66
66
"""
67
- perform dual annealing optimization and extract the parameters
67
+ Perform dual annealing optimization and extract the parameters
68
68
"""
69
69
bounds = [
70
- (1e-5 , x_data .max () - x_data .min ()), # diameter: [small positive value, upper bound]
71
- (0 , (x_data .max () - x_data .min ()) / 2 ), # half slit width: [0, upper bound]
72
- (x_data .min (), x_data .max ()), # x0 : [min x , max x ]
70
+ (1e-5 , z_data .max () - z_data .min ()), # diameter: [small positive value, upper bound]
71
+ (0 , (z_data .max () - z_data .min ()) / 2 ), # half slit width: [0, upper bound]
72
+ (z_data .min (), z_data .max ()), # z0 : [min z , max z ]
73
73
(1e-5 , I_data .max ()), # I0: [small positive value, max observed intensity]
74
74
(1e-5 , 20 ), # muD: [small positive value, upper bound]
75
75
(- 100000 , 100000 ), # slope: [lower bound, upper bound]
76
76
]
77
- result = dual_annealing (_objective_function , bounds , args = (x_data , I_data ))
78
- diameter , half_slit_width , x0 , I0 , mud , slope = result .x
79
- convolved_fitted_signal = _extend_x_and_convolve ( x_data , diameter , half_slit_width , x0 , I0 , mud , slope )
77
+ result = dual_annealing (_objective_function , bounds , args = (z_data , I_data ))
78
+ diameter , half_slit_width , z0 , I0 , mud , slope = result .x
79
+ convolved_fitted_signal = _extend_z_and_convolve ( z_data , diameter , half_slit_width , z0 , I0 , mud , slope )
80
80
residuals = I_data - convolved_fitted_signal
81
81
rmse = np .sqrt (np .mean (residuals ** 2 ))
82
82
return mud , rmse
@@ -99,6 +99,6 @@ def compute_mud(filepath):
99
99
mu*D : float
100
100
The best-fit mu*D value.
101
101
"""
102
- x_data , I_data = loadData (filepath , unpack = True )
103
- best_mud , _ = min ((_compute_single_mud (x_data , I_data ) for _ in range (20 )), key = lambda pair : pair [1 ])
102
+ z_data , I_data = loadData (filepath , unpack = True )
103
+ best_mud , _ = min ((_compute_single_mud (z_data , I_data ) for _ in range (20 )), key = lambda pair : pair [1 ])
104
104
return best_mud
0 commit comments