pythonmpmath

How do you use the **kwargs argument in mpmath.findroot?


I am trying to find the roots of a function that has a lot of arguments using findroot in mpmath, but in this question, I am going to use a simple function.

from mpmath import mp


def func(x, **parameters):

 return parameters["a"]*x*x + 1
   

solution = mp.findroot(f = func, x0 = 0.955j, solver = 'muller', **kwargs = (a = 1))

Output:

SyntaxError: invalid syntax

The func function in the code returns the value of $f(x) = ax^2 + 1$ at an $x$-value, given the value of $a$. For example, the value of $f(1)$ for $a = 1$ is 2. To find the roots of $f$, I used findroot. Since my function has arguments, I will need to use the **kwargs in findroot. However, I am struggling to use it. I keep on getting a syntax error.


Solution

  • The syntax error

    The syntax error is in assigning **kwargs = . The variable kwargs is a dictionary, and ** unpacks that dictionary. The expression **kwargs = (a = 1) is essentially trying to unpack a dictionary while also assigning that dictionary to another variable assignment.

    Keyword arguments

    The **kwargs parameter of a function allows you to pass any number of additional keyword arguments. The parameter a=1 is a keyword argument. The kwargs itself is a collection of keyword arguments that you want to "package together".

    A quick demonstration:

    In [1]: def print_kwargs(**kwargs):
       ...:     for key, value in kwargs.items():
       ...:         print(key, value)
       ...:
    
    In [2]: print_kwargs(a=1, b=2, c=3)
    a 1
    b 2
    c 3
    
    In [3]: params = dict(a=1, b=2, c=3)
    
    In [4]: print_kwargs(**params)
    a 1
    b 2
    c 3
    

    The actual problem

    Looking at the documentation, it looks like the solver is allowed to accept whatever kwargs you pass. In other words, the kwargs aren't passed to your function, func, they're passed to the solver, 'muller' in your case.

    The docs suck and the kwargs that the mpmath.calculus.optimization.Muller takes aren't explained at all as far as I can see so I have no idea what it does with its keyword arguments, but the point is that the kwargs aren't being passed to your function as you expected.

    Unfortunately you'll need to do a bit more work on your function func if you want it to be a dynamic polynomial which can take arguments for its parameters.

    The solution

    Here's what you can do:

    def function_builder(**params):
        def func(x):
            return params["a"] * x**2 + 1  # or whatever function
        return func
    

    And then you'd do:

    >>> params = dict(a=1)
    >>> func = function_builder(**params)
    >>> mp.findroot(f=func, x0=0.955j, solver='muller')
    mpc(real='0.0', imag='1.0')
    

    You can implement function_builder() in such a way as to return a new function object which is only a function of x. Then when you want to find the roots of a particular polynomial, you can call function_builder() with whatever parameters you want and it'll return the function you want to which mp.findroot can then use.

    Notes

    Here's an example of how you might implement a general quadratic equation builder:

    def quadratic_builder(**params):
        a, b, c = params["a"], params["b"], params["c"]
        def f(x):
            return a * x**2 + b * x + c
        return f
    

    Of course, for something as specific as a quadratic equation, it'd be better to explicitly call out the keyword arguments:

    def quadratic_builder(*, a, b, c):
        def f(x):
            return a * x**2 + b * x + c
        return f
    

    And then you can package together "sets" of coefficients:

    coefs_1 = dict(a=1, b=3, c=-5)
    coefs_2 = dict(a=-2, b=1, c=0)
    coefs_3 = dict(a=-6, b=8, c=3)
    
    f1 = quadratic_builder(**coefs_1)
    f2 = quardatic_builder(**coefs_2)
    f3 = quardatic_builder(**coefs_3)
    

    Etc.