Understanding scipy integrate's internal behavior

后端 未结 1 842
梦谈多话
梦谈多话 2021-01-29 01:58

I am trying to understand what scipy.integrate is doing internally. Namely, it seems that something weird and inconsistent is happening.

How get it

1条回答
  •  抹茶落季
    2021-01-29 02:22

    All solvers that are better than the standard fixed-step RK4 method use a variable step size. Treating the solver as a black-box, one can not know what internal step sizes are used.

    What is known, however, is that the explicit one-step methods have multiple stages, at least equal to their order, that each comprise a call to the ODE function at a point close to, but not necessarily on the solution trajectory. Implicit methods may have less stages than the order, but require an iterative approach to the solution of the implicit step equations.

    The Dormand-Prince 45 method has 7 stages, where the last stage is also the first of the next step, so in the long-time average 6 evaluations per step. This is what you see in the ode(dopri) method.

    INTERNAL t =   0.00000000
    INTERNAL t =   0.01000000
    INTERNAL t =   0.00408467
    INTERNAL t =   0.00612700
    INTERNAL t =   0.01633866
    INTERNAL t =   0.01815407
    INTERNAL t =   0.02042333
    INTERNAL t =   0.02042333
    INTERNAL t =   0.03516563
    INTERNAL t =   0.04253677
    INTERNAL t =   0.07939252
    INTERNAL t =   0.08594465
    INTERNAL t =   0.09413482
    INTERNAL t =   0.09413482
    Outside integrate t =   0.09413482
    ...
    

    There one can see that the minimal step of the scipy method consists of 2 DoPri steps. In the sequence of the first step, the first evaluation is just probing if the initial step size is appropriate, this is only done once. All the other step points are at the prescribed times t_n+c_i*dt where c=[0,1/5,3/10,4/5,8/9,1,1].

    You can get proper single steps with the new classes that are the steppers for the new interface solve_ivp. Take care that the default tolerances are here much looser than in the ode(dopri) case, probably following the Matlab philosophy of generating "good enough" plots with minimal effort. For RK45 this can look like

    simulator = RK45(myODE, t0, [1,1], t1, atol=6.8e-7, rtol=2.5e-8)
    
    t = simulator.t
    
    while t < t1:
        simulator.step() 
        t = simulator.t
        x = simulator.y
        print(f'Outside integrate t = {t:12.8f}')
        print(f'x1 = {x[0]:12.10f}, err = {x[0]-1/(1+t):8.6g}')
    

    This uses slightly different internal steps, but, as said, has a "true" single-step output.

    INTERNAL t =   0.00000000
    INTERNAL t =   0.01000000
    INTERNAL t =   0.00408223
    INTERNAL t =   0.00612334
    INTERNAL t =   0.01632891
    INTERNAL t =   0.01814323
    INTERNAL t =   0.02041114
    INTERNAL t =   0.02041114
    Outside integrate t =   0.02041114
    x1 = 0.9799971436, err = 5.2347e-13
    INTERNAL t =   0.04750541
    INTERNAL t =   0.06105254
    INTERNAL t =   0.12878821
    INTERNAL t =   0.14083011
    INTERNAL t =   0.15588248
    INTERNAL t =   0.15588248
    Outside integrate t =   0.15588248
    x1 = 0.8651399668, err = 1.13971e-07
    ...
    

    If you have an input that is a step function, or a zero-order hold, the most expedient solution would be to loop over the steps and initialize one RK45 object per step with the step segment as integration boundaries. Save the last value as initial value for the next step. Perhaps also the last step size as initial step size in the next step.

    Directly using a step function inside the ODE function is inefficient, as the step size controller expects a very smooth ODE function for an optimal step size sequence. At jumps that is grossly violated and can lead to stark local reductions in the step size, and accordingly an increased number of function evaluations.

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