Using adaptive step sizes with scipy.integrate.ode

前端 未结 5 1734
忘掉有多难
忘掉有多难 2020-12-08 08:01

The (brief) documentation for scipy.integrate.ode says that two methods (dopri5 and dop853) have stepsize control and dense output. L

相关标签:
5条回答
  • 2020-12-08 08:03

    The integrate method accepts a boolean argument step that tells the method to return a single internal step. However, it appears that the 'dopri5' and 'dop853' solvers do not support it.

    The following code shows how you can get the internal steps taken by the solver when the 'vode' solver is used:

    import numpy as np
    from scipy.integrate import ode
    import matplotlib.pyplot as plt
    
    
    def logistic(t, y, r):
        return r * y * (1.0 - y)
    
    r = .01
    
    t0 = 0
    y0 = 1e-5
    t1 = 5000.0
    
    backend = 'vode'
    #backend = 'dopri5'
    #backend = 'dop853'
    solver = ode(logistic).set_integrator(backend)
    solver.set_initial_value(y0, t0).set_f_params(r)
    
    sol = []
    while solver.successful() and solver.t < t1:
        solver.integrate(t1, step=True)
        sol.append([solver.t, solver.y])
    
    sol = np.array(sol)
    
    plt.plot(sol[:,0], sol[:,1], 'b.-')
    plt.show()
    

    Result: Plot of the solution

    0 讨论(0)
  • 2020-12-08 08:13

    FYI, although an answer has been accepted already, I should point out for the historical record that dense output and arbitrary sampling from anywhere along the computed trajectory is natively supported in PyDSTool. This also includes a record of all the adaptively-determined time steps used internally by the solver. This interfaces with both dopri853 and radau5 and auto-generates the C code necessary to interface with them rather than relying on (much slower) python function callbacks for the right-hand side definition. None of these features are natively or efficiently provided in any other python-focused solver, to my knowledge.

    0 讨论(0)
  • 2020-12-08 08:14

    Here's another option that should also work with dopri5 and dop853. Basically, the solver will call the logistic() function as often as needed to calculate intermediate values so that's where we store the results:

    import numpy as np
    from scipy.integrate import ode
    import matplotlib.pyplot as plt
    
    sol = []
    def logistic(t, y, r):
        sol.append([t, y])
        return r * y * (1.0 - y)
    
    r = .01
    
    t0 = 0
    y0 = 1e-5
    t1 = 5000.0
    # Maximum number of steps that the integrator is allowed 
    # to do along the whole interval [t0, t1].
    N = 10000
    
    #backend = 'vode'
    backend = 'dopri5'
    #backend = 'dop853'
    solver = ode(logistic).set_integrator(backend, nsteps=N)
    solver.set_initial_value(y0, t0).set_f_params(r)
    
    # Single call to solver.integrate()
    solver.integrate(t1)
    sol = np.array(sol)
    
    plt.plot(sol[:,0], sol[:,1], 'b.-')
    plt.show()
    
    0 讨论(0)
  • 2020-12-08 08:18

    I've been looking at this to try to get the same result. It turns out you can use a hack to get the step-by-step results by setting nsteps=1 in the ode instantiation. It will generate a UserWarning at every step (this can be caught and suppressed).

    import numpy as np
    from scipy.integrate import ode
    import matplotlib.pyplot as plt
    import warnings
    
    
    def logistic(t, y, r):
        return r * y * (1.0 - y)
    
    r = .01
    t0 = 0
    y0 = 1e-5
    t1 = 5000.0
    
    #backend = 'vode'
    backend = 'dopri5'
    #backend = 'dop853'
    
    solver = ode(logistic).set_integrator(backend, nsteps=1)
    solver.set_initial_value(y0, t0).set_f_params(r)
    # suppress Fortran-printed warning
    solver._integrator.iwork[2] = -1
    
    sol = []
    warnings.filterwarnings("ignore", category=UserWarning)
    while solver.t < t1:
        solver.integrate(t1, step=True)
        sol.append([solver.t, solver.y])
    warnings.resetwarnings()
    sol = np.array(sol)
    
    plt.plot(sol[:,0], sol[:,1], 'b.-')
    plt.show()
    

    result:

    0 讨论(0)
  • 2020-12-08 08:23

    Since SciPy 0.13.0,

    The intermediate results from the dopri family of ODE solvers can now be accessed by a solout callback function.

    import numpy as np
    from scipy.integrate import ode
    import matplotlib.pyplot as plt
    
    
    def logistic(t, y, r):
        return r * y * (1.0 - y)
    
    r = .01
    t0 = 0
    y0 = 1e-5
    t1 = 5000.0
    
    backend = 'dopri5'
    # backend = 'dop853'
    solver = ode(logistic).set_integrator(backend)
    
    sol = []
    def solout(t, y):
        sol.append([t, *y])
    solver.set_solout(solout)
    solver.set_initial_value(y0, t0).set_f_params(r)
    solver.integrate(t1)
    
    sol = np.array(sol)
    
    plt.plot(sol[:,0], sol[:,1], 'b.-')
    plt.show()
    

    Result:

    The result seems to be slightly different from Tim D's, although they both use the same backend. I suspect this having to do with FSAL property of dopri5. In Tim's approach, I think the result k7 from the seventh stage is discarded, so k1 is calculated afresh.

    Note: There's a known bug with set_solout not working if you set it after setting initial values. It was fixed as of SciPy 0.17.0.

    0 讨论(0)
提交回复
热议问题