Skip to content

Commit 7b985e8

Browse files
authored
Merge pull request #3 from vtopt/python-wrapper-update-2020-12
Updated python wrapper and example code
2 parents 6a80558 + 1ba1d28 commit 7b985e8

File tree

2 files changed

+81
-43
lines changed

2 files changed

+81
-43
lines changed

python/delsparse.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
#
99
fort_compiler = "gfortran"
1010
shared_object_name = "delsparse_clib.so"
11-
path_to_lib = os.path.join(os.curdir, shared_object_name)
11+
source_dir = os.path.abspath(os.path.dirname(__file__))
12+
path_to_lib = os.path.join(source_dir, shared_object_name)
1213
compile_options = "-fPIC -shared -O3 -fopenmp -std=legacy"
1314
# ^^ 'fPIC' and 'shared' are required. 'O3' is for speed and 'fopenmp'
1415
# is necessary for supporting CPU parallelism during execution.
@@ -29,6 +30,13 @@
2930
# Remove the shared object if it exists, because it is faulty.
3031
if os.path.exists(shared_object_name):
3132
os.remove(shared_object_name)
33+
# Warn the user if they are using a local blas and lapack that
34+
# this is known to cause extrapolation errors.
35+
if (blas_lapack == "blas.f lapack.f"):
36+
import warnings
37+
warnings.warn("\n The provided 'blas.f' and 'lapack.f' are known to cause extrapolation errors."+
38+
"\n Consider using local libraries via compiler flags instead (see config"+
39+
"\n coments for 'blas_lapack' in '"+os.path.join(path_to_lib,__file__)+"').")
3240
# Compile a new shared object.
3341
command = " ".join([fort_compiler, compile_options, blas_lapack,
3442
ordered_dependencies, "-o", path_to_lib])
@@ -280,7 +288,10 @@ def delaunaysparses(d, n, pts, m, q, simps, weights, ierr, interp_in=None, inter
280288

281289
# Setting up "ierr"
282290
ierr_local = np.asarray(ierr, dtype=ctypes.c_int)
283-
ierr_dim_1 = ctypes.c_int(ierr_local.shape[0])
291+
# In accordance with how the Fortran code might be normally used,
292+
# and mathematical notation, grabbing the last dimension allows
293+
# ierr to be passed as a column vector instead of a flat vector.
294+
ierr_dim_1 = ctypes.c_int(ierr_local.shape[-1])
284295

285296
# Setting up "interp_in"
286297
interp_in_present = ctypes.c_bool(True)
@@ -339,12 +350,18 @@ def delaunaysparses(d, n, pts, m, q, simps, weights, ierr, interp_in=None, inter
339350
rnorm_present = ctypes.c_bool(False)
340351
rnorm = np.zeros(shape=(1), dtype=ctypes.c_double, order='F')
341352
elif (type(rnorm) == bool) and (rnorm):
353+
# In accordance with how the Fortran code might be normally used,
354+
# and mathematical notation, grabbing the last dimension allows
355+
# rnorm to be passed as a column vector instead of a flat vector.
342356
rnorm = np.zeros(shape=(1), dtype=ctypes.c_double, order='F')
343-
rnorm_dim_1 = ctypes.c_int(rnorm.shape[0])
357+
rnorm_dim_1 = ctypes.c_int(rnorm.shape[-1])
344358
elif (not np.asarray(rnorm).flags.f_contiguous):
345359
raise(Exception("The numpy array given as argument 'rnorm' was not f_contiguous."))
346360
else:
347-
rnorm_dim_1 = ctypes.c_int(rnorm.shape[0])
361+
# In accordance with how the Fortran code might be normally used,
362+
# and mathematical notation, grabbing the last dimension allows
363+
# rnorm to be passed as a column vector instead of a flat vector.
364+
rnorm_dim_1 = ctypes.c_int(rnorm.shape[-1])
348365
rnorm_local = np.asarray(rnorm, dtype=ctypes.c_double)
349366

350367
# Setting up "ibudget"
@@ -630,7 +647,10 @@ def delaunaysparsep(d, n, pts, m, q, simps, weights, ierr, interp_in=None, inter
630647

631648
# Setting up "ierr"
632649
ierr_local = np.asarray(ierr, dtype=ctypes.c_int)
633-
ierr_dim_1 = ctypes.c_int(ierr_local.shape[0])
650+
# In accordance with how the Fortran code might be normally used,
651+
# and mathematical notation, grabbing the last dimension allows
652+
# ierr to be passed as a column vector instead of a flat vector.
653+
ierr_dim_1 = ctypes.c_int(ierr_local.shape[-1])
634654

635655
# Setting up "interp_in"
636656
interp_in_present = ctypes.c_bool(True)
@@ -690,11 +710,17 @@ def delaunaysparsep(d, n, pts, m, q, simps, weights, ierr, interp_in=None, inter
690710
rnorm = np.zeros(shape=(1), dtype=ctypes.c_double, order='F')
691711
elif (type(rnorm) == bool) and (rnorm):
692712
rnorm = np.zeros(shape=(1), dtype=ctypes.c_double, order='F')
693-
rnorm_dim_1 = rnorm.shape[0]
713+
# In accordance with how the Fortran code might be normally used,
714+
# and mathematical notation, grabbing the last dimension allows
715+
# rnorm to be passed as a column vector instead of a flat vector.
716+
rnorm_dim_1 = rnorm.shape[-1]
694717
elif (not np.asarray(rnorm).flags.f_contiguous):
695718
raise(Exception("The numpy array given as argument 'rnorm' was not f_contiguous."))
696719
else:
697-
rnorm_dim_1 = ctypes.c_int(rnorm.shape[0])
720+
# In accordance with how the Fortran code might be normally used,
721+
# and mathematical notation, grabbing the last dimension allows
722+
# rnorm to be passed as a column vector instead of a flat vector.
723+
rnorm_dim_1 = ctypes.c_int(rnorm.shape[-1])
698724
rnorm_local = np.asarray(rnorm, dtype=ctypes.c_double)
699725

700726
# Setting up "ibudget"

python/example.py

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
# Import the Delaunay Fortran code.
33
import delsparse
44

5-
# List out the "help" documentation.
6-
# help(delsparse)
7-
85
# Return the source point indices and weights associated with a set of
96
# interpolation points. Takes points in row-major (C style) format.
107
#
@@ -79,41 +76,56 @@ class Extrapolation(Exception): pass
7976
# Return the appropriate shaped pair of points and weights
8077
return (indices, weights)
8178

79+
# This testing code is placed in a `main` block in case someone
80+
# copies this file to use the 'delaunay_simplex' function.
81+
if __name__ == "__main__":
82+
# List out the "help" documentation.
83+
# help(delsparse)
8284

83-
# Declare some test function.
84-
import numpy as np
85-
f = lambda x: 3*x[0]+.5*np.cos(8*x[0])+np.sin(5*x[-1])
86-
np.random.seed(0)
85+
# Declare some test function.
86+
import numpy as np
87+
f = lambda x: 3*x[0]+.5*np.cos(8*x[0])+np.sin(5*x[-1])
88+
np.random.seed(0)
8789

88-
# Construct a function that converts indices and weights into a real number prediction.
89-
def delaunay_approx(q, points, values):
90-
q = np.array(q, dtype=float)
91-
if len(q.shape) == 1:
92-
pts, wts = delaunay_simplex(points.copy(), np.reshape(q,(1,len(q))))
93-
return sum(values[pts[0]]*wts[0])
94-
else:
95-
pts, wts = delaunay_simplex(points.copy(), q)
96-
return np.asarray([sum(values[i]*w) for (i,w) in zip(pts,wts)], dtype=float)
90+
# Generate test data.
91+
d = 2
92+
test_size = 1000
93+
train_sizes = (10, 50, 100, 200, 500, 1000, 5000, 10000)
94+
# Construct the "test" data (q, f_q).
95+
q = np.random.random(size=(test_size,d))
96+
f_q = np.asarray(list(map(f,q)), dtype=float)
97+
# Construct initial "train" data (x, y).
98+
x = np.random.random(size=(train_sizes[0],d))
99+
y = np.asarray(list(map(f,x)), dtype=float)
97100

98-
# Generate test data.
99-
test_size = 1000
100-
q = np.random.random(size=(test_size,2))
101-
f_q = np.asarray(list(map(f,q)), dtype=float)
101+
# Construct a function that converts indices and weights into a real number prediction.
102+
def delaunay_approx(q, points, values):
103+
q = np.array(q, dtype=float)
104+
if len(q.shape) == 1:
105+
inds, wts = delaunay_simplex(points.copy(), np.reshape(q,(1,len(q))))
106+
return np.dot(values[inds[0]], wts[0])
107+
else:
108+
inds, wts = delaunay_simplex(points.copy(), q)
109+
vals = values[inds.flatten()].reshape(wts.shape)
110+
return np.sum(vals * wts, axis=1)
102111

103-
# Show the convergence on the true function.
104-
for train_size in (10, 50, 100, 200, 500, 1000, 5000, 10000):
105-
# Construct "training" data.
106-
x = np.random.random(size=(train_size,2))
107-
y = np.asarray([f(_) for _ in x], dtype=float)
108-
# Approximate at points.
109-
f_hat = delaunay_approx(q, x, y)
110-
# Compute errors.
111-
abs_error = abs(f_hat - f_q)
112-
avg_abs_error = sum(abs_error) / test_size
113-
max_abs_error = max(abs_error)
114-
# Show errors.
115-
print()
116-
print("Train size:", train_size)
117-
print(" maximum absolute error: %.2f"%(max_abs_error))
118-
print(" average absolute error: %.2f"%(avg_abs_error))
112+
# Show convergence by adding more points to the training set.
113+
for n in train_sizes:
114+
# Add more random points to the "training" set.
115+
if (n > len(x)):
116+
new_points = np.random.random(size=(n-len(x),d))
117+
new_values = np.asarray(list(map(f,new_points)), dtype=float)
118+
x = np.concatenate( (x,new_points), axis=0 )
119+
y = np.concatenate( (y,new_values) )
120+
# Approximate at points.
121+
f_hat = delaunay_approx(q, x, y)
122+
# Compute errors.
123+
abs_error = abs(f_hat - f_q)
124+
avg_abs_error = sum(abs_error) / test_size
125+
max_abs_error = max(abs_error)
126+
# Show errors.
127+
print()
128+
print("Train size:", n)
129+
print(" maximum absolute error: %.2f"%(max_abs_error))
130+
print(" average absolute error: %.2f"%(avg_abs_error))
119131

0 commit comments

Comments
 (0)