Search code examples
pythonscipyminimizeminimization

How to minimize a function by several parameters? (part 1)


I would like to minimize a function dchi(a, b, c, d) by 4 parameters a,b,c and d.
It looks like there is an mistake in this line res = sp.optimize.minimize(dchi, first_guess), could you please tell me what exactly is written incorrectly and how to fix it?

import functools
import scipy as sp


def CH(chi: float, T: float, a: float, b: float, c: float, d: float, H: float, TC: float, C:float) -> float:
    return (a*(T - TC)*chi**(1/c)+ b * chi**(1/c) * chi**(1/d) * H**(1/d)- 1)+C/(T-TC)


exp=[[10.0052, 87.9716], [11.0433, 86.3866], [12.359, 84.8015], [13.7127,   83.2164], [14.9939, 82.0276], [16.3367, 80.8388], [17.6282,   80.0463], [18.9464, 78.8575], [18.9934, 78.8575], [18.9969,   78.8575], [18.9962, 78.8575], [19.4221, 78.4612], [20.8034,   77.6687], [22.1306, 76.4799], [23.4352, 75.6873], [24.7503,   75.291], [26.0295, 74.4985], [27.3692, 73.706], [30.0264,   72.9134], [31.3262, 72.5172], [32.6313, 72.1209], [33.9404,   71.7246], [35.2494, 71.3284], [36.551, 70.9321], [37.8445,   70.5358], [40.5813, 69.7433], [41.7696, 69.347], [43.0624,   69.347], [44.3609, 68.9507], [45.6536, 68.5545], [46.9616,   68.5545], [48.254, 68.1582], [49.5428, 67.7619], [52.1298,   67.3657], [53.4147, 67.3657], [54.7091, 66.9694], [56.0001,   66.9694], [57.2883, 66.5731], [58.5865, 66.5731], [59.8911,   66.1769], [62.4856, 66.1769], [63.7665, 65.7806], [65.0689,   65.7806], [66.3588, 65.3843], [67.6581, 65.3843], [68.9546,   65.3843], [70.2572, 64.9881], [72.8482, 64.9881], [74.1637,   64.9881], [75.4546, 64.5918], [76.7495, 64.5918], [78.0347,   64.5918], [79.3376, 64.1955], [80.6437, 64.1955], [83.5861,   64.1955], [84.8957, 63.7993], [86.2123, 63.7993], [87.5317,   63.7993], [88.8468, 63.7993], [90.1674, 63.7993], [91.4775,   63.403], [94.2769, 63.403], [95.5863, 63.403], [96.9007,   63.403], [98.2013, 63.403], [99.523, 63.0067], [100.839,   63.0067], [102.147, 63.0067], [104.812, 63.0067], [106.131,   63.0067], [107.448, 62.6104], [108.789, 62.6104], [110.099,   62.6104], [111.414, 62.6104], [112.756, 62.6104], [115.404,   62.6104], [116.737, 62.6104], [118.059, 62.6104], [119.376,   62.2142], [120.7, 62.2142], [122.025, 62.2142], [123.367,   62.2142], [126.006, 62.2142], [127.352, 62.2142], [128.674,   62.2142], [129.999, 62.2142], [131.333, 62.2142], [132.652,   61.8179], [133.987, 61.8179], [136.652, 61.8179], [137.987,   61.8179], [139.324, 61.8179], [140.645, 61.8179], [141.989,   61.8179], [143.316, 61.8179], [144.646, 61.8179], [147.323,   61.8179], [148.651, 61.4216], [149.978, 61.4216], [151.303,   61.4216], [152.637, 61.4216], [153.949, 61.4216], [155.268,   61.4216], [157.929, 61.4216], [159.252, 61.4216], [160.581,   61.0254], [161.889, 61.0254], [163.219, 61.0254], [164.546,   61.0254], [165.873, 61.0254], [168.524, 61.0254], [169.852,   60.6291], [171.173, 60.6291], [172.494, 60.6291], [173.83,   60.6291], [175.16, 60.6291], [176.473, 60.6291], [179.122,   60.2328], [180.459, 60.2328], [181.78, 60.2328], [183.106,   59.8366], [184.439, 59.8366], [185.757, 59.8366], [187.079,   59.8366], [189.73, 59.4403], [191.06, 59.4403], [192.386,   59.044], [193.717, 59.044], [195.037, 59.044], [196.372,   58.6478], [197.693, 58.6478], [200.361, 58.2515], [201.689,   57.8552], [203.008, 57.8552], [204.335, 57.459], [205.651,   57.459], [206.996, 57.0627], [208.324, 57.0627], [210.977,   56.6664], [212.292, 56.2702], [213.602, 55.8739], [214.947,   55.8739], [216.259, 55.4776], [217.583, 55.0813], [218.895,   55.0813], [221.56, 54.2888], [222.865, 53.8925], [224.195,   53.4963], [225.505, 53.1], [226.824, 53.1], [228.192,   52.7037], [229.508, 52.3075], [232.191, 51.5149], [233.507,   51.1187], [234.836, 50.7224], [236.161, 50.3261], [237.479,   49.9299], [238.807, 49.5336], [240.13, 48.741], [242.774,   47.9485], [244.096, 47.5522], [245.417, 47.156], [246.74,   46.7597], [248.057, 46.3634], [249.382, 45.5709], [250.457,   46.2508], [251.117, 46.0365], [252.897, 45.4166], [253.112,   45.3033], [254.115, 44.9356], [255.112, 44.5707], [256.107,   44.2179], [257.936, 43.554], [259.077, 43.1208], [260.395,   42.633], [260.555, 42.5688], [261.881, 42.0749], [263.515,   41.4803], [264.389, 41.108], [264.559, 41.047], [264.724,   40.9939], [264.891, 40.9305], [265.058, 40.8548], [265.219,   40.807], [265.384, 40.7401], [265.549, 40.6799], [265.707,   40.6116], [265.872, 40.5381], [266.041, 40.4919], [266.218,   40.4131], [266.379, 40.365], [266.532, 40.2826], [266.705,   40.2386], [266.876, 40.1762], [267.038, 40.1109], [267.201,   40.0532], [267.373, 39.9972], [267.543, 39.9247], [267.71,   39.8555], [267.874, 39.7915], [268.035, 39.7336], [268.194,   39.6811], [268.367, 39.6056], [268.542, 39.5387], [268.705,   39.4762], [268.866, 39.3997], [269.029, 39.3423], [269.201,   39.2861], [269.374, 39.22], [269.539, 39.1538], [269.698,   39.0823], [269.87, 39.0231], [270.036, 38.9709], [270.194,   38.8925], [270.371, 38.8394], [270.536, 38.7638], [270.697,   38.7051], [270.868, 38.6425], [271.038, 38.574], [271.208,   38.5115], [271.367, 38.4455], [271.54, 38.3782], [271.711,   38.3156], [271.868, 38.2524], [272.03, 38.1917], [272.2,   38.1262], [272.361, 38.0706], [272.525, 38.0021], [274.156,   37.3821], [274.229, 37.3188], [274.362, 37.27], [274.524,   37.2048], [274.705, 37.1411], [274.877, 37.0827], [275.029,   37.0104], [275.196, 36.9607], [275.37, 36.8851], [275.526,   36.8172], [275.683, 36.7629], [275.855, 36.6982], [276.025,   36.6346], [276.185, 36.5695], [276.345, 36.5063], [276.506,   36.4347], [276.68, 36.3789], [276.845, 36.315], [277.012,   36.2632], [277.184, 36.1689], [277.35, 36.1295], [277.517,   36.0462], [277.676, 35.9975], [277.839, 35.9304], [278.009,   35.8579], [278.18, 35.8111], [278.341, 35.7453], [278.5,   35.6707], [278.67, 35.6269], [278.836, 35.5542], [279.005,   35.486], [279.176, 35.4266], [279.333, 35.3622], [279.502,   35.2938], [279.671, 35.2304], [279.833, 35.1688], [279.997,   35.1014], [280.169, 35.0437], [280.337, 34.9787], [280.498,   34.9181], [280.67, 34.8453], [280.837, 34.7763], [280.995,   34.7195], [281.164, 34.6492], [281.332, 34.5889], [281.5,   34.523], [281.664, 34.4628], [281.83, 34.3953], [282.004,   34.3438], [282.164, 34.2835], [282.324, 34.214], [282.493,   34.1503], [282.67, 34.087], [282.833, 34.0348], [282.996,   33.9549], [283.164, 33.9051], [284.809, 33.291], [284.864,   33.2491], [284.993, 33.1839], [285.16, 33.111], [285.323,   33.0541], [285.487, 32.9876], [285.655, 32.9227], [285.821,   32.8639], [285.991, 32.8064], [286.164, 32.7342], [286.329,   32.6806], [286.494, 32.606], [286.66, 32.562], [286.824,   32.4928], [286.985, 32.4283], [287.149, 32.3634], [287.321,   32.2873], [287.487, 32.2322], [287.644, 32.1765], [287.817,   32.1208], [287.988, 32.0552], [288.146, 31.9866], [288.306,   31.9345], [288.476, 31.8621], [288.646, 31.8045], [288.815,   31.7536], [288.985, 31.6971], [289.14, 31.6201], [289.307,   31.551], [289.481, 31.4893], [289.645, 31.444], [289.81,   31.3815], [289.975, 31.311], [290.141, 31.252], [290.306,   31.1829], [290.466, 31.1326], [290.636, 31.0702], [290.807,   31.011], [290.967, 30.9446], [291.139, 30.8823], [291.304,   30.8283], [291.47, 30.7563], [291.646, 30.706], [291.809,   30.6516], [291.974, 30.578], [292.141, 30.5155], [292.306,   30.4571], [292.47, 30.3986], [292.634, 30.3283], [292.805,   30.2715], [292.971, 30.212], [293.127, 30.1523], [293.299,   30.0891], [293.475, 30.0263], [293.634, 29.9634], [293.794,   29.9016], [295.41, 29.3469], [295.467, 29.2859], [295.612,   29.2319], [295.787, 29.1886], [295.954, 29.1198], [296.116,   29.0857], [296.277, 29.0178], [296.445, 28.9598], [296.617,   28.9062], [296.78, 28.8415], [296.946, 28.7719], [297.111,   28.7037], [297.277, 28.6442], [297.444, 28.5661], [297.603,   28.5065], [297.772, 28.438], [297.945, 28.3781], [298.11,   28.3183], [298.27, 28.2553], [298.436, 28.1991], [298.608,   28.1362], [298.775, 28.089], [298.934, 28.0215], [299.089,   27.9536], [299.26, 27.8898], [299.431, 27.8379], [299.596,   27.7736], [299.77, 27.7224], [299.939, 27.6707], [300.1,   27.6075], [300.268, 27.5584], [300.436, 27.4834], [300.595,   27.445], [300.761, 27.3805], [300.928, 27.3191], [301.085,   27.2599], [301.248, 27.2049], [301.416, 27.1372], [301.582,   27.0904], [301.742, 27.0381], [301.902, 26.9595], [302.077,   26.9182], [302.254, 26.8639], [302.409, 26.7965], [302.573,   26.751], [302.744, 26.6902], [302.904, 26.6289], [303.075,   26.5635], [303.24, 26.5116], [303.398, 26.4599], [303.571,   26.3952], [303.75, 26.3447], [303.915, 26.2879], [304.07,   26.2416], [304.236, 26.1701], [304.396, 26.1088]]

#print(exp[0][0])

def dchi(a, b, c, d):
    sd=0
    for i in range(len(exp)):
        CH_bound = functools.partial(CH, T=exp[i][0], a=a, b=b, c=c, d=d, H=315, TC=1000, C=2)
        ch1 = sp.optimize.root_scalar(f=CH_bound, x0=0)
        sd=sd+(ch1.root - exp[i][1]) ** 2
    return sd

#print(dchi(1,1,1,1))

first_guess = [1,1,1,1]
res = sp.optimize.minimize(dchi, first_guess)

#print(res)

the errors:

Traceback (most recent call last):
  File "C:\Users\...\PycharmProjects\pythonProject6\main.py", line 23, in <module>
    res = sp.optimize.minimize(dchi, first_guess)
  File "C:\Users\...\PycharmProjects\pythonProject6\venv\lib\site-packages\scipy\optimize\_minimize.py", line 705, in minimize
    res = _minimize_bfgs(fun, x0, args, jac, callback, **options)
  File "C:\Users\...\PycharmProjects\pythonProject6\venv\lib\site-packages\scipy\optimize\_optimize.py", line 1419, in _minimize_bfgs
    sf = _prepare_scalar_function(fun, x0, jac, args=args, epsilon=eps,
  File "C:\Users\...\PycharmProjects\pythonProject6\venv\lib\site-packages\scipy\optimize\_optimize.py", line 383, in _prepare_scalar_function
    sf = ScalarFunction(fun, x0, args, grad, hess,
  File "C:\Users\...\PycharmProjects\pythonProject6\venv\lib\site-packages\scipy\optimize\_differentiable_functions.py", line 158, in __init__
    self._update_fun()
  File "C:\Users\...\PycharmProjects\pythonProject6\venv\lib\site-packages\scipy\optimize\_differentiable_functions.py", line 251, in _update_fun
    self._update_fun_impl()
  File "C:\Users\...\PycharmProjects\pythonProject6\venv\lib\site-packages\scipy\optimize\_differentiable_functions.py", line 155, in update_fun
    self.f = fun_wrapped(self.x)
  File "C:\Users\...\PycharmProjects\pythonProject6\venv\lib\site-packages\scipy\optimize\_differentiable_functions.py", line 137, in fun_wrapped
    fx = fun(np.copy(x), *args)
TypeError: dchi() missing 3 required positional arguments: 'b', 'c', and 'd'

Process finished with exit code 1

Solution

  • To solve at least a couple of your problems, use a complex, properly unpack your parameters, and check root convergence:

    import functools
    
    import numpy as np
    import scipy as sp
    
    
    def CH(
        chi: float, T: float, a: float, b: float, c: float, d: float, H: float, TC: float, C: float,
    ) -> float:
        chi = chi + 0j
        err = (
            a*(T - TC)*chi**(1/c)
            + b * chi**(1/c) * chi**(1/d) * H**(1/d)
            + C/(T-TC)
            - 1
        )
        return np.abs(err)
    
    
    exp = (
        (10.0052, 87.9716), (11.0433, 86.3866), (12.359, 84.8015),
        (169.852,   60.6291), (171.173, 60.6291), (172.494, 60.6291), (173.83,   60.6291),
    )
    
    
    def dchi(params: np.ndarray) -> float:
        a, b, c, d = params
        sd = 0
        x0 = 0
        for exp_a, exp_b in exp:
            CH_bound = functools.partial(CH, T=exp_a, a=a, b=b, c=c, d=d, H=315, TC=1000, C=2)
            ch1 = sp.optimize.root_scalar(
                f=CH_bound, x0=x0,
            )
            assert ch1.converged
            sd += (ch1.root - exp_b)**2
            x0 = ch1.root
        return sd
    
    
    first_guess = (1,1,1,1)
    res = sp.optimize.minimize(
        fun=dchi,
        x0=first_guess,
    )
    assert res.success, res.message
    print(res.x)
    

    This does get further, but in context your function is problematic and eventually doesn't converge. Are there bounds on any of the variables you've shown? You really do need to find out what these should be.

    Anyway, it's a bad idea to run an inner root-find. You should be running a single upper-level minimize with a vectorized root constraint; and don't loop.

    With your chi function as

    import numpy as np
    from scipy.optimize import NonlinearConstraint, minimize
    
    
    def CH(
        chi: np.ndarray, T: float, H: float, TC: float, C: float, a: float, b: float, c: float, d: float,
    ) -> np.ndarray:
        err = (
            a*(T - TC)*chi**(1/c)
            + b * chi**(1/c) * chi**(1/d) * H**(1/d)
            + C/(T-TC)
            - 1
        )
        return err
    

    then a (slow, but) successful optimization can look like

    def dchi(params: np.ndarray) -> float:
        chi = params[4:]
        chi_err = chi - exp_chi
        err = chi_err.dot(chi_err)
        return err
    
    def dchi_jac(params: np.ndarray) -> np.ndarray:
        chi = params[4:]
        jac = np.zeros_like(params)
        jac[4:] = 2*(chi - exp_chi)
        return jac
    
    def dchi_root(params: np.ndarray) -> np.ndarray:
        # for each value of exp_T, there is some unknown optimal value of chi such that
        # CH(chi, T) == 0
        abcd, chi = np.split(params, (4,))
        a, b, c, d = abcd
        root_err = CH(
            chi=chi, T=exp_T, a=a, b=b, c=c, d=d, H=315, TC=1000, C=2,
        )
        return root_err
    
    exp_T, exp_chi = np.array(exp).T
    first_guess = np.concatenate((
        (0.79, 0.00893, 0.428, 1.38),
        exp_chi,
    ))
    result = minimize(
        fun=dchi, jac=dchi_jac,
        x0=first_guess,
        constraints=NonlinearConstraint(
            fun=dchi_root,
            lb=0, ub=0,
        ),
        options={'maxiter': 10, 'disp': True},
    )
    print(result.message)
    print(f'Chi error: {result.fun:.3f}')
    print(f'Root error: {np.abs(dchi_root(result.x)).sum():.3f}')
    print(f'Parameters: {result.x[:4]}')
    
    Iteration limit reached    (Exit mode 9)
                Current function value: 1543.01943809294
                Iterations: 10
                Function evaluations: 16
                Gradient evaluations: 10
    Iteration limit reached
    Chi error: 1543.019
    Root error: 57.920
    Parameters: [-1.61941337e-05 -7.62007792e-06  6.02274220e-01  1.32563818e+00]
    

    This will do better if you give it more iterations to run, and especially if you write a second Jacobian for the root.