Search code examples
pythonscipyminimization

Scipy.minimize not correctly minimizing


I am currently doing a project that maps the velocity of stars as you move out from the center of the galaxy(the distance to the center is denoted by r).

Essentially, I am aiming to minimize the distance between my model function and my observed function. To do this, I have to minimize the function: np.sum(((v_model-v_obs)/errors)**2) where errors is an array of the error at each different r value. v_obs is the observed velocity at each r value (r is just an array of numbers). To minimize the function, I have to manipulate v_model which can be done via manipulating two "fixing parameters" p0 and r0 in the equation (integrand is shown below):

np.sqrt(4.302*10**(-6)*quad(integrand,0,r.all(),args=(p0,r0))[0]/r.all())

Before I get into the problem, I want to know if r.all() is appropriate since it wouldn't allow me to put r due to it being an array. An alternative I had was to make an array of v_model via:

#am is the amount of elements in the array r 
#r, v_model,v_obs, and errors all have the same size
def integrand(r,p0,r0):
    return (p0 * r0**3)/((r+r0)*((r**2)+(r0**2)))*4*3.1415926535*r**2
integrals = []
for i in r:
     integrals.append(quad(integrand, 0 ,i,args=(p0,r0)))

v_model = []


for x in range (0,am):
    k = integrals[x][0]
    i = r[x]
    v_model.append(np.sqrt((4.302*10**(-6)*k)/i))

Regardless, to minimize the function np.sum(((v_model-v_obs)/errors)**2) I tried to do something like this:

def chisqfunc(parameters):
    p0 = parameters[0]
    r0 = parameters[1]
    v_model = []
    for x in range(0,am):
        v_model.append(np.sqrt(4.302*10.0**(-6)*quad(integrand, 0, r[x], args=(p0,r0))[0]/r[x]))
    chisq = np.sum(((v_model-v_obs)/errors)**2)
    return chisq
x0 = np.array([10**6,24])
resolution = minimize(chisqfunc,x0)

However, the values I get back aren't good fits at all (which is evident when I graph the observed data and my model)

In conclusion, I have two main question:

1.) Is my function taking the model minus the observed at each different r value, and if not, how do I fix this? (I think I messed up my v_model equation)

2.) Why is it returning wrong numbers for r0 and p0?

Here is my full code (By the way, to know if the minimization is working properly: r0 should be around 1.5 and p0 should be around: 3.5*10**8)

from scipy.optimize import*
import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate
from scipy.integrate import quad

#number of measurements
am = 18


r0 = 1.8
p0 = 3.5*10**8.62

#Observed Data
v_obs = np.array([
234.00,
192.00,
212.00,
223.00,
222.00,
224.00,
224.00,
226.00,
226.00,
227.00,
227.00,
224.00,
220.00,
218.00,
217.00,
216.00,
210.00,
208.00



]
)

r = np.array([0.92, 
2.32,
3.24,   
4.17,   
5.10,   
6.03,   
6.96,   
7.89,   
8.80,   
9.73,   
10.64,
11.58,
12.52,
13.46,
14.40,
15.33,
16.27,
17.11


]
)

errors = np.array([
3.62,
4.31,
3.11,
5.5,
3.9,
3.5,
2.7,
2.5,
2.3,
2.1,
2.3,
2.6,
3.1,
3.2,
3.2,
3.1,
2.9,
2.8
    ])


#integral 
def integrand(r,p0,r0):
    return (p0 * r0**3)/((r+r0)*((r**2)+(r0**2)))*4*3.1415926535*r**2
integrals = []
for i in r:
     integrals.append(quad(integrand, 0 ,i,args=(p0,r0)))

v_model = []


for x in range (0,am):
    k = integrals[x][0]
    i = r[x]
    v_model.append(np.sqrt((4.302*10**(-6)*k)/i))


def chisqfunc(parameters):
    p0 = parameters[0]
    r0 = parameters[1]
    v_model = np.sqrt(4.302*10**(-6)*quad(integrand,0,r.all(),args=(p0,r0))[0]/r.all())
    chisq = np.sum(((v_model-v_obs)/errors)**2)
    print(v_model)
    return chisq
x0 = np.array([10**6,24])
resolution = minimize(chisqfunc,x0)
print("This is the function",resolution)

Let me know if I left out any data and thank you in advance!


Solution

  • If you change p0 in the function it brings the number down in the optimizer routine. That seems to work well, I guess the numbers are so large it causes some numerical errors in the solver. Just remember to multiply the p0 answer by 3.85e+09, just like is done in the chisqfunc routine.

    from scipy.optimize import*
    import numpy as np
    import matplotlib.pyplot as plt
    import scipy.integrate as integrate
    from scipy.integrate import quad
    #-----------------------------------------------------------------------------
    am = 18 # number of measurements
    #Observed Data
    v_obs = np.array([
    234.00, 192.00, 212.00, 223.00, 222.00, 224.00, 224.00, 226.00, 226.00, 227.00, 
    227.00, 224.00, 220.00, 218.00, 217.00, 216.00, 210.00, 208.00,
    ])
    r = np.array([
     0.92,  2.32,  3.24,  4.17,  5.10,  6.03,  6.96,  7.89, 8.80, 9.73,   
    10.64, 11.58, 12.52, 13.46, 14.40, 15.33, 16.27, 17.11
    ])
    errors = np.array([
    3.62, 4.31, 3.11, 5.5, 3.9, 3.5, 2.7, 2.5, 2.3, 2.1,
    2.3, 2.6, 3.1, 3.2, 3.2, 3.1, 2.9, 2.8
    ])
    grav_const = 4.302e-06
    #print "np.pi, grav_const = " + str(np.pi) + ", " + str(grav_const)
    #-----------------------------------------------------------------------------
    def func_to_integrate(rx, p0, r0):
        rho = (p0 * r0**3) / ( (rx + r0) * (rx**2 + r0**2) )
        function_result = rho * 4.0 * np.pi * rx**2
        #print "rx, p0, r0 = " + str(rx) + ", " + str(p0) + ", " + str(r0)
        return function_result
    #-----------------------------------------------------------------------------
    def simpsonsRule(func, a, b, n, p0, r0):
        if n%2 == 1:
            return "Not applicable"
        else:
            h = (b - a) / float(n)
            s = func(a, p0, r0) + sum((4 if i%2 == 1 else 2) * func(a+i*h, p0, r0) for i in range(1,n)) + func(b, p0, r0)
            return s*h/3.0
    #-----------------------------------------------------------------------------
    def chisqfunc(iter_vars):
        global v_model
        p0 = iter_vars[0] * 3.85e+09
        r0 = iter_vars[1]
        v_model = []
        for index in range(0, am):
            integral_result = simpsonsRule(func_to_integrate, 0.0, r[index], 200, p0, r0)
            v_mod_result = np.sqrt(grav_const * integral_result / r[index])
            v_model.append(v_mod_result)
        chisq = 0.0
        for index in range(0, am):
            chisq += ((v_model[index] - v_obs[index])/errors[index])**2
            #chisq += ((v_model[index] - v_obs[index]))**2
        chisq = np.sqrt(chisq)
        #chisq = np.sum(((v_model - v_obs)/errors)**2)
        print "chisq = " + str(chisq)
        #print "iterated p0, r0 = " + str(p0) + ", " + str(r0)
        return chisq
    #-----------------------------------------------------------------------------
    initial_guess = np.array([1.0, 2.0])
    #initial_guess = np.array([3.5*10**8.62, 1.5])
    #resolution = minimize(chisqfunc, initial_guess, method='TNC') # , tol=1e-12, options={'accuracy':0})
    #resolution = minimize(chisqfunc, initial_guess, method='TNC', options={'rescale':-1})
    #resolution = minimize(chisqfunc, initial_guess, method='CG')
    resolution = minimize(chisqfunc, initial_guess, method='Nelder-Mead')
    # # options tried
    # 'gtol':1e-6, 'xtol':-1, 'rescale':0, 'ftol':0, 'accuracy':0, 'offset':None, 'scale':None
    print("This is the function",resolution)
    #print "resolution.x"
    #print resolution.x
    print "iterated p0 = " + str(resolution.x[0])
    print "iterated p0 * 3.85e+09 = " + str(resolution.x[0] * 3.85e+09)
    print "iterated r0 = " + str(resolution.x[1])
    print "p0 should be about = " + str(3.5e+08)
    print "r0 should be about = " + str(1.5)
    print "-----------------------------------------------"
    print "iterated model errors"
    for index in range(0,am):
        print "v_obs, v_model, error = " + str(v_obs[index]) + ", " + str(v_model[index]) + ", " + str(v_obs[index] - v_model[index])
    chisq = 0.0
    for index in range(0, am):
        chisq += ((v_model[index] - v_obs[index])/errors[index])**2
        #chisq += ((v_model[index] - v_obs[index]))**2
    chisq = np.sqrt(chisq)
    print "iterated chisq = " + str(chisq)
    print ""
    #-----------------------------------------------------------------------------
    print "model errors using p0 = 3.5*10**8.62, r0 = 1.8"
    p0 = 3.5*10**8.62
    r0 = 1.8
    # chisq = 118 for p0 = 3.5*10**8.62, r0 = 1.8
    # chisq = 564 for p0 = 3.5*10**8   , r0 = 1.5
    chisq = 0.0
    for index in range(0, am):
        integral_result = simpsonsRule(func_to_integrate, 0.0, r[index], 200, p0, r0)
        v_mod = np.sqrt(grav_const * integral_result / r[index])
        print "v_obs, v_mod, error = " + str(v_obs[index]) + ", " + str(v_mod) + ", " + str(v_obs[index] - v_mod)
        #chisq += ((v_mod - v_obs[index]))**2
        chisq += ((v_mod - v_obs[index])/errors[index])**2
    chisq = np.sqrt(chisq)
    print "using p0 = 3.5*10**8.62, r0 = 1.8, the chisq = " + str(chisq)
    #-----------------------------------------------------------------------------