55from diffpy .utils .parsers .loaddata import loadData
66
77
8- def _top_hat (x , half_slit_width ):
8+ def _top_hat (z , half_slit_width ):
99 """
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
1111 """
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 )
1313
1414
15- def _model_function (x , diameter , x0 , I0 , mud , slope ):
15+ def _model_function (z , diameter , z0 , I0 , mud , slope ):
1616 """
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)
1919 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
2323 """
2424 min_radius = - diameter / 2
2525 max_radius = diameter / 2
26- h = x - x0
26+ x = z - z0
2727 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 ],
3131 )
32- return (I0 - slope * x ) * np .exp (- mud / diameter * length )
32+ return (I0 - slope * z ) * np .exp (- mud / diameter * length )
3333
3434
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 ):
3636 """
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
3838 (note that the convolved I values are the same as modeled I values if slit width is close to 0)
3939 """
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 )
4646 I_convolved = I_extended # this takes care of the case where slit width is close to 0
4747 if kernel .sum () != 0 :
4848 kernel /= kernel .sum ()
4949 I_convolved = convolve (I_extended , kernel , mode = "same" )
50- padding_length = len (x_left_pad )
50+ padding_length = len (z_left_pad )
5151 return I_convolved [padding_length :- padding_length ]
5252
5353
54- def _objective_function (params , x , observed_data ):
54+ def _objective_function (params , z , observed_data ):
5555 """
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
5757 by minimizing the sum of squared residuals between the observed data and the convolved model data
5858 """
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 )
6161 residuals = observed_data - convolved_model_data
6262 return np .sum (residuals ** 2 )
6363
6464
65- def _compute_single_mud (x_data , I_data ):
65+ def _compute_single_mud (z_data , I_data ):
6666 """
67- perform dual annealing optimization and extract the parameters
67+ Perform dual annealing optimization and extract the parameters
6868 """
6969 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 ]
7373 (1e-5 , I_data .max ()), # I0: [small positive value, max observed intensity]
7474 (1e-5 , 20 ), # muD: [small positive value, upper bound]
7575 (- 100000 , 100000 ), # slope: [lower bound, upper bound]
7676 ]
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 )
8080 residuals = I_data - convolved_fitted_signal
8181 rmse = np .sqrt (np .mean (residuals ** 2 ))
8282 return mud , rmse
@@ -99,6 +99,6 @@ def compute_mud(filepath):
9999 mu*D : float
100100 The best-fit mu*D value.
101101 """
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 ])
104104 return best_mud
0 commit comments