44from typing import Optional
55
66import numpy as np
7- from scipy .integrate import simpson
87from lmfit import Parameters , minimize
98
109from . import Pattern
1110from .transform import calculate_fr , calculate_gr , calculate_sq_from_fr
1211
1312__all__ = [
1413 "optimize_sq" ,
14+ "optimize_sq_fit" ,
1515 "optimize_density" ,
1616]
1717
@@ -98,9 +98,88 @@ def optimize_sq(
9898 return sq_pattern
9999
100100
101+ def optimize_sq_fit (sq_pattern : Pattern , r_cutoff : float ) -> Pattern :
102+ """
103+ Optimizes the S(Q) pattern by fitting a polynomial to the F(Q) = q( S(Q) - 1 ). The order of the polynomial
104+ is determined by the q_max and r_cutoff value = r_cutoff * q_max / pi. The zero order term is fixed to 0.
105+
106+ This method is based on the normalization description for PDFGetX3 in the following reference:
107+
108+ Juhás, P., Davis, T., Farrow, C.L., Billinge, S.J.L., 2013. PDFgetX3: a rapid and highly automatable
109+ program for processing powder diffraction data into total scattering pair distribution functions.
110+ J Appl Crystallogr 46, 560–566. https://doi.org/10.1107/S0021889813005190
111+
112+ In order to try to do a similar procedure as in the above paper, the input S(Q) should be created using
113+ a normalization without incoherent scattering. Since it is assume that the polynomial fit, will also
114+ remove the incoherent scattering.
115+
116+ :param sq_pattern:
117+ original S(Q)
118+ :param r_cutoff:
119+ cutoff value below which there is no signal expected (below the first peak in g(r))
120+
121+ :return:
122+ optimized S(Q) pattern
123+ """
124+
125+ q = sq_pattern .x
126+ fq = sq_pattern .x * (sq_pattern .y - 1 )
127+
128+ degree = q .max () * r_cutoff / (np .pi )
129+ degree = max (1.0 , degree ) # at least a linear fit
130+
131+ degree_high = np .ceil (degree ).astype (int )
132+ degree_low = np .floor (degree ).astype (int )
133+
134+ if degree_low == degree_high :
135+ # When degrees are the same, we only need to fit once
136+ coeffs_low = fit_polynom_through_origin (q , fq , degree_low )
137+ p_low = np .poly1d (coeffs_low )
138+ fq_fit = p_low (q )
139+ else :
140+ weight_low , weight_high = degree_high - degree , degree - degree_low
141+ coeffs_low = fit_polynom_through_origin (q , fq , degree_low )
142+ coeffs_high = fit_polynom_through_origin (q , fq , degree_high )
143+ p_low = np .poly1d (coeffs_low )
144+ p_high = np .poly1d (coeffs_high )
145+ fq_fit = p_low (q ) * weight_low + p_high (q ) * weight_high
146+
147+ return Pattern (sq_pattern .x , sq_pattern .y - fq_fit / sq_pattern .x )
148+
149+
150+ def fit_polynom_through_origin (x , y , degree : int ) -> np .ndarray :
151+ """
152+ Fits a polynomial of given degree through the data points (x, y) with the constraint that the polynomial goes
153+ through the origin (0,0). The zero order term is fixed to 0.
154+
155+ Implementation is based on ChatGPT recommendation for it to be the fastest solution.
156+
157+ :param x:
158+ x data points
159+ :param y:
160+ y data points
161+ :param degree:
162+ degree of the polynomial
163+
164+ :return:
165+ coefficients of the polynomial, highest degree first
166+ """
167+ # Vandermonde matrix WITHOUT the x⁰ column
168+ # shape: (len(x), degree)
169+ X = np .vstack ([x ** k for k in range (1 , degree + 1 )]).T
170+
171+ # Solve X * beta = y
172+ beta , * _ = np .linalg .lstsq (
173+ X , y , rcond = - 1
174+ ) # rcond=-1 means as much precision as possible
175+
176+ return np .concatenate (
177+ (beta [::- 1 ], [0 ])
178+ ) # add zero coefficient for x⁰ and reverse order
179+
180+
101181from .calc import calculate_pdf
102- from .configuration import DataConfig , CalculationConfig
103- from .methods import ExtrapolationMethod
182+ from .configuration import CalculationConfig , DataConfig
104183
105184
106185def optimize_density (
@@ -117,8 +196,8 @@ def optimize_density(
117196 The density in the SampleConfig of the DataConfig is taking as starting parameter
118197
119198 For method='gr' or method='fr' the optimization is based on the g(r) or f(r) function, and the density is
120- optimized to minimize the low g(r) or f(r) region to be close to zero. The Lorch modification function will be
121- applied before calculating the chi square of the low r region if it is applied in the calculation configuration.
199+ optimized to minimize the low g(r) or f(r) region to be close to zero. The Lorch modification function will be
200+ applied before calculating the chi square of the low r region if it is applied in the calculation configuration.
122201 The general procedure is explained in Eggert et al. 2002 PRB, 65, 174105.
123202
124203 For method='sq' the optimization is based on the low Q part of the S(Q) function, and the density is optimized
0 commit comments