-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ongoing work to make nd_cstep the new standard tool
- Loading branch information
Showing
3 changed files
with
301 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,24 @@ | ||
"""numerical differentiation functions: | ||
Derivative, Gradient, Jacobian, and Hessian | ||
Author : pbrod, josef-pkt | ||
License : BSD | ||
Notes | ||
----- | ||
These are simple forward differentiation, so that we have them available | ||
without dependencies. | ||
* Jacobian should be faster than numdifftools.core because it doesn't use loop | ||
over observations. | ||
* numerical precision will vary and depend on the choice of stepsizes | ||
Author: Per A. Brodtkorb | ||
Created: 01.08.2008 | ||
Copyright: (c) pab 2008 | ||
Licence: New BSD | ||
Based on matlab functions derivest.m gradest.m hessdiag.m, hessian.m | ||
and jacobianest.m version 1.0 released 12/27/2006 by John D'Errico | ||
(e-mail: [email protected]) | ||
Also based on the python functions approx_fprime, approx_fprime_cs, | ||
approx_hess_cs, approx_hess1, approx_hess2 and approx_hess3 in the | ||
statsmodels.tools.numdiff module released in 2014 written by Josef Perktold. | ||
""" | ||
|
||
# TODO: | ||
# * some cleanup | ||
# * check numerical accuracy (and bugs) with numdifftools and analytical | ||
# derivatives | ||
# - linear least squares case: (hess - 2*X'X) is 1e-8 or so | ||
# - gradient and Hessian agree with numdifftools when evaluated away from | ||
# minimum | ||
# - forward gradient, Jacobian evaluated at minimum is inaccurate, centered | ||
# (+/- base_step) is ok | ||
# * dot product of Jacobian is different from Hessian, either wrong example or | ||
# a bug (unlikely), or a real difference | ||
# | ||
# | ||
# What are the conditions that Jacobian dotproduct and Hessian are the same? | ||
# | ||
# See also: | ||
# | ||
# BHHH: Greene p481 17.4.6, MLE Jacobian = d loglike / d beta , where loglike | ||
# is vector for each observation | ||
# see also example 17.4 when J'J is very different from Hessian | ||
# also does it hold only at the minimum, what's relationship to covariance | ||
# of Jacobian matrix | ||
# http://projects.scipy.org/scipy/ticket/1157 | ||
# http://en.wikipedia.org/wiki/Levenberg%E2%80%93Marquardt_algorithm | ||
# objective: sum((y-f(beta,x)**2), Jacobian = d f/d beta | ||
# and not d objective/d beta as in MLE Greene similar: | ||
# http://crsouza.blogspot.com/2009/11/neural-network-learning-by-levenberg_18.html#hessian | ||
# | ||
# in example: if J = d x*beta / d beta then J'J == X'X | ||
# similar to | ||
# http://en.wikipedia.org/wiki/Levenberg%E2%80%93Marquardt_algorithm | ||
from __future__ import print_function | ||
import numpy as np | ||
from numdifftools.core import dea3 | ||
from collections import namedtuple | ||
from matplotlib import pyplot as plt | ||
from numdifftools.multicomplex import bicomplex | ||
|
@@ -55,6 +28,8 @@ | |
from scipy.ndimage.filters import convolve1d | ||
import warnings | ||
# NOTE: we only do double precision internally so far | ||
_TINY = np.finfo(float).tiny | ||
_EPS = np.finfo(float).eps | ||
EPS = np.MachAr().eps | ||
_SQRT_J = (1j + 1.0) / np.sqrt(2.0) # = 1j**0.5 | ||
|
||
|
@@ -73,6 +48,268 @@ | |
np.arange(-4, 5))} | ||
|
||
|
||
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 test_epsal(): | ||
HUGE = 1.E+60 | ||
TINY = 1.E-60 | ||
ZERO = 0.E0 | ||
ONE = 1.E0 | ||
true_vals = [0.78539816, 0.94805945, 0.99945672] | ||
E = [] | ||
for N, SOFN in enumerate([0.78539816, 0.94805945, 0.98711580]): | ||
E.append(SOFN) | ||
if N == 0: | ||
ESTLIM = SOFN | ||
else: | ||
AUX2 = ZERO | ||
for J in range(N, 0, -1): | ||
AUX1 = AUX2 | ||
AUX2 = E[J-1] | ||
DIFF = E[J] - AUX2 | ||
if (abs(DIFF) <= TINY): | ||
E[J-1] = HUGE | ||
else: | ||
E[J-1] = AUX1 + ONE/DIFF | ||
|
||
if (N % 2) == 0: | ||
ESTLIM = E[0] | ||
else: | ||
ESTLIM = E[1] | ||
print(ESTLIM, true_vals[N]) | ||
|
||
|
||
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 | ||
>>> 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 | ||
|
||
|
||
def fornberg_weights_all(x, x0, M=1): | ||
''' | ||
Return finite difference weights_and_points for derivatives | ||
|
@@ -1635,7 +1872,8 @@ def main2(): | |
num_extrap = 0 | ||
method = 'complex' | ||
data = [] | ||
for name in ['exp', 'expm1', 'sin', 'cos', 'square']: # function_names[:-3]: | ||
for name in ['exp', 'expm1', 'sin', 'cos', 'square']: | ||
# function_names[:-3]: | ||
for order in range(4, 5, 1): | ||
# order = 1 | ||
for n in range(1, 16, 1): | ||
|
Oops, something went wrong.