-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathreverseRK_test.py
216 lines (186 loc) · 8.24 KB
/
reverseRK_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# -*- coding: utf-8 -*-
"""
Created on Wed Jun 15 18:49:12 2022
@author: lfrancoi
"""
import numpy as np
import numpy.matlib
import ctypes as ct
from scipy.optimize import fsolve
import copy
import scipy.integrate
import scipy.interpolate
from damped_newton import damped_newton_solve
import newton
NEWTONCHOICE=2 #0: Scipy's fsolve 1: Scipy's newton (bad !), 2: custom damped Newton, 3: custom undamped quasi-Newton (weirdly works better than the damped one...)
def inverseERKintegration(fun, y0, t_span, nt, method, jacfun=None, bPrint=True,
vectorized=False, fullDebug=False):
""" Performs the integration of the system dy/dt = f(t,y)*
with a reversed explicit RK method
from t=t_span[0] to t_span[1], with initial condition y(t_span[0])=y0.
The RK method described by A,b,c is an explicit method (which we reverse)
- fun : (function handle) model function (time derivative of y)
- y0 : (1D-array) initial condition
- t_span : (1D-array) array of 2 values (start and end times)
- nt : (integer) number of time steps
- A : (2D-array) Butcher table of the chosen RK method
- b : (1D-array) weightings for the quadrature formula of the RK methods
- c : (1D-array) RK substeps time
"""
assert y0.ndim==1, 'y0 must be 0D or 1D'
out = scipy.integrate._ivp.ivp.OdeResult()
t = np.linspace(t_span[0], t_span[1], nt)
A,b,c = method['A'], method['b'], method['c'] # Butcher coefficients
n = y0.size # size of the problem
dt = (t_span[1]-t_span[0]) / (nt-1) # time step
s = np.size(b) # number of stages for the RK method
y = np.zeros((n, nt)) # solution accros all time steps
y[:,0] = y0
## make sure the function is vectorised
if vectorized:
fun_vectorised = fun
else:
def fun_vectorised(t,x):
""" Vectorize a non-vectorized function """
if x.ndim==1:
return fun(t,x)
else:
if not (isinstance(t, np.ndarray)):
t = t*np.ones((x.shape[1]))
res0 = fun(t[0],x[:,0])
res = np.zeros((res0.shape[0],x.shape[1]))
res[:,0]=res0
for i in range(1,x.shape[1]):
res[:,i] = fun(t[i],x[:,i])
return res
## define the residual function
def resfun(ynp1,yn,tn,dt,A,n,s, return_substeps=False):
""" Residuals for the substeps.
The input is Y = (y0[...], y1[...], ...).T """
# 1 - compute reverse stages explicitly
Y = np.zeros((n,s), order='F')
for i in range(s):
Y[:,i] = ynp1
for j in range(i): # explicit RK reversed
Y[:,i] = Y[:,i] - dt*A[i,j]*fun(tn+(1-c[j])*dt, Y[:,j])
# TODO use k's instead
# 2 - compute the reversed yn
# TODO: on est toujours stiffly accurate, non ?
ynrev = ynp1
for i in range(s):
ynrev = ynrev - dt*b[i]*fun(tn+(1-c[i])*dt, Y[:,i])
# ynrev = Y[:,-1]
print('ynrev=',ynrev)
# 2 - compute residuals as a matrix (one row for each step)
res = ynrev - yn
if return_substeps:
return res, Y
else:
return res
## skirmish
# bStifflyAccurate = np.all(b==A[-1,:]) # then the last stage is the solution at the next time point
## advance in time
out.nfev = 0
out.njev = 0
K= np.zeros((n, s), order='F')
unm1 = np.copy(y0)
# At = A.T
warm_start_dict = None
infodict_hist = {} # additonal optional debug storage
out.infodict_hist = infodict_hist
for it, tn in enumerate(t[:-1]):
if bPrint:
if np.mod(it,np.floor(nt/10))==0:
print('\n{:.1f} %'.format(100*it/nt), end='')
if np.mod(it,np.floor(nt/100))==0:
print('.', end='')
# solve the complete non-linear system via a Newton method
yini = unm1[:]
ynp1, infodict, warm_start_dict = damped_newton_solve(
fun=lambda x: resfun(ynp1=x,yn=unm1, tn=tn, dt=dt, A=A, n=n, s=s),
x0=np.copy(yini), rtol=1e-9, ftol=1e-30,
jacfun=None, warm_start_dict=warm_start_dict,
itmax=100, jacmax=20, tau_min=1e-4, convergenceMode=0,
bPrint=True)
out.nfev += infodict['nfev']
out.njev += infodict['njev']
if infodict['ier']!=0: # Newton did not converge
# restart the Newton solve with all outputs enabled
ynp1, infodict, warm_start_dict = damped_newton_solve(fun=lambda x: resfun(Y=x,y0=unm1, tn=tn, dt=dt, A=A, n=n, s=s),
x0=np.copy(yini), rtol=1e-9, ftol=1e-30,
jacfun=None, warm_start_dict=warm_start_dict,
itmax=100, jacmax=20, tau_min=1e-4, convergenceMode=0, bPrint=True)
msg = 'Newton did not converge'
# raise Exception(msg)
out.y = y[:,:it+1]
out.t = t[:it+1]
out.message = msg
return out
if fullDebug: # store additional informations about the Newton solve
if not bool(infodict_hist): # the debug dictionnary has not yet been initialised
for key in infodict.keys():
if not isinstance(infodict[key], np.ndarray) and (key!='ier' and key!='msg'): # only save single values
infodict_hist[key] = [infodict[key]]
else: # backup already initialised
for key in infodict_hist.keys():
infodict_hist[key].append(infodict[key])
y[:,it+1] = ynp1
unm1=ynp1
# END OF INTEGRATION
out.y = y
out.t = t
# out.y_substeps = y_substeps # last substeps
return out
if __name__=='__main__':
import matplotlib.pyplot as plt
import rk_coeffs
#%% Single method test
#### PARAMETERS ####
problemtype = 'non-stiff'
NEWTONCHOICE=3
# name= 'rk4'
name='EE'
# name='RK45'
# name='Heun-Euler'
### Setup and solve the problem
if problemtype=='non-stiff': # ODE
print('Testing time integration routines with mass-spring system')
k_sur_m = 33.
Amod = np.array( ( (0,1),(-k_sur_m, 0) ))
def modelfun(t,x,options={}):
""" Mass-spring system"""
Xdot = np.dot(Amod, x)
return Xdot
y0 = np.array((0.3,1))
tf = 2.0
nt = 30
elif problemtype=='stiff': # Hirschfelder-Curtiss
print('Testing time integration routines with Hirschfelder-Curtiss stiff equation')
k=10.
def modelfun(t,x):
""" Mass-spring system"""
return -(k*x-np.sin(t) )
y0 = np.array((0.3,1))
tf = 5.0
nt = 30
method = rk_coeffs.getButcher(name=name)
sol = inverseERKintegration(fun=modelfun, y0=y0, t_span=[0,tf], nt=nt,
method=method, jacfun=None, bPrint=True,
fullDebug=True)
plt.figure()
plt.semilogy(sol.t[:-1], np.diff(sol.t))
plt.grid()
plt.xlabel('t (s)')
plt.ylabel('dt (s)')
sol_ref = scipy.integrate.solve_ivp(fun=modelfun, t_span=[0., tf], y0=y0, method='DOP853', first_step=1e-2,
atol=1e-13, rtol=1e-13)
plt.figure()
plt.plot(sol.t, sol.y[0,:], label='position', marker='x')
plt.plot(sol.t, sol.y[1,:], label='vitesse', marker='x')
plt.plot(sol_ref.t, sol_ref.y[0,:], label='position ref', linestyle='--', marker=None, markevery=1)
plt.plot(sol_ref.t, sol_ref.y[1,:], label='vitesse ref', linestyle='--', marker=None, markevery=1)
plt.title('Solutions')
plt.legend()
plt.xlabel('time (s)')
plt.ylabel('position')
plt.show()