Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data fit to a circle using python

I am trying to fit a set of data to an off-center-circle using python. However, the displacement of the center of the circle (to the center of the axis) is evaluated negative, when it should be positive.

enter image description here

Consider a circle of radius a with center at (θ_0, r_0). Therefore,

enter image description here

From this, I developed the following equations (I suppose everything is right):

Point (θ, r) on the circle:

enter image description here

Partial derivative of r with respect to a:

enter image description here

Partial derivative of r with respect to r_0:

enter image description here

Partial derivative of r with respect to θ_0:

enter image description here

This is a MWE:

import numpy as np               #... for mathematical operations and constants
from scipy import optimize       #... for data fitting
import matplotlib.pyplot as plt  #... for plots


dsts = [1.0238822745042868, 1.0179614005977726, 1.0288142236514832, 1.014240255062302, 1.0297548330833965]
angs = [1.2118767129907884, 0.46576418683129606, -0.2945355605442553, 0.7614519513889376, -0.7442328029712463]


#--> DEFINE FUNCTIONS TO FIT THE DATA
#... see: https://scipython.com/book/chapter-8-scipy/examples/non-linear-fitting-to-an-ellipse/

def circle(theta, p): #... defines the function for circle
    a, r0, theta_0 = p
    rc = r0*np.cos(theta - theta_0)
    return ( rc + (rc**2 + a**2 - r0**2)**0.5 )

def residuals(p, r, theta):
    """ Return the observed - calculated residuals using f(theta, p). """
    return r - circle(theta, p)

def jac(p, r, theta):
    """ Calculate and return the Jacobian of residuals. """
    tmp = circle(theta, p)
    a, r0, theta_0 = p
    da  = a/( tmp - r0*np.cos(theta - theta_0) )
    dr  = ( r*np.cos(theta - theta_0) - r0 )/( r - r0*np.cos(theta - theta_0) )
    dt  = tmp*r0*np.sin(theta - theta_0)/( tmp - r0*np.cos(theta - theta_0) )
    
    return -da,  -dr, -dt
    return np.array((-da, -dr, -dt)).T


#--> FIT THE DATA
p0 = (1, 0.05, 0) #... initial guess
plsq = optimize.leastsq(residuals, p0, Dfun=jac, args=(dsts, angs), col_deriv=True)


#--> CREATE FITTING CURVE ARRAYS
t_fit = np.linspace(0, 2*np.pi, 100)
r_fit = circle(t_fit, plsq[0])


#--> PLOT
cm = 1/2.54  #... centimeters-to-inches conversor
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(8.5*cm, 8.5*cm), dpi=300)

#--> Write the title
fit_a = str(round(plsq[0][0], 4))
fit_r = str(round(plsq[0][1], 4))
fit_t = str(round(plsq[0][2]*180/np.pi, 1))
pltTitle = "$a =$" + fit_a + "; $r_0 =$ " + fit_r + "; $\\theta_0 =$ " + fit_t + "$^\circ$"
ax.set_title(pltTitle, va='bottom', fontsize=8)

#--> Plot the measured data
ax.scatter(angs, dsts)

#--> Plot the fitted orbit
ax.plot(t_fit, r_fit, linestyle='dotted')

#--> Configure the axis
ax.set_rmax(1.2)
ax.set_rticks(np.linspace(0.2, 1, 5))
ax.set_xticklabels([])      #... remove angular tick labels
ax.set_yticklabels([])      #... remove radial tick labels
ax.grid(True)

#--> Display the plot
plt.show()

Any suggestion to get positive r_0?

like image 254
Brasil Avatar asked Feb 01 '26 07:02

Brasil


1 Answers

After examining your problem a bit closer I realized that there is in fact nothing wrong with your results!

Your problem just has an ambiguity with respect to r_0 and theta_0.

r( r0, theta_0 )  = r( -r0, theta_0 + [2n+1]pi )

Proper boundary conditions (or close enough start-values) should do the job just fine!

Alternatively you can also just "sanitize" the obtained results by applying something like:

def sanitize_results(a, r0, theta0):
    if r0 < 0:
        return a, -r0, (theta0 + np.pi)%(2*np.pi)
    else:
        return a, r0, theta0%(2*np.pi)

enter image description here


UPDATE

To use explicit boundary-conditions in the least-squares solver, you have to switch to the newer scipy interface, e.g.: scipy.optimize.least_squares

plsq = least_squares(
    residuals, 
    p0, 
    jac=jac, 
    args=(dsts, angs),
    bounds=((0, 0, 0), (10, 10, 2*np.pi))
    )

the obtained parameter values are then accessible via

plsq.x

... and a final warning: Your sketch is actually a bit misleading... your equation will only work if the center of the coordinate-system is inside the circle! ...otherwise your equation is not valid for all 360° (e.g. there is more than 1 possible solution for r given a set of parameters (a, r0, thata0)

like image 192
raphael Avatar answered Feb 03 '26 19:02

raphael



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!