Search code examples
pythondictionaryscipynumerical-integrationodeint

passing dictionaries of parameters and initial values to scipy.integrate.odeint


I'm trying to integrate a system of differential equations using spicy.itegrate.odeint.

First, parameters and initial conditions are sampled and returned in two dictionaries (x0 and p). Then the model is created and written as a function to a file, looking roughly as follows (with dummy equations):

def model(x, t, p):
    xdot = [
        x['rate1'], p["a"]
        x['rate2'], p["b"] * x["state1"] - p["c"] * x["state2"]
        x['rate3'], p["c"] * x["state2"]
        x["state4"], x["rate1"] + x["rate2"] 
        x["state5"], - x["rate2"] + x["rate3"]
        ]
    return xdot

This is so that I can easily generate different models from simple inputs. Thus, what might normally be hardcoded variables, are now keys in a dictionary with the corresponding value. I do this because assigning variables dynamically is considered bad practice.

When I try to integrate the system using odeint, as follows

sol = odeint(model, x0, t, args=(p,),
              atol=1.0e-8, rtol=1.0e-6)

where, thus, x0 is a dictionary of initial conditions, and p of parameters (and t a list of floats). I get the following error:

TypeError: float() argument must be a string or a number, not 'dict'

Obviously, scipy is not happy with my attempt to pass a dictionary to parameterize and initialize my model. The question is if there is a way for me to resolve this, or whether I am forced to assign all values in my dictionary to variables with the name of their corresponding key. The latter does not allow me to pass the same set of initial conditions and parameters to all models, since they differ both in states and parameters. Thus, wish to pass the same set of parameters to all models, regardless of wether the parameters are in the model or not.


Solution

  • For performance reasons scipy functions like odeint work with arrays where each parameter is associated with a fixed position.

    A solution to access parameters by name is to convert them to a namedtuple which gives them both, a name and a position. However, the conversion needs to be done inside the function because odeint passes the parameters as a numpy.array to the model function.

    This example should convey the idea:

    from scipy.integrate import odeint
    from collections import namedtuple
    
    params = namedtuple('params', ['a', 'b', 'c', 'd'])
    
    def model(x, t0):
        x = params(*x)
        xdot = [1, 
                x.a + x.b, 
                x.c / x.a, 
                1/x.d**2]  # whatever
        return xdot
    
    
    x0 = params(a=1, b=0, c=2, d=0.5)
    
    t = [0, 0.5, 1]
    
    sol = odeint(model, x0, t)