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 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
|
"""
Created on 28. aug. 2015
@author: pab
"""
from __future__ import division, print_function
import numpy as np
from scipy import linalg
from scipy.ndimage.filters import convolve1d
import warnings
EPS = np.finfo(float).eps
_EPS = EPS
_TINY = np.finfo(float).tiny
def convolve(sequence, rule, **kwds):
"""Wrapper around scipy.ndimage.convolve1d that allows complex input."""
if np.iscomplexobj(sequence):
return (convolve1d(sequence.real, rule, **kwds) + 1j *
convolve1d(sequence.imag, rule, **kwds))
return convolve1d(sequence, rule, **kwds)
class Dea(object):
"""
LIMEXP is the maximum number of elements the
epsilon table data can contain. The epsilon table
is stored in the first (LIMEXP+2) entries of EPSTAB.
LIST OF MAJOR VARIABLES
-----------------------
E0,E1,E2,E3 - DOUBLE PRECISION
The 4 elements on which the computation of
a new element in the epsilon table is based.
NRES - INTEGER
Number of extrapolation results actually
generated by the epsilon algorithm in prior
calls to the routine.
NEWELM - INTEGER
Number of elements to be computed in the
new diagonal of the epsilon table. The
condensed epsilon table is computed. Only
those elements needed for the computation of
the next diagonal are preserved.
RES - DOUBLE PREISION
New element in the new diagonal of the
epsilon table.
ERROR - DOUBLE PRECISION
An estimate of the absolute error of RES.
Routine decides whether RESULT=RES or
RESULT=SVALUE by comparing ERROR with
ABSERR from the previous call.
RES3LA - DOUBLE PREISION
Vector of DIMENSION 3 containing at most
the last 3 results.
"""
def __init__(self, limexp=3):
self.limexp = 2 * (limexp // 2) + 1
self.epstab = np.zeros(limexp+5)
self.ABSERR = 10.
self._n = 0
self._nres = 0
if (limexp < 3):
raise ValueError('LIMEXP IS LESS THAN 3')
def _compute_error(self, RES3LA, NRES, RES):
fact = [6.0, 2.0, 1.0][min(NRES-1, 2)]
error = fact * np.abs(RES - RES3LA[:NRES]).sum()
return error
def _shift_table(self, EPSTAB, N, NEWELM, NUM):
i_0 = 1 if ((NUM // 2) * 2 == NUM - 1) else 0
i_n = 2 * NEWELM + 2
EPSTAB[i_0:i_n:2] = EPSTAB[i_0 + 2:i_n + 2:2]
if (NUM != N):
i_n = NUM - N
EPSTAB[:N + 1] = EPSTAB[i_n:i_n + N + 1]
return EPSTAB
def _update_RES3LA(self, RES3LA, RESULT, NRES):
if NRES > 2:
RES3LA[:2] = RES3LA[1:]
RES3LA[2] = RESULT
else:
RES3LA[NRES] = RESULT
def __call__(self, SVALUE):
EPSTAB = self.epstab
RES3LA = EPSTAB[-3:]
RESULT = SVALUE
N = self._n
NRES = self._nres
EPSTAB[N] = SVALUE
if (N == 0):
ABSERR = abs(RESULT)
elif (N == 1):
ABSERR = 6.0 * abs(RESULT - EPSTAB[0])
else:
ABSERR = self.ABSERR
EPSTAB[N + 2] = EPSTAB[N]
NEWELM = N // 2
NUM = N
K1 = N
for I in range(NEWELM):
E0 = EPSTAB[K1 - 2]
E1 = EPSTAB[K1 - 1]
E2 = RES = EPSTAB[K1 + 2]
DELTA2, DELTA3 = E2 - E1, E1 - E0
ERR2, ERR3 = abs(DELTA2), abs(DELTA3)
TOL2 = max(abs(E2), abs(E1)) * _EPS
TOL3 = max(abs(E1), abs(E0)) * _EPS
converged = (ERR2 <= TOL2 and ERR3 <= TOL3)
if converged:
ABSERR = ERR2 + ERR3
RESULT = RES
break
if (I != 0):
E3 = EPSTAB[K1]
DELTA1 = E1 - E3
ERR1 = abs(DELTA1)
TOL1 = max(abs(E1), abs(E3)) * _EPS
converged = (ERR1 <= TOL1 or ERR2 <= TOL2 or
ERR3 <= TOL3)
if not converged:
SS = 1.0 / DELTA1 + 1.0 / DELTA2 - 1.0 / DELTA3
else:
converged = (ERR2 <= TOL2 or ERR3 <= TOL3)
if not converged:
SS = 1.0 / DELTA2 - 1.0 / DELTA3
EPSTAB[K1] = E1
if (converged or abs(SS * E1) <= 1e-04):
N = 2 * I
if (NRES == 0):
ABSERR = ERR2 + ERR3
RESULT = RES
else:
RESULT = RES3LA[min(NRES-1, 2)]
break
RES = E1 + 1.0 / SS
EPSTAB[K1] = RES
K1 = K1 - 2
if (NRES == 0):
ABSERR = ERR2 + abs(RES - E2) + ERR3
RESULT = RES
continue
ERROR = self._compute_error(RES3LA, NRES, RES)
if (ERROR > 10.0 * ABSERR):
continue
ABSERR = ERROR
RESULT = RES
else:
ERROR = self._compute_error(RES3LA, NRES, RES)
# 50
if (N == self.limexp - 1):
N = 2 * (self.limexp // 2) - 1
EPSTAB = self._shift_table(EPSTAB, N, NEWELM, NUM)
self._update_RES3LA(RES3LA, RESULT, NRES)
ABSERR = max(ABSERR, 10.0*_EPS * abs(RESULT))
NRES = NRES + 1
N += 1
self._n = N
self._nres = NRES
# EPSTAB[-3:] = RES3LA
self.ABSERR = ABSERR
return RESULT, ABSERR
def test_dea():
def linfun(i):
return np.linspace(0, np.pi/2., 2**i+1)
dea = Dea(limexp=11)
print('NO. PANELS TRAP. APPROX APPROX W/EA ABSERR')
for k in np.arange(10):
x = linfun(k)
val = np.trapz(np.sin(x), x)
vale, err = dea(val)
print('%5d %20.8f %20.8f %20.8f' % (len(x)-1, val, vale, err))
def dea3(v0, v1, v2, symmetric=False):
"""
Extrapolate a slowly convergent sequence
Parameters
----------
v0, v1, v2 : array-like
3 values of a convergent sequence to extrapolate
Returns
-------
result : array-like
extrapolated value
abserr : array-like
absolute error estimate
Description
-----------
DEA3 attempts to extrapolate nonlinearly to a better estimate
of the sequence's limiting value, thus improving the rate of
convergence. The routine is based on the epsilon algorithm of
P. Wynn, see [1]_.
Example
-------
# integrate sin(x) from 0 to pi/2
>>> import numpy as np
>>> from bumps import numdifftools as nd
>>> Ei= np.zeros(3)
>>> linfun = lambda i : np.linspace(0, np.pi/2., 2**(i+5)+1)
>>> for k in np.arange(3):
... x = linfun(k)
... Ei[k] = np.trapz(np.sin(x),x)
>>> [En, err] = nd.dea3(Ei[0], Ei[1], Ei[2])
>>> truErr = Ei-1.
>>> (truErr, err, En)
(array([ -2.00805680e-04, -5.01999079e-05, -1.25498825e-05]),
array([ 0.00020081]), array([ 1.]))
See also
--------
dea
Reference
---------
.. [1] C. Brezinski (1977)
"Acceleration de la convergence en analyse numerique",
"Lecture Notes in Math.", vol. 584,
Springer-Verlag, New York, 1977.
"""
E0, E1, E2 = np.atleast_1d(v0, v1, v2)
abs, max = np.abs, np.maximum # @ReservedAssignment
with warnings.catch_warnings():
warnings.simplefilter("ignore") # ignore division by zero and overflow
delta2, delta1 = E2 - E1, E1 - E0
err2, err1 = abs(delta2), abs(delta1)
tol2, tol1 = max(abs(E2), abs(E1)) * _EPS, max(abs(E1), abs(E0)) * _EPS
delta1[err1 < _TINY] = _TINY
delta2[err2 < _TINY] = _TINY # avoid division by zero and overflow
ss = 1.0 / delta2 - 1.0 / delta1 + _TINY
smalle2 = (abs(ss * E1) <= 1.0e-3)
converged = (err1 <= tol1) & (err2 <= tol2) | smalle2
result = np.where(converged, E2 * 1.0, E1 + 1.0 / ss)
abserr = err1 + err2 + np.where(converged, tol2 * 10, abs(result-E2))
if symmetric and len(result) > 1:
return result[:-1], abserr[1:]
return result, abserr
class Richardson(object):
"""
Extrapolates as sequence with Richardsons method
Notes
-----
Suppose you have series expansion that goes like this
L = f(h) + a0 * h^p_0 + a1 * h^p_1+ a2 * h^p_2 + ...
where p_i = order + step * i and f(h) -> L as h -> 0, but f(0) != L.
If we evaluate the right hand side for different stepsizes h
we can fit a polynomial to that sequence of approximations.
This is exactly what this class does.
Example
-------
>>> import numpy as np
>>> from bumps import numdifftools as nd
>>> n = 3
>>> Ei = np.zeros((n,1))
>>> h = np.zeros((n,1))
>>> linfun = lambda i : np.linspace(0, np.pi/2., 2**(i+5)+1)
>>> for k in np.arange(n):
... x = linfun(k)
... h[k] = x[1]
... Ei[k] = np.trapz(np.sin(x),x)
>>> En, err, step = nd.Richardson(step=1, order=1)(Ei, h)
>>> truErr = Ei-1.
>>> (truErr, err, En)
(array([[ -2.00805680e-04],
[ -5.01999079e-05],
[ -1.25498825e-05]]), array([[ 0.00320501]]), array([[ 1.]]))
"""
def __init__(self, step_ratio=2.0, step=1, order=1, num_terms=2):
self.num_terms = num_terms
self.order = order
self.step = step
self.step_ratio = step_ratio
def _r_matrix(self, num_terms):
step = self.step
i, j = np.ogrid[0:num_terms+1, 0:num_terms]
r_mat = np.ones((num_terms + 1, num_terms + 1))
r_mat[:, 1:] = (1.0 / self.step_ratio) ** (i*(step*j + self.order))
return r_mat
def _get_richardson_rule(self, sequence_length=None):
if sequence_length is None:
sequence_length = self.num_terms + 1
num_terms = min(self.num_terms, sequence_length - 1)
if num_terms > 0:
r_mat = self._r_matrix(num_terms)
return linalg.pinv(r_mat)[0]
return np.ones((1,))
def _estimate_error(self, new_sequence, old_sequence, steps, rule):
m, _n = new_sequence.shape
if m < 2:
return (np.abs(new_sequence) * EPS + steps) * 10.0
cov1 = np.sum(rule**2) # 1 spare dof
fact = np.maximum(12.7062047361747 * np.sqrt(cov1), EPS * 10.)
err = np.abs(np.diff(new_sequence, axis=0)) * fact
tol = np.maximum(np.abs(new_sequence[1:]),
np.abs(new_sequence[:-1])) * EPS * fact
converged = err <= tol
abserr = err + np.where(converged, tol * 10,
abs(new_sequence[:-1]-old_sequence[1:])*fact)
# abserr = err1 + err2 + np.where(converged, tol2 * 10, abs(result-E2))
# abserr = s * fact + np.abs(new_sequence) * EPS * 10.0
return abserr
def extrapolate(self, sequence, steps):
return self.__call__(sequence, steps)
def __call__(self, sequence, steps):
ne = sequence.shape[0]
rule = self._get_richardson_rule(ne)
nr = rule.size - 1
m = ne - nr
new_sequence = convolve(sequence, rule[::-1], axis=0, origin=(nr // 2))
abserr = self._estimate_error(new_sequence, sequence, steps, rule)
return new_sequence[:m], abserr[:m], steps[:m]
if __name__ == '__main__':
pass
|