1

I am solving a sensitivity analysis problem, the direct equation corresponds to an initial-value problem, and the adjoint problem is a boundary value problem. The initial value problem is solved (perfectly) with scipy.integrate.odeint() and the boundary value problem should be solved with scipy.integrate.solve_bvp(). I will share the 4 routines I did, to solve these problems and point later the execution error in a particular example.

ODEINT STUFF

def ode_sys(sol,t,A,B,D):
    """
    This function returns the order reduction for the ODE's (1) and (2)
    from Singh2020.pdf
    A , B, D: are (known) scalar parameters
    x_sol , y_sol: are the vocal fold displacements
    u_sol , v_sol: are the vocal fold velocities
    You need to convert the 2-second order ODEs into 4-first order ODEs
    """
    
    x_sol , u_sol , y_sol , v_sol = sol
    
    x_der = u_sol
    u_der = (A - B * (1 + np.power(x_sol,2))) * u_sol + (0.5 *D-1) * x_sol + A * v_sol
    y_der = v_sol
    v_der = (A - B * (1 + np.power(y_sol,2))) * v_sol - (0.5 *D+1) * y_sol + A * u_sol
    
    return [x_der,u_der,y_der,v_der]

def ode_solver(A,B,D,sol_0,t):
    """
    This function integrates the coupled ODEs that represent the Van der Pol oscillators
    A     :: alpha
    B     :: beta
    D     :: delta
    sol_0 :: initial condition [x(0),x'(0),y(0),y'(0)]
    t     :: time array
    u0    :: computed glottal flow
    sol   :: displacements of both vocal folds vs time
    """
    # Here I define constants of the model (physiological props.)
    c_til,d_len,x_0 = physical_props() # they are scalar values
    
    sol   = odeint(ode_sys,sol_0,t,args=(A,B,D))
    u0    = c_til * d_len * (2 * x_0 + sol[:,0] + sol[:,2])
    
    return u0,sol

SOLVE_BVP STUFF

def adj_sys(adj_sol,t,voc_sol,data,A,B,D):
    """
    This function returns the order reduction for ODE's 20-23 from Singh2020B.pdf
    A , B, D: are (known) scalar parameters
    x_adj , y_adj: are the adjoint displacements
    u_adj , v_adj: are the adjoint velocities
    x_sol , y_sol: are the vocal fold displacements (known data)
    u_sol , v_sol: are the vocal fold velocities (known data)
    You need to convert the 2-second order ODEs into 4-first order ODEs
    """
    print('inside sys,adj',adj_sol.shape)
    print('inside sys,voc',voc_sol.shape)
    x_adj , u_adj , y_adj , v_adj = adj_sol
    x_sol , u_sol , y_sol , v_sol = voc_sol
    
    # Here I define constants of the model (physiological props.)
    c_til,d_len,x_0 = physical_props()
    u0    = c_til * d_len * (2 * x_0 + x_sol + y_sol)
    R     = u0 - data
    
    x_adj_der = u_adj
    u_adj_der = A * (x_adj + y_adj) - 2.0*c_til*d_len*R - B * (1.0 + np.power(x_sol,2)) * x_adj + (0.5*D - 1.0 - 2.0*B*x_sol*u_sol)
    y_adj_der = v_adj
    v_adj_der = A * (x_adj + y_adj) - 2.0*c_til*d_len*R - B * (1.0 + np.power(y_sol,2)) * y_adj - (0.5*D + 1.0 + 2.0*B*y_sol*v_sol)
    
    return [x_adj_der,u_adj_der,y_adj_der,v_adj_der]

def bc(x_adj , u_adj , y_adj , v_adj):
    """
    This function returns the boundary conditions from Eqs.24-27 from Singh2020B.pdf
    """
    # These return values are what we want to be 0:
    return x_adj[-1] , u_adj[-1] , y_adj[-1] , v_adj[-1]

def adj_solver(A,B,D,voc_sol,t):
    """
    This function integrates the coupled ODEs that represent the adjoint state for Van der Pol oscillators
    A       :: alpha
    B       :: beta
    D       :: delta
    voc_sol :: Vocal fold displacements and velocities
    t       :: time array
    """
    
    #result = solve_bvp(adj_sys, bc, t, voc_sol, voc_sol , data , A , B , D)
    
    result = solve_bvp(lambda y , t: adj_sys(y,t,voc_sol,data,A,B,D),
                       lambda y0, y1, y2, y3: bc(y0, y1, y2, y3),
                       t, voc_sol)
    
    adj_sol = [result.y[0],result.y[1],result.y[2],result.y[3]]
    
    return adj_sol

As you can see to solve the adjoint problem I first need to solve the initial value problem. And the solutions of the initial value problem are inputs for the adjoint problem...

The content of the main function

# Here I define constants of the model (physiological props.)
c_til,d_len,x_0 = physical_props()
sol_0 = [0,0.1,0,0.1] # Initial state from papers (reference) (x(0),x'(0),y(0),y'(0))
theta = [c_til,d_len,x_0]

# Here I define a synthetic dataset just to test the integration
D     = 0.3
A     = 0.6 * D
B     = 0.2

t     = np.linspace(0,60*pi,1000)
data,voc_sol  = ode_solver(A,B,D,sol_0,t)

# Solve the adjoint state
print(voc_sol.T.shape)
adj_state = adj_solver(A,B,D,voc_sol.T,t)

So to summarize ode_solver() works perfectly, the results are as expected. But adj_solver() fails to produce the solution showing the following error message:

(4, 1000)
inside sys,adj (1000,)
inside sys,voc (4, 1000)
Traceback (most recent call last):
  File "demo.py", line 166, in <module>
    adj_state = adj_solver(A,B,D,voc_sol.T,t)
  File "demo.py", line 123, in adj_solver
    t, voc_sol)
  File "/home/valdez/anaconda3/lib/python3.7/site-packages/scipy/integrate/_bvp.py", line 1063, in solve_bvp
    f = fun_wrapped(x, y, p)
  File "/home/valdez/anaconda3/lib/python3.7/site-packages/scipy/integrate/_bvp.py", line 648, in fun_p
    return np.asarray(fun(x, y), dtype)
  File "demo.py", line 121, in <lambda>
    result = solve_bvp(lambda y , t: adj_sys(y,t,voc_sol,data,A,B,D),
  File "demo.py", line 87, in adj_sys
    x_adj , u_adj , y_adj , v_adj = adj_sol
ValueError: too many values to unpack (expected 4)

See in the terminal prints, the arrays for the adjoint solver are wrong... Even if I supplied the initial guess with the correct shape array.

Any help, advice, and suggestion will be very well-acknowledged, will save my day.

I forgot to add the answers I used to write the adjoint solver Answer 1 and Answer 2 Both of them used solve_bvp()... In my case I am having trouble making a callable function without extra arguments.

Andres Valdez
  • 164
  • 17
  • Your immediate error is the format of the ODE function. All scipy solvers except `odeint` (with default `tfirst=False`) demand the order `t,y`. Effectively you try to resolve the one time value into the tuple of space components. The next, or one of the next, errors will be that `bc` only takes 2 arguments, or 3 if a parameter tuple is in play. – Lutz Lehmann Mar 15 '21 at 15:53
  • Sorry, @LutzLehmann I didn't catch you up. Here the tutorial [https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_bvp.html] wrote `res_a = solve_bvp(fun, bc, x, y_a)` with `x` as indep. variable, I followed that order... – Andres Valdez Mar 16 '21 at 13:42
  • This was not about the arguments of `solve_bvp`, but about the arguments of the functions that are passed to it. As a minimum, you need to switch to `lambda t,y:adj_sys(y,t,...)`. `bc` gets the states at the start and end of the integration interval as arguments, not the components of one of them. – Lutz Lehmann Mar 16 '21 at 13:53
  • @LutzLehmann Ok I got the point on `adj_sys`. I must confess I am not getting the `bc` part... I want null at the end, which means at the beginning it's null "derivative"... How can I impose null at the end? – Andres Valdez Mar 16 '21 at 14:37
  • @LutzLehmann In addition, following your answer here [https://stackoverflow.com/a/47860518/9185675], how can I make a numpy function interpolating the external arrays `voc_sol` inside `adj_sys`? – Andres Valdez Mar 16 '21 at 14:46
  • @LutzLehmann Ok, I think I got the interpolation method... `np.interp()` can handle that... – Andres Valdez Mar 16 '21 at 14:52
  • There is also `scipy.interpolate.interp1d` that generates a function for multiple interpolation calls. For the end conditions to be all zero use `def bc(u0,ue): return ue`. But in that case you can also just integrate backwards. – Lutz Lehmann Mar 16 '21 at 15:03
  • All-in-all, it is questionable if you need to *integrate* backwards. Forwards integration can also compute the step Jacobians, and for the backwards sweep you only need to collect them. As the coefficients A,...,D are constant, there is not much overhead in computing all partial derivatives in forward mode. It would be different if they were changing, piecewise linear functions. Then you have a many-to-one function where the sensitivities are best computed with in gradient backwards mode. – Lutz Lehmann Mar 16 '21 at 15:04
  • @LutzLehmann Yes, I was told about backward integration... I think is much more simpler that way.... – Andres Valdez Mar 16 '21 at 15:24

0 Answers0