RKkit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- RKkit/README.md +2 -0
- RKkit/RKExceptions.py +74 -0
- RKkit/RKPolutilities.py +52 -0
- RKkit/RKRungeKutta.py +30 -0
- RKkit/RKTrees.py +114 -0
- RKkit/RKcolloc.py +71 -0
- RKkit/RKformula.py +474 -0
- RKkit/RKplot.py +108 -0
- RKkit/__init__.py +10 -0
- RKkit/formulas.py +103 -0
- rkkit-0.1.0.dist-info/METADATA +205 -0
- rkkit-0.1.0.dist-info/RECORD +14 -0
- rkkit-0.1.0.dist-info/WHEEL +4 -0
- rkkit-0.1.0.dist-info/licenses/LICENSE +674 -0
RKkit/RKformula.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
r"""
|
|
3
|
+
|
|
4
|
+
Explore properties of Runge-Kutta methods.
|
|
5
|
+
|
|
6
|
+
AUTHOR:
|
|
7
|
+
|
|
8
|
+
- Thierry Dumont (2016, 2018. 2020: Python 3, _persistance decorator)
|
|
9
|
+
Institut C. Jordan, Lyon, France.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# ****************************************************************************
|
|
13
|
+
# Copyright (C) 2018 Thierry Dumont tdumont@math.univ-lyon1.fr
|
|
14
|
+
#
|
|
15
|
+
# This program is free software: you can redistribute it and/or modify
|
|
16
|
+
# it under the terms of the GNU General Public License as published by
|
|
17
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
18
|
+
# (at your option) any later version.
|
|
19
|
+
# https://www.gnu.org/licenses/
|
|
20
|
+
# ****************************************************************************
|
|
21
|
+
|
|
22
|
+
from sage.structure.sage_object import SageObject
|
|
23
|
+
from sage.rings.infinity import minus_infinity
|
|
24
|
+
from sage.matrix.constructor import Matrix,matrix
|
|
25
|
+
from sage.matrix.special import identity_matrix
|
|
26
|
+
from sage.modules.free_module_element import vector
|
|
27
|
+
from sage.rings.qqbar import *
|
|
28
|
+
from sage.calculus.functional import derivative
|
|
29
|
+
from sage.rings.imaginary_unit import I
|
|
30
|
+
from sage.symbolic.ring import SR
|
|
31
|
+
from sage.rings.real_double import RDF
|
|
32
|
+
from sage.functions.log import exp
|
|
33
|
+
from sage.functions.generalized import sign
|
|
34
|
+
#
|
|
35
|
+
from .RKTrees import *
|
|
36
|
+
#
|
|
37
|
+
from .RKExceptions import *
|
|
38
|
+
from .RKPolutilities import *
|
|
39
|
+
#
|
|
40
|
+
import functools
|
|
41
|
+
#
|
|
42
|
+
class RKformula(SageObject):
|
|
43
|
+
r"""
|
|
44
|
+
Store coefficients of a Runge-Kutta method, compute some of its
|
|
45
|
+
properties.
|
|
46
|
+
|
|
47
|
+
EXAMPLES::
|
|
48
|
+
|
|
49
|
+
sage: F = RKformula(A,B)
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
def __init__(self,F):
|
|
53
|
+
"""
|
|
54
|
+
Initilalize ``self``. F is a Runge-Kutta class.
|
|
55
|
+
|
|
56
|
+
EXAMPLES::
|
|
57
|
+
|
|
58
|
+
sage: R = RK4()
|
|
59
|
+
sage: F = RKformula(R)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# force coefficients to live in AA:
|
|
63
|
+
self.A = F.A.change_ring(AA)
|
|
64
|
+
self.B = vector([ AA(b) for b in F.B])
|
|
65
|
+
self.C = vector([ AA(c) for c in F.C])
|
|
66
|
+
self.D = AA
|
|
67
|
+
self.R = AA["z"]# PolynomialRing(AA, 'z')
|
|
68
|
+
self.s = self.A.dimensions()[1]
|
|
69
|
+
# as computing properties can be slow, we will cache them here as
|
|
70
|
+
# soon as they are computed:
|
|
71
|
+
self.known_properties={}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _persistance(foo):
|
|
75
|
+
"""
|
|
76
|
+
Decorator: caches results of "foo" in self.known_properties
|
|
77
|
+
"""
|
|
78
|
+
@functools.wraps(foo)
|
|
79
|
+
def magic( self, *args, **kwargs ):
|
|
80
|
+
if foo.__name__ in self.known_properties:
|
|
81
|
+
return self.known_properties[foo.__name__]
|
|
82
|
+
else:
|
|
83
|
+
x=foo( self, *args, **kwargs )
|
|
84
|
+
self.known_properties[foo.__name__]=x
|
|
85
|
+
return x
|
|
86
|
+
return magic
|
|
87
|
+
|
|
88
|
+
def _latex_(self):
|
|
89
|
+
r"""
|
|
90
|
+
Return the LaTeX representation of X.
|
|
91
|
+
"""
|
|
92
|
+
return latex(self.A)+" "+latex(self.B)
|
|
93
|
+
|
|
94
|
+
def n_stages(self):
|
|
95
|
+
"""
|
|
96
|
+
Return number of stages.
|
|
97
|
+
"""
|
|
98
|
+
return self.s
|
|
99
|
+
|
|
100
|
+
@_persistance
|
|
101
|
+
def stability_function(self):
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
Compute the stability function.
|
|
105
|
+
|
|
106
|
+
EXAMPLES::
|
|
107
|
+
|
|
108
|
+
sage: F.stability_function()
|
|
109
|
+
|
|
110
|
+
(1/3*z + 1)/(1/6*z^2 - 2/3*z + 1)
|
|
111
|
+
"""
|
|
112
|
+
z = self.R.gen()
|
|
113
|
+
Rng = self.D
|
|
114
|
+
K = matrix(Rng,[self.B for i in range(0,self.s)])
|
|
115
|
+
II = identity_matrix(Rng,self.s)
|
|
116
|
+
D = II - z*self.A
|
|
117
|
+
N = D + z*K
|
|
118
|
+
return N.determinant()/D.determinant()
|
|
119
|
+
|
|
120
|
+
@_persistance
|
|
121
|
+
def A_is_invertible(self):
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
Test if the matrix A part of the Butcher array is invertible.
|
|
125
|
+
|
|
126
|
+
EXAMPLES::
|
|
127
|
+
|
|
128
|
+
sage: F.A_is_invertible()
|
|
129
|
+
|
|
130
|
+
True
|
|
131
|
+
"""
|
|
132
|
+
return self.A.is_invertible()
|
|
133
|
+
|
|
134
|
+
@_persistance
|
|
135
|
+
def is_explicit(self):
|
|
136
|
+
"""
|
|
137
|
+
Test if the method is explicit.
|
|
138
|
+
|
|
139
|
+
EXAMPLES::
|
|
140
|
+
|
|
141
|
+
sage: F.is_explicit()
|
|
142
|
+
|
|
143
|
+
False
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
return self.stability_function().denominator().degree()==0
|
|
147
|
+
|
|
148
|
+
@_persistance
|
|
149
|
+
def poles_of_stability_function(self):
|
|
150
|
+
"""
|
|
151
|
+
Compute the poles of the stability function, if any.
|
|
152
|
+
"""
|
|
153
|
+
if self.is_explicit():
|
|
154
|
+
return []
|
|
155
|
+
else:
|
|
156
|
+
RD = self.stability_function().denominator()
|
|
157
|
+
Poles,ok,nr = roots_checked(RD,QQbar)
|
|
158
|
+
if not ok:
|
|
159
|
+
raise RootsException(nr,RD)
|
|
160
|
+
else:
|
|
161
|
+
return Poles
|
|
162
|
+
|
|
163
|
+
@_persistance
|
|
164
|
+
def real_part_of_poles_all_positive(self):
|
|
165
|
+
"""
|
|
166
|
+
Documentation is in the name of this method!
|
|
167
|
+
|
|
168
|
+
Returns: (all poles have >=0 real part?) and number of poles==0.
|
|
169
|
+
"""
|
|
170
|
+
Poles = self.poles_of_stability_function()
|
|
171
|
+
llp = all(s[0].real()>=0 for s in Poles)
|
|
172
|
+
llzero = len([s for s in Poles if s[0].real()==0 ])
|
|
173
|
+
return llp,llzero
|
|
174
|
+
|
|
175
|
+
@_persistance
|
|
176
|
+
def order_of_stability_function(self):
|
|
177
|
+
"""
|
|
178
|
+
Documentation is in the name of this method!
|
|
179
|
+
"""
|
|
180
|
+
order = 0
|
|
181
|
+
z = self.R.gen()
|
|
182
|
+
while derivative(self.stability_function(),z,order)(z = 0)==1: \
|
|
183
|
+
order+= 1
|
|
184
|
+
return order -1
|
|
185
|
+
|
|
186
|
+
@_persistance
|
|
187
|
+
def module_of_stability_function_squared(self):
|
|
188
|
+
"""
|
|
189
|
+
Documentation is in the name of this method!
|
|
190
|
+
"""
|
|
191
|
+
return self.stability_function()*conjugate(self.stability_function())
|
|
192
|
+
|
|
193
|
+
@_persistance
|
|
194
|
+
def stability_function_on_im_axis(self):
|
|
195
|
+
"""
|
|
196
|
+
Trace of the stability function on the imaginary axis.
|
|
197
|
+
"""
|
|
198
|
+
R = self.stability_function()
|
|
199
|
+
P = AA['x']
|
|
200
|
+
x = P.gen()
|
|
201
|
+
return R(z = QQbar(I)*x)
|
|
202
|
+
|
|
203
|
+
@_persistance
|
|
204
|
+
def squared_module_of_stability_function_on_Im(self):
|
|
205
|
+
"""
|
|
206
|
+
Square of the module of the trace of the stability function on
|
|
207
|
+
the imaginary axis.
|
|
208
|
+
"""
|
|
209
|
+
RIaxe = self.stability_function_on_im_axis()
|
|
210
|
+
RIaxeN = RIaxe.numerator()
|
|
211
|
+
RIaxeD = RIaxe.denominator()
|
|
212
|
+
m2N = RIaxeN*conj(RIaxeD)
|
|
213
|
+
m2NR = realpart(m2N)
|
|
214
|
+
m2NI = impart(m2N)
|
|
215
|
+
m2n = generic_power(m2NR,2)+generic_power(m2NI,2)
|
|
216
|
+
m2d = RIaxeD*conj(RIaxeD)
|
|
217
|
+
return m2n/generic_power(m2d,2)
|
|
218
|
+
|
|
219
|
+
@_persistance
|
|
220
|
+
def is_module_of_stability_function_constant_on_Im(self):
|
|
221
|
+
"""
|
|
222
|
+
Documentation is in the name of this method!
|
|
223
|
+
"""
|
|
224
|
+
m2 = self.squared_module_of_stability_function_on_Im()
|
|
225
|
+
x = m2.parent().gen()
|
|
226
|
+
return derivative(m2,x) == 0
|
|
227
|
+
|
|
228
|
+
@_persistance
|
|
229
|
+
def is_module_of_stability_function_less_than_1(self):
|
|
230
|
+
"""
|
|
231
|
+
Is the module of the trace of the stability funtion on the
|
|
232
|
+
imaginary axis <1 ?
|
|
233
|
+
"""
|
|
234
|
+
is_const = self.is_module_of_stability_function_constant_on_Im()
|
|
235
|
+
if is_const:
|
|
236
|
+
return True
|
|
237
|
+
else:
|
|
238
|
+
m2 = self.squared_module_of_stability_function_on_Im()
|
|
239
|
+
l1,ok,n = roots_checked(m2.numerator()-m2.denominator(),QQbar)
|
|
240
|
+
if not ok:
|
|
241
|
+
raise RootsException(n,m2.numerator()-m2.denominator())
|
|
242
|
+
else:
|
|
243
|
+
l1m1 = [s[0] for s in l1 if s[0].imag()==0 ]
|
|
244
|
+
l1m1realp = [s.real() for s in l1m1 if s >= 0]
|
|
245
|
+
l1r = len(l1m1realp)
|
|
246
|
+
if l1r==1:
|
|
247
|
+
# module value is 0, only in x == 0.
|
|
248
|
+
return m2(z=1) <1
|
|
249
|
+
else:
|
|
250
|
+
points = [(l1m1realp[i]+l1m1realp[i+1])/2 \
|
|
251
|
+
for i in range(0,l1r-1)]
|
|
252
|
+
points.append(l1m1realp[l1r-1]*3/2)
|
|
253
|
+
signes = [sign(abs(m2(x = s).real())-1) for s in points]
|
|
254
|
+
return len([x for x in signes if x >=0]) == 0
|
|
255
|
+
|
|
256
|
+
@_persistance
|
|
257
|
+
def is_A_stable(self):
|
|
258
|
+
"""
|
|
259
|
+
Do we have an A_stable method ?
|
|
260
|
+
|
|
261
|
+
See: HW II , second edition, page 43.
|
|
262
|
+
"""
|
|
263
|
+
return (self.is_module_of_stability_function_constant_on_Im() or \
|
|
264
|
+
self.is_module_of_stability_function_less_than_1()) and \
|
|
265
|
+
self.real_part_of_poles_all_positive()[0]
|
|
266
|
+
|
|
267
|
+
@_persistance
|
|
268
|
+
def is_stiffly_accurate(self):
|
|
269
|
+
"""
|
|
270
|
+
Do we have a stiffly accurate method ?
|
|
271
|
+
"""
|
|
272
|
+
# Proposition 3.8 in H-W. TII, page 45.
|
|
273
|
+
A = self.A
|
|
274
|
+
B = self.B
|
|
275
|
+
s1 = self.s-1
|
|
276
|
+
return self.is_A_stable() and \
|
|
277
|
+
(len([1 for j in range(0,self.s) if A[s1,j] != B[j]]) == 0 or \
|
|
278
|
+
len([1 for i in range(0,self.s) if A[i,0] != B[0]]) == 0)
|
|
279
|
+
|
|
280
|
+
@_persistance
|
|
281
|
+
def R_infinite(self):
|
|
282
|
+
r"""
|
|
283
|
+
R(\infty). See Hairer-Wanner t.II pages 45 and 375.
|
|
284
|
+
"""
|
|
285
|
+
if self.A_is_invertible():
|
|
286
|
+
AI = self.A.inverse()
|
|
287
|
+
One = vector([AA(1) for i in range(0,self.s)])
|
|
288
|
+
ret = AA(1)-self.B.dot_product(AI*One)
|
|
289
|
+
ret.exactify()
|
|
290
|
+
return ret
|
|
291
|
+
else:
|
|
292
|
+
raise MatrixIsSingular("A")
|
|
293
|
+
|
|
294
|
+
@_persistance
|
|
295
|
+
def is_L_stable(self):
|
|
296
|
+
"""
|
|
297
|
+
Do we have a L_stable method ?
|
|
298
|
+
"""
|
|
299
|
+
if not self.is_A_stable():
|
|
300
|
+
return False
|
|
301
|
+
else:
|
|
302
|
+
R=self.stability_function()
|
|
303
|
+
return R.denominator().degree() > R.numerator().degree() or \
|
|
304
|
+
self.R_infinite().is_zero()
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@_persistance
|
|
308
|
+
def M_matrix(self):
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
Compute the matrix M used by is_algebraically_stable(self) and
|
|
312
|
+
conserve_quadratic_invariants(self).
|
|
313
|
+
|
|
314
|
+
"""
|
|
315
|
+
B = self.B
|
|
316
|
+
A = self.A
|
|
317
|
+
M = matrix(QQbar,self.s,self.s)
|
|
318
|
+
for i in range(0,self.s):
|
|
319
|
+
for j in range(0,self.s):
|
|
320
|
+
M[i,j] = B[i]*A[i,j]+B[j]*A[j,i]-B[i]*B[j]
|
|
321
|
+
return M
|
|
322
|
+
|
|
323
|
+
@_persistance
|
|
324
|
+
def is_algebraically_stable(self):
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
Is the method algebraically stable ?
|
|
328
|
+
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
if self.is_explicit() or any(s<0 for s in self.B):
|
|
332
|
+
return False
|
|
333
|
+
else:
|
|
334
|
+
M = self.M_matrix()
|
|
335
|
+
return all(s >=0 for s in M.list())
|
|
336
|
+
|
|
337
|
+
@_persistance
|
|
338
|
+
def is_Symmetric(self):
|
|
339
|
+
"""
|
|
340
|
+
All is in the title.
|
|
341
|
+
"""
|
|
342
|
+
P = matrix(AA,self.s,self.s)
|
|
343
|
+
for i in range(0,self.s):
|
|
344
|
+
P[i,self.s-i-1]=1
|
|
345
|
+
PA =P *self.A+ self.A*P
|
|
346
|
+
return all(self.B == Row for Row in PA.rows()) and \
|
|
347
|
+
self.B == P*self.B
|
|
348
|
+
|
|
349
|
+
@_persistance
|
|
350
|
+
def conserve_quadratic_invariants(self):
|
|
351
|
+
"""
|
|
352
|
+
Documentation is in the name of this method!
|
|
353
|
+
"""
|
|
354
|
+
return not self.is_explicit() and self.M_matrix().is_zero()
|
|
355
|
+
|
|
356
|
+
@_persistance
|
|
357
|
+
def is_Symplectic(self):
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
Test for simplexity. We can conclude (positively) only if
|
|
361
|
+
the matrix N (see above) is zero.
|
|
362
|
+
|
|
363
|
+
"""
|
|
364
|
+
if self.is_explicit():
|
|
365
|
+
return False
|
|
366
|
+
elif self.M_matrix().is_zero():
|
|
367
|
+
return True
|
|
368
|
+
else:
|
|
369
|
+
return "Unknown"
|
|
370
|
+
|
|
371
|
+
def check_order_using_rooted_trees(self,order):
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
Check rooted tree at order 'order'.
|
|
375
|
+
|
|
376
|
+
"""
|
|
377
|
+
self.RTrees = RKTrees()
|
|
378
|
+
t = False
|
|
379
|
+
for i in range(1,order+1):
|
|
380
|
+
t = self.RTrees.check_order(self.A,self.B,i)
|
|
381
|
+
if not t:
|
|
382
|
+
break
|
|
383
|
+
return t #True iff formula is at least of order "order".
|
|
384
|
+
|
|
385
|
+
@_persistance
|
|
386
|
+
def make_order_equations(self,order):
|
|
387
|
+
self.RTrees = RKTrees()
|
|
388
|
+
s=[]
|
|
389
|
+
for i in range(1,order+1):
|
|
390
|
+
s+=self.RTrees.make_order_equations(self.A,self.B,i)
|
|
391
|
+
return s
|
|
392
|
+
@_persistance
|
|
393
|
+
def stability_on_real_negative_axis(self):
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
In the case where the method is not A-stable, find the limit
|
|
397
|
+
of stability on the real negative axis.
|
|
398
|
+
|
|
399
|
+
"""
|
|
400
|
+
if self.is_A_stable():
|
|
401
|
+
return minus_infinity
|
|
402
|
+
else:
|
|
403
|
+
p=generic_power(self.stability_function(),2)-1
|
|
404
|
+
r=[s[0] for s in sorted(p.numerator().roots(),reverse=True)
|
|
405
|
+
if s[0]<0]
|
|
406
|
+
if len(r)==0:
|
|
407
|
+
return minus_infinity
|
|
408
|
+
else:
|
|
409
|
+
return r[0]
|
|
410
|
+
|
|
411
|
+
@_persistance
|
|
412
|
+
def order(self):
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
Compute order of the method using rooted trees.
|
|
416
|
+
|
|
417
|
+
"""
|
|
418
|
+
o = 0
|
|
419
|
+
while self.check_order_using_rooted_trees(o+1):
|
|
420
|
+
o+= 1
|
|
421
|
+
return o
|
|
422
|
+
|
|
423
|
+
@_persistance
|
|
424
|
+
def order_star_function(self):
|
|
425
|
+
"""
|
|
426
|
+
Compute the order star function.
|
|
427
|
+
This is for drawing the "star" associated to the formula.
|
|
428
|
+
"""
|
|
429
|
+
x=SR.var("x")
|
|
430
|
+
y=SR.var("y")
|
|
431
|
+
Rs = self.stability_function()
|
|
432
|
+
RRs=Rs.numerator().change_ring(RDF)/Rs.denominator().change_ring(RDF)
|
|
433
|
+
s=RRs(x+SR(I)*y)/exp(x+SR(I)*y)
|
|
434
|
+
return s.abs()
|
|
435
|
+
|
|
436
|
+
def compute_all_properties(self):
|
|
437
|
+
"""
|
|
438
|
+
|
|
439
|
+
Compute all possible properties of the formula.
|
|
440
|
+
|
|
441
|
+
"""
|
|
442
|
+
self.A_is_invertible()
|
|
443
|
+
self.is_explicit()
|
|
444
|
+
self.stability_function()
|
|
445
|
+
self.poles_of_stability_function()
|
|
446
|
+
self.real_part_of_poles_all_positive()
|
|
447
|
+
self.order_of_stability_function()
|
|
448
|
+
self.stability_function_on_im_axis()
|
|
449
|
+
self.squared_module_of_stability_function_on_Im()
|
|
450
|
+
self.is_module_of_stability_function_constant_on_Im()
|
|
451
|
+
self.is_module_of_stability_function_less_than_1()
|
|
452
|
+
self.is_A_stable()
|
|
453
|
+
self.is_stiffly_accurate()
|
|
454
|
+
self.is_L_stable()
|
|
455
|
+
self.is_algebraically_stable()
|
|
456
|
+
self.is_Symmetric()
|
|
457
|
+
self.is_Symplectic()
|
|
458
|
+
self.conserve_quadratic_invariants()
|
|
459
|
+
self.stability_on_real_negative_axis()
|
|
460
|
+
self.stability_on_real_negative_axis()
|
|
461
|
+
self.order()
|
|
462
|
+
self.order_star_function()
|
|
463
|
+
|
|
464
|
+
def print_all_known_properties(self):
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
Print all already computed properties.
|
|
468
|
+
|
|
469
|
+
"""
|
|
470
|
+
donotprint=["A","B","C","D","R","s","RTrees","M_matrix"]
|
|
471
|
+
D=self.known_properties
|
|
472
|
+
for key in D:
|
|
473
|
+
if key not in donotprint:
|
|
474
|
+
print("-> ",key," : ",D[key],"\n")
|
RKkit/RKplot.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from .RKExceptions import GraphicProblem
|
|
2
|
+
from sage.rings.infinity import minus_infinity
|
|
3
|
+
from sage.symbolic.ring import SR
|
|
4
|
+
from sage.rings.qqbar import QQbar
|
|
5
|
+
from sage.rings.imaginary_unit import I
|
|
6
|
+
from sage.functions.other import conjugate
|
|
7
|
+
|
|
8
|
+
from sage.plot.contour_plot import contour_plot
|
|
9
|
+
|
|
10
|
+
def RKplot(RKf,title="",Enlarge=4,TranslateX=0,
|
|
11
|
+
ncurves=1,limits=[],fill=False,type="stab"):
|
|
12
|
+
r"""
|
|
13
|
+
Plot isovalues of stability function or Order star.
|
|
14
|
+
|
|
15
|
+
PARAMETERS:
|
|
16
|
+
----------
|
|
17
|
+
|
|
18
|
+
RKf: the formula (instantiation of RKformula).
|
|
19
|
+
|
|
20
|
+
Enlarge: plot is done in a window around (0,0) in \mathbb{C}.
|
|
21
|
+
|
|
22
|
+
We can Enlarge the size of the window by this factor (a real
|
|
23
|
+
positive number).
|
|
24
|
+
|
|
25
|
+
TranslateX : translate the origin in the window along x axis.
|
|
26
|
+
|
|
27
|
+
ncurves: number of curves to plot.
|
|
28
|
+
|
|
29
|
+
limits: [[min of x, max of x],[min of y, max of y]] for the window.
|
|
30
|
+
In most case, this is computed.
|
|
31
|
+
|
|
32
|
+
fill: to fill the plot (see contour_plot documentation).
|
|
33
|
+
|
|
34
|
+
type= 'stab' for stability function (default), 'star' for the Order star.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
def sf1(x,y,P):
|
|
38
|
+
s = P(x+QQbar(I)*y)
|
|
39
|
+
return (s*conjugate(s)).real()
|
|
40
|
+
|
|
41
|
+
RDroots = RKf.poles_of_stability_function()
|
|
42
|
+
Rstab= RKf.stability_on_real_negative_axis()
|
|
43
|
+
|
|
44
|
+
# try to compute limits:
|
|
45
|
+
if Rstab==minus_infinity and RDroots == [] :
|
|
46
|
+
if limits==[]:
|
|
47
|
+
raise GraphicProblem(
|
|
48
|
+
"Cannot find limits. You must define: limits = [(x1,y1),(x2,y2)]")
|
|
49
|
+
else:
|
|
50
|
+
raise GraphicProblem("Mthod is not A_stable and no stab. limit is known")
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
if limits == []:
|
|
54
|
+
if len(RDroots) > 0:
|
|
55
|
+
Lr = max(max([abs(s[0].n().real()) for s in RDroots]), \
|
|
56
|
+
max([abs(s[0].n().imag()) for s in RDroots]))
|
|
57
|
+
else:
|
|
58
|
+
Lr = 0
|
|
59
|
+
|
|
60
|
+
if Rstab != minus_infinity:
|
|
61
|
+
L = max(-Rstab,Lr)
|
|
62
|
+
Lm= -L*Enlarge
|
|
63
|
+
Lp= -Lm
|
|
64
|
+
elif RKf.is_A_stable:
|
|
65
|
+
L = Lr
|
|
66
|
+
Lm = -Enlarge*L
|
|
67
|
+
Lp = 3*Enlarge*L/2
|
|
68
|
+
else:
|
|
69
|
+
Lm = -L
|
|
70
|
+
Lp = L
|
|
71
|
+
|
|
72
|
+
L1 = (Lm,Lp)
|
|
73
|
+
L2 = (-Enlarge*L,Enlarge*L)
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
if limits != []:
|
|
77
|
+
L1 = limits[0]
|
|
78
|
+
L2 = limits[1]
|
|
79
|
+
|
|
80
|
+
# What to plot:
|
|
81
|
+
if type == "stab":
|
|
82
|
+
sf = lambda x,y: sf1(x,y,RKf.stability_function())
|
|
83
|
+
elif type == "star":
|
|
84
|
+
sf = RKf.order_star_function()
|
|
85
|
+
else:
|
|
86
|
+
raise AttributeError("RKplot: "+type+" :unknown plot type")
|
|
87
|
+
|
|
88
|
+
if title != "":
|
|
89
|
+
stitle = title
|
|
90
|
+
else:
|
|
91
|
+
if type == "stab":
|
|
92
|
+
stitle= "Stability function."
|
|
93
|
+
else:
|
|
94
|
+
stitle = "Order star."
|
|
95
|
+
|
|
96
|
+
# translate:
|
|
97
|
+
if TranslateX!=0:
|
|
98
|
+
d = (L1[1]-L1[0])*TranslateX/100.
|
|
99
|
+
L1 = (L1[0]-d,L1[1]-d)
|
|
100
|
+
|
|
101
|
+
x = SR.var("x")
|
|
102
|
+
y = SR.var("y")
|
|
103
|
+
|
|
104
|
+
return contour_plot(sf,(x,L1[0],L1[1]), (y,L2[0],L2[1]),
|
|
105
|
+
contours = [0,1],
|
|
106
|
+
labels = True,fill = fill, label_inline = True,
|
|
107
|
+
axes = True,colorbar = True,title = stitle)
|
|
108
|
+
|
RKkit/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#
|
|
2
|
+
from .RKformula import RKformula
|
|
3
|
+
from .RKcolloc import colloc
|
|
4
|
+
from .RKplot import RKplot
|
|
5
|
+
from .RKRungeKutta import RungeKutta
|
|
6
|
+
#
|
|
7
|
+
from sage.rings.qqbar import *
|
|
8
|
+
from sage.rings.polynomial import laurent_polynomial_ring
|
|
9
|
+
|
|
10
|
+
__all__=["RKRungeKutta","RKformula","RKplot","RKcolloc","formulas"]
|
RKkit/formulas.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from sage.rings.qqbar import AA
|
|
2
|
+
from sage.structure.sage_object import SageObject
|
|
3
|
+
from sage.matrix.constructor import matrix
|
|
4
|
+
from sage.modules.free_module_element import vector
|
|
5
|
+
from sage.misc.functional import sqrt
|
|
6
|
+
from RKkit.RKRungeKutta import *
|
|
7
|
+
class Gauss4(RungeKutta):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
title="Gauss method, 2 points, order 4."
|
|
10
|
+
A=matrix(AA,[[1/AA(4),1/AA(4)-sqrt(3)/AA(6)],[1/AA(4)+sqrt(3)/AA(6),
|
|
11
|
+
1/AA(4)]])
|
|
12
|
+
##
|
|
13
|
+
B=vector([1/AA(2),1/AA(2)])
|
|
14
|
+
super().__init__(A,B,title)
|
|
15
|
+
|
|
16
|
+
class Lobatto4(RungeKutta):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
title="Lobatto method, order 4"
|
|
19
|
+
# Lobatto order 4.See Hairer Nörsett, Wanner TI, page 211.
|
|
20
|
+
A=matrix(AA,[[0,0,0],[1/AA(4),1/AA(4),0],[0,1,0]])
|
|
21
|
+
B=vector(AA,[1/AA(6),AA(2)/AA(3),1/AA(6)])
|
|
22
|
+
super().__init__(A,B,title)
|
|
23
|
+
|
|
24
|
+
class Radau2a(RungeKutta):
|
|
25
|
+
def __init__(self):
|
|
26
|
+
title="Radau 2a method"
|
|
27
|
+
A=matrix(AA,[[5/AA(12),-1/AA(12)],[3/AA(4),1/AA(4)]])
|
|
28
|
+
##
|
|
29
|
+
B=vector([3/AA(4),1/AA(4)])
|
|
30
|
+
super().__init__(A,B,title)
|
|
31
|
+
|
|
32
|
+
class Radau5(RungeKutta):
|
|
33
|
+
def __init__(self):
|
|
34
|
+
title="Radau5 method"
|
|
35
|
+
#
|
|
36
|
+
A=matrix(AA,3,3)
|
|
37
|
+
s6=AA(sqrt(6))
|
|
38
|
+
A[0,0]=1/AA(9)
|
|
39
|
+
A[0,1]=(-1-s6)/18
|
|
40
|
+
A[0,2]=(-1+s6)/18
|
|
41
|
+
#
|
|
42
|
+
A[1,0]=1/AA(9)
|
|
43
|
+
A[1,1]=(88+7*s6)/360
|
|
44
|
+
A[1,2]=(88-43*s6)/360
|
|
45
|
+
#
|
|
46
|
+
A[2,0]=1/AA(9)
|
|
47
|
+
A[2,1]=(88+43*s6)/360
|
|
48
|
+
A[2,2]=(88-7*s6)/360
|
|
49
|
+
##
|
|
50
|
+
B=vector([1/AA(9),(16+s6)/36,(16-s6)/36])
|
|
51
|
+
super().__init__(A,B,title)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RK4(RungeKutta):
|
|
55
|
+
def __init__(self):
|
|
56
|
+
title="Classical Runge-Kutta 4 explicit method"
|
|
57
|
+
A=matrix(AA,[[0,0,0,0],[1/AA(2),0,0,0],
|
|
58
|
+
[0,1/AA(2),0,0],[0,0,1,0]])
|
|
59
|
+
B=vector(AA,[1/AA(6),2/AA(6),2/AA(6),1/AA(6)])
|
|
60
|
+
super().__init__(A,B,title)
|
|
61
|
+
|
|
62
|
+
class SDIRK3(RungeKutta):
|
|
63
|
+
def __init__(self):
|
|
64
|
+
g=AA((2+sqrt(3))/6)
|
|
65
|
+
A=matrix(AA,[[g,0],[1-2*g,g]])
|
|
66
|
+
B=vector([1/AA(2),1/AA(2)])
|
|
67
|
+
title= "Sdirk3 method"
|
|
68
|
+
super().__init__(A,B,title)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SDIRK5(RungeKutta):
|
|
72
|
+
def __init__(self):
|
|
73
|
+
title="SDIRKmethod, order 5"
|
|
74
|
+
# See Hairer and Wanner II, page 100.
|
|
75
|
+
A=matrix(AA,5,5)
|
|
76
|
+
gamm=1/AA(4)
|
|
77
|
+
for i in range(5):
|
|
78
|
+
A[i,i]=gamm
|
|
79
|
+
A[1,0]=1/AA(2)
|
|
80
|
+
A[2,0]=17/AA(50)
|
|
81
|
+
A[3,0]=371/AA(1360)
|
|
82
|
+
A[4,0]=25/AA(24)
|
|
83
|
+
#
|
|
84
|
+
A[2,1]=-1/AA(25)
|
|
85
|
+
A[3,1]=-137/AA(2720)
|
|
86
|
+
|
|
87
|
+
A[4,1]=-49/AA(48)
|
|
88
|
+
#
|
|
89
|
+
A[3,2]=15/AA(544)
|
|
90
|
+
A[4,2]=125/AA(16)
|
|
91
|
+
#
|
|
92
|
+
A[4,3]=-85/AA(12)
|
|
93
|
+
#
|
|
94
|
+
B=vector([25/AA(24),-49/AA(48),125/AA(16),-85/AA(12),1/AA(4)])
|
|
95
|
+
super().__init__(A,B,title)
|
|
96
|
+
class veryBad(RungeKutta):
|
|
97
|
+
def __init__(self):
|
|
98
|
+
title="Classical Runge-Kutta 4 explicit method, ill coded"
|
|
99
|
+
# 1/2,2/6 ... will be converted into floats, which are not exact!
|
|
100
|
+
A=matrix(AA,[[0,0,0,0],[1/2,0,0,0],
|
|
101
|
+
[0,1/2,0,0],[0,0,1,0]])
|
|
102
|
+
B=vector(AA,[1/6,2/6,2/6,1/6])
|
|
103
|
+
super().__init__(A,B,title)
|