noob anomaly
noob anomaly

Reputation: 3

Minimizing a function of two variables using scipy.optimize

I have written the following code in Python:

def f_multi_para(q_sq, alpha_par):
    return f_0 / (q_sq * alpha_par)

def dB_dqsq_model2_para(q_sq, V_ub_par, alpha_par):
    sec1 = G_F**2 * V_ub_par
    sec2 = (1 - m_e**2 / q_sq)**2
    sec3 = (q_sq * (1 + m_e**2 / q_sq) * f_multi_para(q_sq, alpha_par)**2)
    return sec1 * sec2 * sec3

def chi_sq(params):
    V_ub_par, alpha_par = params
    return np.sum(((dB_dqsq_arr - np.array([dB_dqsq_model2_para(v, V_ub_par, alpha_par) for v in q_sq_arr])) / dBdqsq_err_arr)**2)

initial_guess = [0.0037, 0.54]
result = optimize.minimize(chi_sq, initial_guess)
if result.success:
    fitted_params = result.x
    print(fitted_params)
else:
    raise ValueError(result.message)

Where q_sq, V_ub_par, and alpha_par are the function parameters, and the rest are stored variables. I am trying to minimize chi_sq(params) with respect to the parameters V_ub_par and alpha_par whilst q_sq takes several values through a for loop. I found a solution to minimizing a multivariate function using scipy.optimize here and I followed the same methodology; however, when I run the code, the following error pops up:

ValueError: The user-provided objective function must return a scalar value.

I am not sure what this error means, and how to fix it (for context, I am relatively new to python, and programming in general).

I am using the following libraries:

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

And the variables in the above code are either integers or arrays, given as follows:

f_0 = 0.261
G_F = 1.166 * 10**(-5)
m_e = 0.511 * 10**(-3)
dB_dqsq_arr = np.array([7.2,7.14,6.7,7.56,6.44,7.17,6.67,6.33,6.2,4.32,4.25,3.4,1.17])
dBdqsq_err_arr = np.array([0.70,0.45,0.39,0.43,0.43,0.45,0.47,0.48,0.44,0.43,0.41,0.42,0.26])
q_sq_arr = np.array([1,3,5,7,9,11,13,15,17,19,21,23,25])

Upvotes: 0

Views: 461

Answers (2)

Reinderien
Reinderien

Reputation: 15233

The following fixes vectorisation gaps and runs successfully. It's suspicious that the minimum found is equal to the initial guess, but I consider that to be a separable problem.

import numpy as np
from scipy import optimize

f_0 = 0.261
G_F = 1.166e-5
m_e = 0.511e-3
dB_dqsq_arr = np.array((7.2,7.14,6.7,7.56,6.44,7.17,6.67,6.33,6.2,4.32,4.25,3.4,1.17))
dBdqsq_err_arr = np.array((0.70,0.45,0.39,0.43,0.43,0.45,0.47,0.48,0.44,0.43,0.41,0.42,0.26))
q_sq_arr = np.arange(1, 26, 2)


def f_multi_para(q_sq: int | np.ndarray, alpha_par: float) -> float:
    return f_0 / (q_sq * alpha_par)


def dB_dqsq_model2_para(q_sq: int | np.ndarray, V_ub_par: float, alpha_par: float) -> float:
    sec1 = G_F**2 * V_ub_par
    sqdiff = 1 - m_e**2 / q_sq
    sec2 = sqdiff**2
    sec3 = q_sq * sqdiff * f_multi_para(q_sq, alpha_par)**2
    return sec1 * sec2 * sec3


def chi_sq(params: np.ndarray) -> float:
    V_ub_par, alpha_par = params
    chi = (
        dB_dqsq_arr -
        dB_dqsq_model2_para(q_sq_arr, V_ub_par, alpha_par)
    ) / dBdqsq_err_arr
    return chi.dot(chi)


def regression_test() -> None:
    actual = chi_sq((0.004, 0.54))
    assert np.isclose(actual, 2307.989692506141, rtol=0, atol=1e-12)


def solve() -> None:
    initial_guess = (0.0037, 0.54)
    result = optimize.minimize(fun=chi_sq, x0=initial_guess)
    if not result.success:
        raise ValueError(result.message)
    fitted_params = result.x
    print(fitted_params)


if __name__ == '__main__':
    regression_test()
    solve()

Upvotes: 0

TheCableGUI
TheCableGUI

Reputation: 124

To further debug your code i constructed this analyzing function and scalar generic typing functions, use it to evalute your values to a further extent.

from typing import Union, TypeVar
import numpy as np
from scipy.optimize import OptimizeResult


# Define a generic scalar type Scalar
scalar_types = (int, float, complex)
Scalar = TypeVar('Scalar', *scalar_types)


def scalar(value: Scalar) -> Union[Scalar, Exception]:
    """ construct a scalar using generic typing """

    if isinstance(value, scalar_types):
        return value
    
    elif isinstance(value, np.ndarray):
        raise Exception("Expected a scalar (int, complex, float) value but received np.ndarray")

    elif isinstance(value, OptimizeResult):
        raise Exception("Expected a scalar but got an scipy.optimize.OptimizeResult")

    try:
        print(dir(value))
    except:
        
        try:
            print(vars(value))
        except:
            print("dir and vars can not be used on ", type(value))
        
    return Exception(f"Expected a scalar (int, complex, float) value but received {type(value)}")

SOLUTION Your function chi_sq does not return a scalar. it returns a np.ndarray. Rework the chi_sq to ensure that it returns a type of int, complex or float.


def chi_sq(params):
    V_ub_par, alpha_par = params
    vals = (dB_dqsq_arr - np.array([dB_dqsq_model2_para(v, V_ub_par, alpha_par) for v in q_sq_arr]))
    chi = [x / dBdqsq_err_arr for x in vals]
    return np.sum(chi)

Resulting Code


from math import *
import numpy as np
from scipy.integrate import quad
import scipy.optimize as optimize


# DATA
f_0 = 0.261 # Double
G_F = 1.166 * 10**(-5) # Double
m_e = 0.511 * 10**(-3) # Double

# TYPE array[float]
dB_dqsq_arr = np.array([7.2,7.14,6.7,7.56,6.44,7.17,6.67,6.33,6.2,4.32,4.25,3.4,1.17])
# TYPE array[float]
dBdqsq_err_arr = np.array([0.70,0.45,0.39,0.43,0.43,0.45,0.47,0.48,0.44,0.43,0.41,0.42,0.26])
# TYPE array[int]
q_sq_arr = np.array([1,3,5,7,9,11,13,15,17,19,21,23,25])



def f_multi_para(q_sq, alpha_par):
    return f_0 / (q_sq * alpha_par)

def dB_dqsq_model2_para(q_sq, V_ub_par, alpha_par):
    sec1 = G_F**2 * V_ub_par
    sec2 = (1 - m_e**2 / q_sq)**2
    sec3 = (q_sq * (1 + m_e**2 / q_sq) * f_multi_para(q_sq, alpha_par)**2)
    return sec1 * sec2 * sec3

def chi_sq(params):
    V_ub_par, alpha_par = params
    vals = (dB_dqsq_arr - np.array([dB_dqsq_model2_para(v, V_ub_par, alpha_par) for v in q_sq_arr]))
    chi = [x / dBdqsq_err_arr for x in vals]
    return np.sum(chi)


initial_guess = [0.0037, 0.54]
print(type(chi_sq(initial_guess)))
result = optimize.minimize(chi_sq, initial_guess)
if result.success:
    fitted_params = result.x
    print(fitted_params)
else:
    raise ValueError(result.message)

Upvotes: 0

Related Questions