Heat Transfer
Heat Transfer

Reputation: 1

fsolve (scipy) results in ValueError

I am solving so called Blasius problem. I use the following code (copied from youtube lecutre https://www.youtube.com/watch?v=0L4h-hqZY2Y , timecode: 8:35):

import numpy as np
from scipy.integrate import odeint
from scipy.optimize import fsolve

eta = np.linspace(0, 10, 101)

def blas(f, t):
    return (f[1], f[2], -(1/2)*f[2]*f[0])

def blas0(x, ts):
    f0 = (0., 0., x)
    f = odeint(blas, f0, ts)
    return 1.-f[-1, 1]

print(blas0(0.0, eta))
print(blas0(0.1, eta))
print(blas0(0.2, eta))
print(blas0(0.332, eta))
print(eta)

xpp0 = fsolve(blas0, x0=0.1, args=(eta), xtol=1e-6)

All the listed print-functions work well and print correct results. However, the function fsolve results in the following error:

xpp0 = fsolve(blas0, x0=0.1, args=(eta), xtol=1e-6)
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
ValueError: setting an array element with a sequence. The requested 
array has an inhomogeneous shape after 1 dimensions. The detected 
shape was (3,) + inhomogeneous part.

Neither of these methods work.

The packages list:

Package         Version
--------------- -----------
contourpy       1.3.0
CoolProp        6.6.0
cycler          0.12.1
fonttools       4.53.1
kiwisolver      1.4.7
matplotlib      3.9.2
numpy           2.1.1
packaging       24.1
pandas          2.2.2
pillow          10.4.0
pip             24.2
pyparsing       3.1.4
PyQt6           6.7.1
PyQt6-Qt6       6.7.2
PyQt6_sip       13.8.0
python-dateutil 2.9.0.post0
pytz            2024.2
scipy           1.14.1
six             1.16.0
tzdata          2024.1

Does anybody knows what the problem can be?

Upvotes: 0

Views: 82

Answers (2)

James
James

Reputation: 36623

Your issue is that fsolve expects an sequence for x0 and it is silently converting the a sequence when a number is passed. And your need to pass in args as a list or tuple.

You can fix this by just pulling the first element from x in blas0

def blas0(x, ts):
    x = list(x)[0]
    f0 = (0., 0., x)
    f = odeint(blas, f0, ts)
    return 1.-f[-1, 1]

Here is the full solution:

import numpy as np
from scipy.integrate import odeint
from scipy.optimize import fsolve

eta = np.linspace(0, 10, 101)

def blas(f, t):
    return (f[1], f[2], -(1/2)*f[2]*f[0])

def blas0(x, ts):
    x = list(x)[0]
    f0 = (0., 0., x)
    f = odeint(blas, f0, ts)
    return 1.-f[-1, 1]

xpp0 = fsolve(blas0, x0=0.1, args=(eta,), xtol=1e-6)
print(xpp0)
# prints: [0.33205733]

Upvotes: 1

hpaulj
hpaulj

Reputation: 231395

Here's the FULL error message

In [124]: xpp0 = fsolve(blas0, x0=0.1, args=(eta), xtol=1e-6)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[124], line 1
----> 1 xpp0 = fsolve(blas0, x0=0.1, args=(eta), xtol=1e-6)

File ~\miniconda3\lib\site-packages\scipy\optimize\_minpack_py.py:163, in fsolve(func, x0, args, fprime, full_output, col_deriv, xtol, maxfev, band, epsfcn, factor, diag)
     51 """
     52 Find the roots of a function.
     53 
   (...)
    153 
    154 """
    155 options = {'col_deriv': col_deriv,
    156            'xtol': xtol,
    157            'maxfev': maxfev,
   (...)
    160            'factor': factor,
    161            'diag': diag}
--> 163 res = _root_hybr(func, x0, args, jac=fprime, **options)
    164 if full_output:
    165     x = res['x']

File ~\miniconda3\lib\site-packages\scipy\optimize\_minpack_py.py:229, in _root_hybr(func, x0, args, jac, col_deriv, xtol, maxfev, band, eps, factor, diag, **unknown_options)
    227 if not isinstance(args, tuple):
    228     args = (args,)
--> 229 shape, dtype = _check_func('fsolve', 'func', func, x0, args, n, (n,))
    230 if epsfcn is None:
    231     epsfcn = finfo(dtype).eps

File ~\miniconda3\lib\site-packages\scipy\optimize\_minpack_py.py:26, in _check_func(checker, argname, thefunc, x0, args, numinputs, output_shape)
     24 def _check_func(checker, argname, thefunc, x0, args, numinputs,
     25                 output_shape=None):
---> 26     res = atleast_1d(thefunc(*((x0[:numinputs],) + args)))
     27     if (output_shape is not None) and (shape(res) != output_shape):
     28         if (output_shape[0] != 1):

Cell In[123], line 13, in blas0(x, ts)
     11 def blas0(x, ts):
     12     f0 = (0., 0., x)
---> 13     f = odeint(blas, f0, ts)
     14     return 1.-f[-1, 1]

File ~\miniconda3\lib\site-packages\scipy\integrate\_odepack_py.py:242, in odeint(func, y0, t, args, Dfun, col_deriv, full_output, ml, mu, rtol, atol, tcrit, h0, hmax, hmin, ixpr, mxstep, mxhnil, mxordn, mxords, printmessg, tfirst)
    240 t = copy(t)
    241 y0 = copy(y0)
--> 242 output = _odepack.odeint(func, y0, t, args, Dfun, col_deriv, ml, mu,
    243                          full_output, rtol, atol, tcrit, h0, hmax, hmin,
    244                          ixpr, mxstep, mxhnil, mxordn, mxords,
    245                          int(bool(tfirst)))
    246 if output[-1] < 0:
    247     warning_msg = _msgs[output[-1]] + " Run with full_output = 1 to get quantitative information."

ValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (3,) + inhomogeneous part.

Note that actual error is in the odeint call, not the fsolve itself. All those clipped lines mislead you and your readers.

An error message like this means that some argument is a "ragged" array, something with a mix of shapes, and it can't convert it to a regular numeric array. Searching SO for that "inhomogeneous" phrase will turn up lots of examples. But here we need to determine which odeint argument is bad, and why.

numpy versions, and up/down grades have nothing to do with this kind of error. Something about your code, and arguments you pass is wrong.

Using args=(eta) when it should be (eta,), a tuple looks suspicious, but doesn't fix this particular issue.


Testing blas0 calls is good, but we need to see exactly what fsolve is doing.

Adding a print line to blas0:

In [128]: def blas0(x, ts):
     ...:     f0 = (0., 0., x)
     ...:     print(x, f0, ts.shape)
     ...:     f = odeint(blas, f0, ts)
     ...:     return 1.-f[-1, 1]
     ...:     

In [129]: eta.shape
Out[129]: (101,)

In [130]: blas0(0.1, eta)
0.1 (0.0, 0.0, 0.1) (101,)
Out[130]: 0.5507916014363659

Now look at the fsolve:

In [131]: xpp0 = fsolve(blas0, x0=0.1, args=(eta,), xtol=1e-6)
[0.1] (0.0, 0.0, array([0.1])) (101,)
...

f0 is a tuple containing an array. Trying to make an array from that tuple raises your error

In [132]: np.array((0.0, 0.0, np.array([0.1])))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[132], line 1
----> 1 np.array((0.0, 0.0, np.array([0.1])))

ValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (3,) + inhomogeneous part.

changing blas0 so it treats x as a scalar, and it works

In [133]: def blas0(x, ts):
     ...:     f0 = (0., 0., x[0])
     ...:     print(x, f0, ts.shape)
     ...:     f = odeint(blas, f0, ts)
     ...:     return 1.-f[-1, 1]
     ...:     

In [134]: xpp0 = fsolve(blas0, x0=0.1, args=(eta,), xtol=1e-6)
[0.1] (0.0, 0.0, 0.1) (101,)
[0.1] (0.0, 0.0, 0.1) (101,)
[0.1] (0.0, 0.0, 0.1) (101,)
[0.1] (0.0, 0.0, 0.10000000149011612) (101,)
[0.28363035] (0.0, 0.0, 0.283630348083009) (101,)
[0.32424457] (0.0, 0.0, 0.3242445695754514) (101,)
[0.3318578] (0.0, 0.0, 0.33185780004663934) (101,)
[0.33205655] (0.0, 0.0, 0.3320565471407554) (101,)
[0.33205733] (0.0, 0.0, 0.3320573348245278) (101,)
[0.33205733] (0.0, 0.0, 0.3320573349034362) (101,)

In [135]: xpp0
Out[135]: array([0.33205733])

Upvotes: 1

Related Questions