This code is the step by step handmade solution to the standard linear optimization. With step-by-step I mean that it is easy to see what happens if you write it down in matrix and vector form, but all the matrix and vector operations are done manually here. With working example and comparison to other methods, it looks like this
(For more details on a more general matrix approach, see e.g. here
)
"""
I will assume that x, y and ySig are numpy arrays so,
we can avoid all the loops.
The code follws the main idea that we want to minimize the error in
(y - (c1 + c2 x ) ). Here actually even weighted. So, when expressing
in matrix form this is
( y - A a ).T W ( y - A a ) = min,
where a = (c1, c2) and A.T = ( ( 1, ..., 1 ),( x1, ..., xn ) ),
i.e. A is a n by 2 matrix. A.T is the transposed matrix
setting the derivative to zero results in
A.T W A a = A.T W y
note that W = diag( 1 / sigma_1^2, ..., 1/ sigma_n^2 ),
i.e. the inverse of the covariance matrix.( here, no correlation is assumed)
Evaluating A.T W A we get
( ( XX, X ),( X, S) ).T with:
XX = sum x_i^2 / sig_i^2
X = sum x_i / sig_i^2
S = sum 1 / sig_i^2
obviously we need the inverse, which is P = ((S, -X),(-X, XX))/ det,
with det = XX * S - X^2
we then have a = P A.T W y
and A.T W y = ( eta, xi) with
Y = sum y_i / sig_i^2 and
XY = sum x_i y_i / sig_i^2 )
in general a = J y so if the covariance of y is Vy the covariance of a
is Va = J Vy J.T
here Va = ( A.T W A )^(-1) A.T W Vy W.T A (A.T W.T A )^(-1)
plugging in and noting that W = Vy^(-1) and W.T = W we have
Va = P
Now putting the variable names from above
"""
import numpy as np
def fit_weighted( x, y, ySig ):
#N.B.: la funizione utilizza come formula della retta y=a+bx !
if (
( len( x ) != len( y ) ) or
( len( ySig ) != len( y ) )
):
raise ValueError (
"Input lists of unequal length!"
)
try:
XX = np.dot( x**2, 1 / ySig**2 )
X = np.dot( x, 1 / ySig**2 )
S = np.dot( 1 / ySig , 1 / ySig )
Y = np.dot( 1 / ySig, y / ySig )
XY = np.dot( x / ySig, y / ySig )
det = XX * S - X**2 ### problematic for small det
amc = ( XX * Y - X * XY ) / det
bmc = ( S * XY - X * Y ) / det
sigmaAmc = np.sqrt( XX / det )
sigmaBmc = np.sqrt( S / det )
###chi2
C1 = amc * np.ones( len( x ) )
C2 = bmc * x
delta = y - C1 - C2
chi2 = np.dot( delta / ySig, delta / ySig )
"""
Note, we already have P and could provide the covariance matrix
as output
"""
return [
'A_mc:',amc,'B_mc:',bmc,
'sigmaA:', sigmaAmc,'sigmaB:', sigmaBmc,
'chi^2:', chi2
]
except ZeroDivisionError:
nan = float( 'nan' )
return [
'A_mc:', nan,'B_mc:', nan,
'sigmaA:', nan,'sigmaB', nan,
'chi^2:', nan
]
if __name__ == "__main__":
"""
showing that we get the same results with curve_fit
"""
from scipy.optimize import curve_fit
c1 = 0.34
c2 = 1.233
xl = np.linspace( -2,3, 15 )
yl = c1 + c2 * xl
s = np.random.normal( size=len( xl ), scale=0.1 )
yl = yl + s
sa = np.abs( s )
result = fit_weighted( xl, yl, sa )
print( result )
cfresult = curve_fit(
lambda x, a, b: a + b * x,
xl, yl,
sigma=sa, absolute_sigma=True
)
print( cfresult[0] )
print( np.sqrt( cfresult[1][0,0] ) )
print( np.sqrt( cfresult[1][1,1] ) )