0

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])
  • can you provide all the imports and context for the modules. such as the origins of dB_dqsq_arr and m_e / f_0 / G_F – TheCableGUI Aug 02 '23 at 19:35
  • 1
    @TheCableGUI Thank you for the suggestion, I have updated my question with the libraries I have imported and the variables used. – noob anomaly Aug 02 '23 at 20:07
  • if at all possible could you share the "q_sq_arr" variable as well? – TheCableGUI Aug 02 '23 at 20:14
  • @TheCableGUI my apologies for missing that out, I have now added q_sq_arr as well. – noob anomaly Aug 02 '23 at 20:17
  • Is your objective function (chi_sq) returning a numpy array at the moment? (instead of a single float). If so, you will need to modify that function to return a float (hard to tell what your logic is with looping around dB_dqsq_model2_para - but perhaps you are missing a "sum" or other reduction afterwards) – Azmy Rajab Aug 02 '23 at 20:22
  • @AzmyRajab Ah yes, I just realized that I missed the sum function, the chi_sq function is supposed to be a float which returns the sum of the mentioned function. I tried adding np.sum() around the current return value; however, now the following error shows: "ValueError: Desired error not necessarily achieved due to precision loss." – noob anomaly Aug 02 '23 at 20:26
  • i posted the solution with the corrected functions and works as intended. – TheCableGUI Aug 02 '23 at 20:49
  • are you by chance calculating theoretical neutrino-nucleus interactions in particle physics using differential cross section scatting process with respect to squared momentum transfers? – TheCableGUI Aug 02 '23 at 21:18
  • @TheCableGUI No, I am tyring to extract V_ub and alpha information for B meson decays by comparing various form factor models and chi-square minimization. Though it might not be exactly evident from the code in my post because I have removed several variables so as to not bombard the readers with too many unecessary variables which do not really affect the way the code itself works. – noob anomaly Aug 02 '23 at 22:06
  • @noobanomaly right on. have fun. Is there any place i could start if i was interested in this? – TheCableGUI Aug 02 '23 at 22:22

2 Answers2

0

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)

0

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()
Reinderien
  • 11,755
  • 5
  • 49
  • 77
  • Thank you for your response! Regarding the initial values being equal to the minimizations, that is to be expected since the intial guesses that I choose are the literature values for the two quantities. I did this on purpose so as to not choose values which are too off from the literature values (because I wasn't sure what the conditions on setting the initial guess values are). – noob anomaly Aug 03 '23 at 03:18