2012-10-17 11 views
18

La (breve) documentazione per scipy.integrate.ode dice che due metodi (dopri5 e dop853) hanno controllo di passi e output denso. Guardando gli esempi e il codice stesso, vedo solo un modo molto semplice per ottenere l'output da un integratore. Vale a dire, sembra che appena un passo in avanti l'integratore da alcuni dt fisso, ottiene il valore della funzione (s) in quel momento, e ripetere.Utilizzo di dimensioni di passo adattive con scipy.integrate.ode

Il mio problema ha tempi piuttosto variabili, quindi vorrei solo ottenere i valori in qualunque momento passi che deve valutare per raggiungere le tolleranze richieste. Cioè, presto, le cose stanno cambiando lentamente, quindi i passi del tempo di uscita possono essere grandi. Ma dato che le cose diventano interessanti, i passi del tempo di output devono essere minori. In realtà non voglio output denso a intervalli uguali, voglio solo i passi temporali che la funzione adattiva utilizza.

EDIT: uscita Dense

Una nozione relativa (quasi l'opposto) è "uscita densa", per cui le misure adottate sono grandi come stepper preoccupa di prendere, ma i valori della funzione sono interpolate (solitamente con precisione paragonabile all'accuratezza dello stepper) a qualsiasi cosa tu voglia. FORTRAN sottostante scipy.integrate.ode è apparentemente capace di questo, ma ode non ha l'interfaccia. odeint, d'altra parte, si basa su un codice diverso e fa evidentemente un output denso. (È possibile eseguire l'output ogni volta che viene richiamato il proprio lato destro per vedere quando ciò accade e vedere che non ha nulla a che fare con i tempi di output.)

Così ho potuto sfruttare l'adattabilità, purché Potrei decidere i passi temporali di uscita che voglio in anticipo. Purtroppo, per il mio sistema preferito, io non so nemmeno che cosa i tempi approssimativi sono in funzione del tempo, fino a quando ho eseguito l'integrazione. Quindi dovrò combinare l'idea di prendere un passo dell'integratore con questa nozione di output denso.

risposta

10

Da SciPy 0.13.0,

I risultati intermedi della dopri famiglia di risolutori ODE possono ora accessibili da una funzione solout callback.

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() 

Risultato: Logistic function solved using DOPRI5

Il risultato sembra essere leggermente diverso da Tim D's, anche se entrambi utilizzano lo stesso backend. Sospetto che questo abbia a che fare con la proprietà FSAL di dopri5. Nell'approccio di Tim, penso che il risultato k7 del settimo stadio venga scartato, quindi k1 viene calcolato di nuovo.

Nota: c'è un noto bug with set_solout not working if you set it after setting initial values. È stato risolto a partire da SciPy 0.17.0.

+1

Ricorda che per ora (versione 0.16) devi chiamare 'set_solout()' ** prima ** 'set_initial_value()' o solout non verrà chiamato. –

8

Il metodo integrate accetta un argomento booleano step che indica al metodo di restituire un singolo passaggio interno. Tuttavia, sembra che i risolutori 'dopri5' e 'dop853' non lo supportino.

Il codice seguente mostra come è possibile ottenere i passi interne adottate dal risolutore quando viene utilizzato il risolutore 'vode':

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() 

Risultato: Plot of the solution

+1

Sì, temevo che fosse il caso. Speravo che ci sarebbe stato un modo semplice per estendere 'dopri5' e' dop853', ma la mia pazienza termina con fortran, quindi penso che mi limiterò a implementare un time stepper. Sembra vergognoso, tuttavia, che Python sia rimasto senza integratori robusti, efficienti e flessibili ... – Mike

+0

Avevo bisogno di questa stessa funzionalità mentre provavo a convertire uno script MATLAB che utilizza l'ode45. Ho inviato un ticket su Scipy.org [Ticket # 1820] (http://projects.scipy.org/scipy/ticket/1820). –

+0

@Mike: Beh, anche con 'dopri5' e' dop853' si può sempre memorizzare il valore dall'interno della funzione 'logistica' e basta fare una sola chiamata al metodo' integrazione'. (Ho intenzione di postare questo come risposta alternativa.) – balu

12

Ho cercato in questo per cercare di ottenere lo stesso risultato. Si scopre che è possibile utilizzare un hack per ottenere i risultati passo-passo impostando nsteps = 1 nell'istanza dell'ode. Genererà un UserWarning ad ogni passo (questo può essere catturato e soppresso).

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() 

risultato:

+0

Questo sembra fare il lavoro, va bene. Ora, se solo non avessi passato settimane a trasferire il mio codice su GSL ... :) – Mike

+2

Si scopre che è possibile sopprimere l'avviso emesso da Fortran con questo piccolo trucco "solver._integrator.iwork [2] = -1' (Modificherò il codice sopra per mostrare questo). Questo imposta un flag passato attraverso l'interfaccia di Fortran che sopprime la stampa sullo stdout. –

-1

Ecco un'altra opzione che dovrebbe funzionare anche con dopri5 e dop853. In sostanza, il solutore chiamerà la funzione logistic() la frequenza necessaria per calcolare i valori intermedi così che è dove abbiamo memorizzare i risultati:

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() 
+1

Qui ci sono tre passaggi temporali. Il più grande è il passo del tempo di uscita; cosa 'ode' si attiva di default. Poi abbiamo il passo del tempo adattativo dell'integratore, che è dove voglio l'output. Il più piccolo è il passo temporale intermedio utilizzato dagli integratori in stile Runge-Kutta (ad esempio, 'dopri5',' dop853'). Cioè, 'dopri5' chiamerà' logistic' più volte durante i passaggi intermedi per creare un singolo passo temporale. La mia preoccupazione è che credo che i passaggi intermedi abbiano una precisione inferiore; il valore 'y' è in realtà solo accurato al primo ordine in molti casi. – Mike

+0

"Credo che i passaggi intermedi abbiano una precisione inferiore nell'ordine" Oops, pensavo che 'logistic' sarebbe stato chiamato con il passo adattivo del tempo dell'integratore. In caso contrario, la mia risposta ovviamente non aiuta. Ma ne siamo sicuri? – balu

+0

L'idea è di costruire approssimazioni migliori all'incremento corretto; combinando i risultati di più chiamate consente di annullare alcuni termini di errore. Infatti, ora che lo guardo, l'articolo di Wikipedia ha una sezione che spiega ["* Il metodo * Runge-Kutta"] (https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods#The_Runge. E2.80.93Kutta_method), in cui mostra che la funzione viene valutata due volte nel punto intermedio con valori diversi.Quindi il tuo 'sol' in effetti potrebbe memorizzare due diversi valori' y' per lo stesso valore 't' in alcuni casi, perché il primo era meno preciso. – Mike

2

Cordiali saluti, anche se una risposta è stata già accettata, vorrei sottolineare per il record storico tale output denso e campionamento arbitrario da ovunque lungo la traiettoria calcolata è supportato nativamente in PyDSTool.Ciò include anche una registrazione di tutte le fasi temporali determinate in modo adattivo utilizzate internamente dal risolutore. Questo si interfaccia con entrambi dopri853 and radau5 e genera automaticamente il codice C necessario per interfacciarli con essi piuttosto che fare affidamento su (molto più lenti) callback di funzioni python per la definizione del lato destro. Nessuna di queste funzionalità è fornita in modo nativo o efficiente in nessun altro solutore focalizzato su Python, per quanto ne so.