You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
498 lines
18 KiB
Python
498 lines
18 KiB
Python
'''
|
|
|
|
All the software contained in this library is protected by copyright.
|
|
Permission to use, copy, modify, and distribute this software for any
|
|
purpose without fee is hereby granted, provided that this entire notice
|
|
is included in all copies of any software which is or includes a copy
|
|
or modification of this software and in all copies of the supporting
|
|
documentation for such software.
|
|
|
|
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
|
WARRANTY. IN NO EVENT, NEITHER THE AUTHORS, NOR THE PUBLISHER, NOR ANY
|
|
MEMBER OF THE EDITORIAL BOARD OF THE JOURNAL "NUMERICAL ALGORITHMS",
|
|
NOR ITS EDITOR-IN-CHIEF, BE LIABLE FOR ANY ERROR IN THE SOFTWARE, ANY
|
|
MISUSE OF IT OR ANY DAMAGE ARISING OUT OF ITS USE. THE ENTIRE RISK OF
|
|
USING THE SOFTWARE LIES WITH THE PARTY DOING SO.
|
|
|
|
ANY USE OF THE SOFTWARE CONSTITUTES ACCEPTANCE OF THE TERMS OF THE
|
|
ABOVE STATEMENT.
|
|
|
|
|
|
AUTHORS:
|
|
Per A Brodtkorb
|
|
Python code Based on matlab code written by:
|
|
|
|
Marco Caliari
|
|
University of Verona, Italy
|
|
E-mail: marco.caliari@univr.it
|
|
|
|
Stefano de Marchi, Alvise Sommariva, Marco Vianello
|
|
University of Padua, Italy
|
|
E-mail: demarchi@math.unipd.it, alvise@math.unipd.it,
|
|
marcov@math.unipd.it
|
|
|
|
Reference
|
|
---------
|
|
Padua2DM: fast interpolation and cubature at the Padua points in Matlab/Octave
|
|
NUMERICAL ALGORITHMS, 56 (2011), PP. 45-60
|
|
|
|
|
|
Padua module
|
|
------------
|
|
In polynomial interpolation of two variables, the Padua points are the first
|
|
known example (and up to now the only one) of a unisolvent point set
|
|
(that is, the interpolating polynomial is unique) with minimal growth of their
|
|
Lebesgue constant, proven to be O(log2 n).
|
|
This module provides all the functions needed to perform interpolation and
|
|
cubature at the Padua points, together with the functions and the demos used
|
|
in the paper.
|
|
|
|
pdint.m : main function for interpolation at the Padua points
|
|
pdcub.m : main function for cubature at the Padua points
|
|
pdpts.m : function for the computation of the Padua points
|
|
padua_fit.m : function for the computation of the interpolation
|
|
coefficients by FFT (recommended)
|
|
pdcfsMM.m : function for the computation of the interpolation
|
|
coefficients by matrix multiplications
|
|
pdval.m : function for the evaluation of the interpolation
|
|
polynomial
|
|
pdwtsFFT.m : function for the computation of the cubature
|
|
weights by FFT
|
|
pdwtsMM.m : function for the computation of the cubature
|
|
weights by matrix multiplications (recommended)
|
|
funct.m : function containing some test functions
|
|
demo_pdint.m : demo script for pdint
|
|
demo_cputime_pdint.m : demo script for the computation of CPU time for
|
|
interpolation
|
|
demo_errors_pdint.m : demo script for the comparison of interpolation with
|
|
coefficients computed by FFT or by matrix
|
|
multiplications
|
|
demo_pdcub : demo script for pdcub
|
|
demo_cputime_pdcub.m : demo script for the computation of CPU time for
|
|
cubature
|
|
demo_errors_pdcub.m : demo script for the comparison of cubature with
|
|
weights computed by FFT or by matrix multiplications
|
|
demo_errors_pdcub_gl.m : demo script for the comparison of different cubature
|
|
formulas
|
|
cubature_square.m : function for the computation of some cubature
|
|
formulas for the square
|
|
omelyan_solovyan_rule.m : function for the computation of Omelyan-Solovyan
|
|
cubature points and weights
|
|
Contents.m : Contents file for Matlab
|
|
|
|
|
|
'''
|
|
from __future__ import division
|
|
import numpy as np
|
|
from numpy.fft import fft
|
|
from wafo.dctpack import dct
|
|
# from scipy.fftpack.realtransforms import dct
|
|
|
|
|
|
class _ExampleFunctions(object):
|
|
'''
|
|
Computes test function in the points (x, y)
|
|
|
|
Parameters
|
|
----------
|
|
x,y : array-like
|
|
evaluate the function in the points (x,y)
|
|
id : scalar int (default 0)
|
|
id defining which test function to use. Options are
|
|
0: franke
|
|
1: half_sphere
|
|
2: poly_degree20
|
|
3: exp_fun1
|
|
4: exp_fun100
|
|
5: cos30
|
|
6: constant
|
|
7: exp_xy
|
|
8: runge
|
|
9: abs_cubed
|
|
10: gauss
|
|
11: exp_inv
|
|
|
|
Returns
|
|
-------
|
|
z : array-like
|
|
value of the function in the points (x,y)
|
|
'''
|
|
@staticmethod
|
|
def franke(x, y):
|
|
'''Franke function.
|
|
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 500, is
|
|
2.1547794245591083e+000 with an estimated absolute error of 8.88e-016.
|
|
|
|
The value of the definite integral on the square [0,1] x [0,1],
|
|
obtained using a Padua Points cubature formula of degree 500, is
|
|
4.06969589491556e-01 with an estimated absolute error of 8.88e-016.
|
|
|
|
Maple: 0.40696958949155611906
|
|
'''
|
|
exp = np.exp
|
|
return (3. / 4 * exp(-((9. * x - 2)**2 + (9. * y - 2)**2) / 4) +
|
|
3. / 4 * exp(-(9. * x + 1)**2 / 49 - (9. * y + 1) / 10) +
|
|
1. / 2 * exp(-((9. * x - 7)**2 + (9. * y - 3)**2) / 4) -
|
|
1. / 5 * exp(-(9. * x - 4)**2 - (9. * y - 7)**2))
|
|
|
|
@staticmethod
|
|
def half_sphere(x, y):
|
|
'''The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 2000, is
|
|
3.9129044444568244e+000 with an estimated absolute error of 3.22e-010.
|
|
'''
|
|
return ((x - 0.5)**2 + (y - 0.5)**2)**(1. / 2)
|
|
|
|
@staticmethod
|
|
def poly_degree20(x, y):
|
|
''''Bivariate polynomial having moderate degree.
|
|
The value of the definite integral on the square [-1,1] x
|
|
[-1,1], up to machine precision, is 18157.16017316017 (see ref. 6).
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 500,
|
|
is 1.8157160173160162e+004.
|
|
|
|
2D modification of an example by L.N.Trefethen (see ref. 7), f(x)=x^20.
|
|
'''
|
|
return (x + y)**20
|
|
|
|
@staticmethod
|
|
def exp_fun1(x, y):
|
|
''' The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 2000,
|
|
is 2.1234596326670683e+001 with an estimated absolute error of
|
|
7.11e-015.
|
|
'''
|
|
return np.exp((x - 0.5)**2 + (y - 0.5)**2)
|
|
|
|
@staticmethod
|
|
def exp_fun100(x, y):
|
|
'''The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 2000,
|
|
is 3.1415926535849605e-002 with an estimated absolute error of
|
|
3.47e-017.
|
|
'''
|
|
return np.exp(-100 * ((x - 0.5)**2 + (y - 0.5)**2))
|
|
|
|
@staticmethod
|
|
def cos30(x, y):
|
|
''' The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 500,
|
|
is 4.3386955120336568e-003 with an estimated absolute error of
|
|
2.95e-017.
|
|
'''
|
|
return np.cos(30 * (x + y))
|
|
|
|
@staticmethod
|
|
def constant(x, y):
|
|
'''Constant.
|
|
To test interpolation and cubature at degree 0.
|
|
The value of the definite integral on the square [-1,1] x [-1,1]
|
|
is 4.
|
|
'''
|
|
return np.ones(np.shape(x))
|
|
|
|
@staticmethod
|
|
def exp_xy(x, y):
|
|
'''The value of the definite integral on the square [-1,1] x [-1,1]
|
|
is up to machine precision is 5.524391382167263 (see ref. 6).
|
|
2. The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 500,
|
|
is 5.5243913821672628e+000 with an estimated absolute error of
|
|
0.00e+000.
|
|
2D modification of an example by L.N.Trefethen (see ref. 7),
|
|
f(x)=exp(x).
|
|
'''
|
|
return np.exp(x + y)
|
|
|
|
@staticmethod
|
|
def runge(x, y):
|
|
''' Bivariate Runge function: as 1D complex function is analytic
|
|
in a neighborhood of [-1; 1] but not throughout the complex plane.
|
|
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
up to machine precision, is 0.597388947274307 (see ref. 6).
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 500,
|
|
is 5.9738894727430725e-001 with an estimated absolute error of
|
|
0.00e+000.
|
|
|
|
2D modification of an example by L.N.Trefethen (see ref. 7),
|
|
f(x)=1/(1+16*x^2).
|
|
'''
|
|
return 1. / (1 + 16 * (x**2 + y**2))
|
|
|
|
@staticmethod
|
|
def abs_cubed(x, y):
|
|
'''Low regular function.
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
up to machine precision, is 2.508723139534059 (see ref. 6).
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 500,
|
|
is 2.5087231395340579e+000 with an estimated absolute error of
|
|
0.00e+000.
|
|
|
|
2D modification of an example by L.N.Trefethen (see ref. 7),
|
|
f(x)=abs(x)^3.
|
|
'''
|
|
return (x**2 + y**2)**(3 / 2)
|
|
|
|
@staticmethod
|
|
def gauss(x, y):
|
|
'''Bivariate gaussian: smooth function.
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
up to machine precision, is 2.230985141404135 (see ref. 6).
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 500,
|
|
is 2.2309851414041333e+000 with an estimated absolute error of
|
|
2.66e-015.
|
|
|
|
2D modification of an example by L.N.Trefethen (see ref. 7),
|
|
f(x)=exp(-x^2).
|
|
'''
|
|
return np.exp(-x**2 - y**2)
|
|
|
|
@staticmethod
|
|
def exp_inv(x, y):
|
|
'''Bivariate example stemming from a 1D C-infinity function.
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
up to machine precision, is 0.853358758654305 (see ref. 6).
|
|
The value of the definite integral on the square [-1,1] x [-1,1],
|
|
obtained using a Padua Points cubature formula of degree 2000,
|
|
is 8.5335875865430544e-001 with an estimated absolute error of
|
|
3.11e-015.
|
|
|
|
2D modification of an example by L.N.Trefethen (see ref. 7),
|
|
f(x)=exp(-1/x^2).
|
|
'''
|
|
arg_z = (x**2 + y**2)
|
|
# Avoid cases in which "arg_z=0", setting only in those instances
|
|
# "arg_z=eps".
|
|
arg_z = arg_z + (1 - np.abs(np.sign(arg_z))) * 1.e-100
|
|
arg_z = 1. / arg_z
|
|
return np.exp(-arg_z)
|
|
|
|
def __call__(self, x, y, id=0): # @ReservedAssignment
|
|
s = self
|
|
test_function = [s.franke, s.half_sphere, s.poly_degree20, s.exp_fun1,
|
|
s.exp_fun100, s.cos30, s.constant, s.exp_xy, s.runge,
|
|
s.abs_cubed, s.gauss, s.exp_inv]
|
|
return test_function[id](x, y)
|
|
example_functions = _ExampleFunctions()
|
|
|
|
|
|
def _find_m(n):
|
|
ix = np.r_[1:(n + 1) * (n + 2):2]
|
|
if np.mod(n, 2) == 0:
|
|
n2 = n // 2
|
|
offset = np.array([[0, 1] * n2 + [0, ]] * (n2 + 1))
|
|
ix = ix - offset.ravel(order='F')
|
|
return ix
|
|
|
|
|
|
def padua_points(n, domain=(-1, 1, -1, 1)):
|
|
''' Return Padua points
|
|
|
|
Parameters
|
|
----------
|
|
n : scalar integer
|
|
interpolation degree
|
|
domain : vector [a,b,c,d]
|
|
defining the rectangle [a,b] x [c,d]. (default domain = (-1,1,-1,1))
|
|
|
|
Returns
|
|
-------
|
|
pad : array of shape (2 x (n+1)*(n+2)/2) such that
|
|
(pad[0,:], pad[1,: ]) defines the Padua points in the domain
|
|
rectangle [a,b] x [c,d].
|
|
or
|
|
X1,Y1,X2,Y2 : arrays
|
|
Two subgrids X1,Y1 and X2,Y2 defining the Padua points
|
|
-------------------------------------------------------------------------------
|
|
'''
|
|
a, b, c, d = domain
|
|
t0 = [np.pi] if n == 0 else np.linspace(0, np.pi, n + 1)
|
|
t1 = np.linspace(0, np.pi, n + 2)
|
|
zn = (a + b + (b - a) * np.cos(t0)) / 2
|
|
zn1 = (c + d + (d - c) * np.cos(t1)) / 2
|
|
|
|
Pad1, Pad2 = np.meshgrid(zn, zn1)
|
|
ix = _find_m(n)
|
|
return np.vstack((Pad1.ravel(order='F')[ix],
|
|
Pad2.ravel(order='F')[ix]))
|
|
|
|
|
|
def error_estimate(C0f):
|
|
''' Return interpolation error estimate from Padua coefficients
|
|
'''
|
|
n = C0f.shape[1]
|
|
C0f2 = np.fliplr(C0f)
|
|
errest = sum(np.abs(np.diag(C0f2)))
|
|
if (n >= 1):
|
|
errest = errest + sum(np.abs(np.diag(C0f2, -1)))
|
|
if (n >= 2):
|
|
errest = errest + sum(np.abs(np.diag(C0f2, -2)))
|
|
return 2 * errest
|
|
|
|
|
|
def padua_fit(Pad, fun, *args):
|
|
'''
|
|
Computes the Chebyshevs coefficients
|
|
|
|
so that f(x, y) can be approximated by:
|
|
|
|
f(x, y) = sum cjk*Tjk(x, y)
|
|
|
|
Parameters
|
|
----------
|
|
Pad : array-like
|
|
Padua points, as computed with padua_points function.
|
|
fun : function to be interpolated in the form
|
|
fun(x, y, *args), where *args are optional arguments for fun.
|
|
|
|
Returns
|
|
-------
|
|
coefficents: coefficient matrix
|
|
abs_err : interpolation error estimate
|
|
|
|
'''
|
|
|
|
N = np.shape(Pad)[1]
|
|
# recover the degree n from N = (n+1)(n+2)/2
|
|
n = int(round(-3 + np.sqrt(1 + 8 * N)) / 2)
|
|
C0f = fun(Pad[0], Pad[1], *args)
|
|
if (n > 0):
|
|
ix = _find_m(n)
|
|
GfT = np.zeros((n + 2) * (n + 1))
|
|
GfT[ix] = C0f * 2 / (n * (n + 1))
|
|
GfT = GfT.reshape(n + 1, n + 2)
|
|
GfT = GfT.T
|
|
GfT[0] = GfT[0] / 2
|
|
GfT[n + 1] = GfT[n + 1] / 2
|
|
GfT[:, 0] = GfT[:, 0] / 2
|
|
GfT[:, n] = GfT[:, n] / 2
|
|
Gf = GfT.T
|
|
# compute the interpolation coefficient matrix C0f by FFT
|
|
Gfhat = np.real(fft(Gf, 2 * n, axis=0))
|
|
Gfhathat = np.real(fft(Gfhat[:n + 1, :], 2 * (n + 1), axis=1))
|
|
C0f = 2 * Gfhathat[:, 0:n + 1]
|
|
C0f[0] = C0f[0, :] / np.sqrt(2)
|
|
C0f[:, 0] = C0f[:, 0] / np.sqrt(2)
|
|
C0f = np.fliplr(np.triu(np.fliplr(C0f)))
|
|
C0f[n] = C0f[n] / 2
|
|
|
|
return C0f, error_estimate(C0f)
|
|
|
|
|
|
def paduavals2coefs(f):
|
|
useFFTwhenNisMoreThan = 100
|
|
m = len(f)
|
|
n = int(round(-1.5 + np.sqrt(.25 + 2 * m)))
|
|
x = padua_points(n)
|
|
idx = _find_m(n)
|
|
w = 0 * x[0] + 1. / (n * (n + 1))
|
|
idx1 = np.all(np.abs(x) == 1, axis=0)
|
|
w[idx1] = .5 * w[idx1]
|
|
idx2 = np.all(np.abs(x) != 1, axis=0)
|
|
w[idx2] = 2 * w[idx2]
|
|
|
|
G = np.zeros(idx.max() + 1)
|
|
G[idx] = 4 * w * f
|
|
|
|
if (n < useFFTwhenNisMoreThan):
|
|
t1 = np.r_[0:n + 1].reshape(-1, 1)
|
|
Tn1 = np.cos(t1 * t1.T * np.pi / n)
|
|
t2 = np.r_[0:n + 2].reshape(-1, 1)
|
|
Tn2 = np.cos(t2 * t2.T * np.pi / (n + 1))
|
|
C = np.dot(Tn2, np.dot(G, Tn1))
|
|
else:
|
|
|
|
# dct = @(c) chebtech2.coeffs2vals(c);
|
|
C = np.rot90(dct(dct(G.T).T)) # , axis=1)
|
|
|
|
C[0] = .5 * C[0]
|
|
C[:, 1] = .5 * C[:, 1]
|
|
C[0, -1] = .5 * C[0, -1]
|
|
del C[-1]
|
|
|
|
# Take upper-left triangular part:
|
|
return np.fliplr(np.triu(np.fliplr(C)))
|
|
# C = triu(C(:,end:-1:1));
|
|
# C = C(:,end:-1:1);
|
|
|
|
|
|
# TODO: padua_fit2 does not work correctly yet.
|
|
def padua_fit2(Pad, fun, *args):
|
|
N = np.shape(Pad)[1]
|
|
# recover the degree n from N = (n+1)(n+2)/2
|
|
_n = int(round(-3 + np.sqrt(1 + 8 * N)) / 2)
|
|
C0f = fun(Pad[0], Pad[1], *args)
|
|
return paduavals2coefs(C0f)
|
|
|
|
|
|
def _compute_moments(n):
|
|
k = np.r_[0:n:2]
|
|
mom = 2 * np.sqrt(2) / (1 - k ** 2)
|
|
mom[0] = 2
|
|
return mom
|
|
|
|
|
|
def padua_cubature(coefficients, domain=(-1, 1, -1, 1)):
|
|
'''
|
|
Compute the integral through the coefficient matrix.
|
|
'''
|
|
n = coefficients.shape[1]
|
|
mom = _compute_moments(n)
|
|
M1, M2 = np.meshgrid(mom, mom)
|
|
M = M1 * M2
|
|
C0fM = coefficients[0:n:2, 0:n:2] * M
|
|
a, b, c, d = domain
|
|
integral = (b - a) * (d - c) * C0fM.sum() / 4
|
|
return integral
|
|
|
|
|
|
def padua_val(X, Y, coefficients, domain=(-1, 1, -1, 1), use_meshgrid=False):
|
|
'''
|
|
Evaluate polynomial in padua form at X, Y.
|
|
|
|
Evaluate the interpolation polynomial defined through its coefficient
|
|
matrix coefficients at the target points X(:,1),X(:,2) or at the
|
|
meshgrid(X1,X2)
|
|
|
|
Parameters
|
|
----------
|
|
X, Y: array-like
|
|
evaluation points.
|
|
coefficients : array-like
|
|
coefficient matrix
|
|
domain : a vector [a,b,c,d]
|
|
defining the rectangle [a,b] x [c,d]
|
|
use_meshgrid: bool
|
|
If True interpolate at the points meshgrid(X, Y)
|
|
|
|
Returns
|
|
-------
|
|
fxy : array-like
|
|
evaluation of the interpolation polynomial at the target points
|
|
'''
|
|
X, Y = np.atleast_1d(X, Y)
|
|
original_shape = X.shape
|
|
min, max = np.minimum, np.maximum # @ReservedAssignment
|
|
a, b, c, d = domain
|
|
n = np.shape(coefficients)[1]
|
|
|
|
X1 = min(max(2 * (X.ravel() - a) / (b - a) - 1, -1), 1).reshape(1, -1)
|
|
X2 = min(max(2 * (Y.ravel() - c) / (d - c) - 1, -1), 1).reshape(1, -1)
|
|
tn = np.r_[0:n][:, None]
|
|
TX1 = np.cos(tn * np.arccos(X1))
|
|
TX2 = np.cos(tn * np.arccos(X2))
|
|
TX1[1:n + 1] = TX1[1:n + 1] * np.sqrt(2)
|
|
TX2[1:n + 1] = TX2[1:n + 1] * np.sqrt(2)
|
|
if use_meshgrid: # eval on meshgrid points
|
|
return np.dot(TX1.T, np.dot(coefficients, TX2)).T
|
|
# scattered points
|
|
val = np.sum(np.dot(TX1.T, coefficients) * TX2.T, axis=1)
|
|
return val.reshape(original_shape)
|