Skip to content

Commit 6f78069

Browse files
Lutz Grossclaude
andcommitted
Add table_indexing, fix order-0 boundary, accessor methods and unit tests
- Add `table_indexing='ijk'|'kji'` parameter to InterpolationTable; 'ijk' is the new default (table[ix,iy,iz], shape (nx,ny,nz)) and is transposed internally to the C++ 'kji' convention; deprecated interpolateTable now passes table_indexing='kji' to preserve backward compatibility - Fix C++ order-0 upper boundary: cell-centred domain is [Amin, Amin+n*step) so out-of-bounds check is now `a >= Amin + Astep*(twidth+1)` (was off by one) - Fix extend formula and getExtend() for non-square tables using per-axis counts self._n[i] instead of raw table.shape[i] - Replace properties with get-methods (getOrder, getNdim, getOrigin, getStep, getTableIndexing, getExtend) - Add ~30-test Test_InterpolationTable suite in test_objects.py; wire to Finley, Ripley and Oxley domain runners - Rewrite doc/user/escript.tex interpolation section and int_save.py example to use InterpolationTable with ijk convention - Add scons/Babelsberg_options.py and scons/Babelsberg_nompi_options.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5f7a231 commit 6f78069

12 files changed

Lines changed: 888 additions & 176 deletions

File tree

doc/examples/usersguide/int_save.py

Lines changed: 84 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,67 +11,98 @@
1111
#
1212
##############################################################################
1313

14-
__copyright__="""Copyright (c) 2003-2026 by the esys.escript Group
15-
https://github.com/LutzGross/esys-escript.github.io
16-
Primary Business: Queensland, Australia"""
17-
__license__="""Licensed under the Apache License, version 2.0
18-
http://www.apache.org/licenses/LICENSE-2.0"""
19-
__url__="https://github.com/LutzGross/esys-escript.github.io"
2014

15+
# This example demonstrates table interpolation and saving data in CSV format.
16+
# It corresponds to Section "Interpolating Data" in the User Guide.
2117

22-
23-
# This example demonstrates both interpolation and saving data in CSV format
24-
25-
from esys.escript import saveDataCSV, sup, interpolateTable
18+
from esys.escript import InterpolationTable, saveDataCSV, sup, mkDir
2619
import numpy
2720
try:
28-
from esys.finley import Rectangle
21+
from esys.finley import Rectangle, Brick
2922
HAVE_FINLEY = True
3023
except ImportError:
3124
HAVE_FINLEY = False
3225

3326
if not HAVE_FINLEY:
3427
print("Finley module not available")
3528
else:
36-
37-
n=4 #Change this to whatever you like
38-
r=Rectangle(n,n)
39-
x=r.getX()
40-
x0=x[0]
41-
x1=x[1] #we'll use this later
42-
toobig=100 #An exception will be thrown if interpolation produces a value larger than this
43-
44-
#First one dimensional interpolation
45-
46-
#In this example we will interpolate a sine curve
47-
#The values we take from the domain will range from 0 to 1 (inclusive)
48-
49-
sine_table=[0, 0.70710678118654746, 1, 0.70710678118654746, 0, -0.70710678118654746, -1, -0.70710678118654746, 0]
50-
51-
numslices=len(sine_table)-1
52-
53-
minval=0
54-
maxval=1
55-
56-
step=sup(maxval-minval)/numslices #The width of the gap between entries in the table
57-
58-
result=interpolateTable(sine_table, x0, minval, step, toobig)
59-
60-
#Now we save the input and output for comparison
61-
62-
saveDataCSV("output/1d.csv", inp=x0, out=result)
63-
64-
#Now 2D interpolation
65-
66-
#This time the sine curve will be at full height along the x (ie x0) axis.
67-
#Its amplitude will decrease to a flat line along x1=1.1
68-
69-
#Interpolate works with numpy arrays so we'll use them
70-
st=numpy.array(sine_table)
71-
72-
table=[st, 0.5*st, 0*st ] #Note that this table is 2D
73-
74-
#The y dimension should be the outer the dimension of the table
75-
#Note that the order of tuples for the 2nd and 3rd param is (x,y)
76-
result2=interpolateTable(table, x, (minval,0), (0.55, step), toobig)
77-
saveDataCSV("output/2d.csv",inp0=x0, inp2=x1, out=result2)
29+
mkDir("output")
30+
n = 4
31+
r = Rectangle(n, n)
32+
x = r.getX() # Data with shape (2,)
33+
toobig = 100 # RuntimeError if any interpolated value exceeds this
34+
35+
# ------------------------------------------------------------------ #
36+
# 1-D interpolation — order-1 (linear)
37+
# Approximate a sine curve over [0, 1]
38+
# ------------------------------------------------------------------ #
39+
40+
sine_table = numpy.array([0, 0.70710678118654746, 1, 0.70710678118654746, 0,
41+
-0.70710678118654746, -1, -0.70710678118654746, 0])
42+
minval, maxval = 0., 1.
43+
# n-1 intervals between n sample points
44+
step = (maxval - minval) / (len(sine_table) - 1)
45+
46+
interp1d = InterpolationTable(sine_table, origin=minval, step=step,
47+
order=1, undef=toobig)
48+
result = interp1d(x[0])
49+
saveDataCSV("output/1d.csv", inp=x[0], out=result)
50+
51+
# ------------------------------------------------------------------ #
52+
# 1-D interpolation — order-0 (piecewise constant / cell-centred)
53+
# Each cell i covers [origin + i*step, origin + (i+1)*step)
54+
# Use extend= to specify total domain width directly
55+
# ------------------------------------------------------------------ #
56+
57+
interp1d_o0 = InterpolationTable(sine_table, origin=minval,
58+
extend=(maxval - minval),
59+
order=0, undef=toobig)
60+
result0 = interp1d_o0(x[0])
61+
62+
# ------------------------------------------------------------------ #
63+
# 2-D interpolation — order-1 using "ijk" convention (default)
64+
# table[ix, iy] = value at (xmin + ix*xstep, ymin + iy*ystep)
65+
# Table shape: (nx, ny)
66+
# ------------------------------------------------------------------ #
67+
68+
nx = ny = 10
69+
xstep = ystep = (maxval - minval) / (nx - 1)
70+
xmin = ymin = minval
71+
72+
# Sine amplitude decreases from 1 at y=0 to 0 at y=maxval
73+
st = sine_table # length 9
74+
# Build a 9 x 3 table: table[ix, iy], shape (9, 3)
75+
# y covers [0, 0.55*2] in 3 rows (just as in the original example)
76+
table2 = numpy.array([st, 0.5 * st, 0. * st]).T # shape (9, 3)
77+
x2step = step # step along x (sine)
78+
y2step = 0.55 # step along y
79+
80+
interp2d = InterpolationTable(table2, origin=(minval, 0.),
81+
step=(x2step, y2step),
82+
order=1, undef=toobig)
83+
result2 = interp2d(x) # x has shape (2,)
84+
saveDataCSV("output/2d.csv", inp0=x[0], inp1=x[1], out=result2)
85+
86+
# ------------------------------------------------------------------ #
87+
# 3-D interpolation — order-1 using "ijk" convention (default)
88+
# table[ix, iy, iz] = ix + iy*10 + iz*100
89+
# Table shape: (nx, ny, nz)
90+
# ------------------------------------------------------------------ #
91+
92+
b = Brick(n, n, n)
93+
x3 = b.getX() # shape (3,)
94+
toobig3 = 1000000
95+
96+
nx3 = ny3 = nz3 = 10
97+
xstep3 = ystep3 = zstep3 = (maxval - minval) / (nx3 - 1)
98+
99+
table3 = numpy.array([[[ix + iy * 10 + iz * 100
100+
for iz in range(nz3)]
101+
for iy in range(ny3)]
102+
for ix in range(nx3)], dtype=float)
103+
104+
interp3d = InterpolationTable(table3,
105+
origin=(minval, minval, minval),
106+
step=(xstep3, ystep3, zstep3),
107+
order=1, undef=toobig3)
108+
result3 = interp3d(x3)

doc/user/escript.tex

Lines changed: 131 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,109 +1421,162 @@ \subsection{Functions of \Data objects}
14211421

14221422

14231423
\subsection{Interpolating Data}
1424-
\index{interpolateTable}
1424+
\index{InterpolationTable}
14251425
\label{sec:interpolation}
1426-
In some cases, it may be useful to produce Data objects which fit some user
1427-
defined function.
1428-
Manually modifying each value in the Data object is not a good idea since it
1429-
depends on knowing the location and order of each data point in the domain.
1430-
Instead, \escript can use an interpolation table to produce a \Data object.
1426+
In some cases it is useful to produce \Data objects which approximate a
1427+
user-defined function without accessing individual data points directly.
1428+
\escript provides the \class{InterpolationTable} class for this purpose.
1429+
It performs order-1 (linear) or order-0 (piecewise constant) interpolation
1430+
from a regular-grid lookup table onto a mesh.
14311431

1432-
The following example is available as \file{int_save.py} in the \ExampleDirectory.
1433-
We will produce a \Data object which approximates a sine curve.
1432+
The following examples are available as \file{int\_save.py} in the
1433+
\ExampleDirectory.
1434+
1435+
\subsubsection{Setting up the domain}
14341436

14351437
\begin{python}
1436-
from esys.escript import saveDataCSV, sup, interpolateTable
1437-
import numpy
1438-
from esys.finley import Rectangle
1438+
from esys.escript import InterpolationTable, saveDataCSV, sup
1439+
import numpy
1440+
from esys.finley import Rectangle, Brick
1441+
1442+
n = 4
1443+
r = Rectangle(n, n)
1444+
x = r.getX() # Data with shape (2,)
1445+
toobig = 100 # RuntimeError if any interpolated value exceeds this
1446+
\end{python}
1447+
1448+
\subsubsection{1-D interpolation (order-1)}
1449+
1450+
We approximate a sine curve using a nine-point table covering $[0,1]$:
14391451

1440-
n=4
1441-
r=Rectangle(n,n)
1442-
x=r.getX()
1443-
toobig=100
1452+
\begin{python}
1453+
sine_table = numpy.array([0, 0.70710678, 1, 0.70710678, 0,
1454+
-0.70710678, -1, -0.70710678, 0])
1455+
minval, maxval = 0., 1.
1456+
# n-1 intervals between n sample points
1457+
step = (maxval - minval) / (len(sine_table) - 1)
1458+
1459+
interp = InterpolationTable(sine_table, origin=minval, step=step,
1460+
order=1, undef=toobig)
1461+
result = interp(x[0]) # x[0] has shape ()
14441462
\end{python}
14451463

1446-
\noindent First we produce an interpolation table:
1464+
\noindent Alternatively, the domain extent can be given directly with
1465+
\var{extend} instead of \var{step}:
1466+
14471467
\begin{python}
1448-
sine_table=[0, 0.70710678118654746, 1, 0.70710678118654746, 0,
1449-
-0.70710678118654746, -1, -0.70710678118654746, 0]
1468+
interp = InterpolationTable(sine_table, origin=minval,
1469+
extend=(maxval - minval), order=1, undef=toobig)
14501470
\end{python}
1451-
%
1452-
We wish to identify $0$ and $1$ with the ends of the curve, that is
1453-
with the first and eighth value in the table.
1471+
1472+
\noindent By default, values outside the table range are clamped to the
1473+
nearest boundary entry. Passing \code{check\_boundaries=True} raises a
1474+
\exception{RuntimeError} instead.
1475+
1476+
\subsubsection{1-D interpolation (order-0, cell-centred)}
1477+
1478+
Order-0 (piecewise constant) uses a cell-centred convention: cell $i$
1479+
covers $[\var{origin}+i\cdot\var{step},\; \var{origin}+(i+1)\cdot\var{step})$
1480+
with value \code{table[i]}.
1481+
The domain therefore spans $[\var{origin},\;\var{origin}+n\cdot\var{step}]$
1482+
where $n$ is the length of the table, and \code{extend} gives that total width
1483+
directly:
14541484

14551485
\begin{python}
1456-
numslices=len(sine_table)-1
1457-
minval=0.
1458-
maxval=1.
1459-
step=sup(maxval-minval)/numslices
1486+
interp0 = InterpolationTable(sine_table, origin=minval,
1487+
extend=(maxval - minval), order=0, undef=toobig)
1488+
result0 = interp0(x[0])
14601489
\end{python}
1461-
%
1462-
So the values $v$ from the input lie in the interval
1463-
\var{minval} $\leq v <$ \var{maxval}.
1464-
\var{step} represents the gap (in the input range) between entries in the table.
1465-
By default, values of $v$ outside the table argument range (minval, maxval)
1466-
will be pushed back into the range, i.e. if $v <$ \var{minval} the value
1467-
\var{minval} will be used to evaluate the table.
1468-
Similarly, for values $v>$ \var{maxval} the value \var{maxval} is used.
14691490

1470-
Now we produce our new \Data object:
1491+
\subsubsection{Table indexing convention}
1492+
\label{sec:table_indexing}
1493+
1494+
\class{InterpolationTable} supports two table layouts selected by the
1495+
\var{table\_indexing} keyword argument:
1496+
1497+
\begin{description}
1498+
\item[\code{"ijk"} (default)] The \emph{first} numpy index runs along the
1499+
$x$-axis (first coordinate), the second along $y$, the third along $z$.
1500+
A 2-D table has shape \code{(nx, ny)}, a 3-D table \code{(nx, ny, nz)}.
1501+
This is the natural numpy row-major order for spatial data indexed by
1502+
coordinate.
1503+
\item[\code{"kji"}] The \emph{first} numpy index runs along the $z$-axis,
1504+
the second along $y$, the third along $x$.
1505+
A 2-D table has shape \code{(ny, nx)}, a 3-D table \code{(nz, ny, nx)}.
1506+
This matches the convention used by the legacy \function{interpolateTable}
1507+
function.
1508+
\end{description}
1509+
1510+
\subsubsection{2-D interpolation}
1511+
1512+
We interpolate from a plane where $(x,y)\mapsto x + y\cdot10$ for
1513+
$x,y\in[0,9]$.
1514+
Using the default \code{"ijk"} convention the table has shape
1515+
\code{(nx, ny)} with \code{table[ix, iy]} holding the value at grid
1516+
point $(x_{\min}+\var{ix}\cdot\var{dx},\; y_{\min}+\var{iy}\cdot\var{dy})$:
14711517

14721518
\begin{python}
1473-
result=interpolateTable(sine_table, x[0], minval, step, toobig)
1519+
nx = ny = 10
1520+
xstep = ystep = (maxval - minval) / (nx - 1)
1521+
xmin = ymin = minval
1522+
1523+
# "ijk": table[ix, iy], shape (nx, ny)
1524+
table2 = numpy.array([[ix + iy * 10 for iy in range(ny)]
1525+
for ix in range(nx)], dtype=float)
1526+
1527+
interp2 = InterpolationTable(table2, origin=(xmin, ymin),
1528+
step=(xstep, ystep), order=1, undef=toobig)
1529+
result2 = interp2(x) # x has shape (2,)
14741530
\end{python}
1475-
Any values which interpolate to larger than \var{toobig} will raise an
1476-
exception. You can switch on boundary checking by adding
1477-
\code{check_boundaries=True} to the argument list.
14781531

1479-
Now consider a 2D example. We will interpolate from a plane where $\forall x,y\in[0,9]:(x,y)=x+y\cdot10$.
1532+
\subsubsection{3-D interpolation}
14801533

14811534
\begin{python}
1482-
from esys.escript import whereZero
1483-
table2=[]
1484-
for y in range(0,10):
1485-
r=[]
1486-
for x in range(0,10):
1487-
r.append(x+y*10)
1488-
table2.append(r)
1489-
xstep=(maxval-minval)/(10-1)
1490-
ystep=(maxval-minval)/(10-1)
1491-
1492-
xmin=minval
1493-
ymin=minval
1494-
1495-
result2=interpolateTable(table2, x2, (xmin, ymin), (xstep, ystep), toobig)
1535+
from esys.finley import Brick
1536+
b = Brick(n, n, n)
1537+
x3 = b.getX() # shape (3,)
1538+
toobig = 1000000
1539+
1540+
nz = 10
1541+
zstep = (maxval - minval) / (nz - 1)
1542+
zmin = minval
1543+
1544+
# "ijk": table[ix, iy, iz], shape (nx, ny, nz)
1545+
table3 = numpy.array([[[ix + iy*10 + iz*100 for iz in range(nz)]
1546+
for iy in range(ny)]
1547+
for ix in range(nx)], dtype=float)
1548+
1549+
interp3 = InterpolationTable(table3, origin=(xmin, ymin, zmin),
1550+
step=(xstep, ystep, zstep),
1551+
order=1, undef=toobig)
1552+
result3 = interp3(x3)
14961553
\end{python}
14971554

1498-
We can check the values using \function{whereZero}.
1499-
For example, for $x=0$:
1555+
\subsubsection{Accessor methods}
1556+
1557+
After construction, the parameters can be queried:
1558+
15001559
\begin{python}
1501-
print(result2*whereZero(x[0]))
1560+
interp.getOrder() # 0 or 1
1561+
interp.getNdim() # 1, 2, or 3
1562+
interp.getOrigin() # tuple of starting coordinates
1563+
interp.getStep() # tuple of cell sizes
1564+
interp.getExtend() # tuple of domain extents
1565+
interp.getTableIndexing() # "ijk" or "kji"
15021566
\end{python}
15031567

1504-
Finally let us look at a 3D example. Note that the parameter tuples should be
1505-
$(x,y,z)$ but that in the interpolation table, $x$ is the innermost dimension.
1568+
\subsubsection{Deprecated \function{interpolateTable}}
1569+
1570+
The free function \function{interpolateTable} is deprecated and will be
1571+
removed in a future release. It uses the \code{"kji"} table convention.
1572+
Replace calls of the form
1573+
\begin{python}
1574+
result = interpolateTable(tab, dat, start, step, undef)
1575+
\end{python}
1576+
with
15061577
\begin{python}
1507-
b=Brick(n,n,n)
1508-
x3=b.getX()
1509-
toobig=1000000
1510-
1511-
table3=[]
1512-
for z in range(0,10):
1513-
face=[]
1514-
for y in range(0,10):
1515-
r=[]
1516-
for x in range(0,10):
1517-
r.append(x+y*10+z*100)
1518-
face.append(r)
1519-
table3.append(face);
1520-
1521-
zstep=(maxval-minval)/(10-1)
1522-
1523-
zmin=minval
1524-
1525-
result3=interpolateTable(table3, x3, (xmin, ymin, zmin),
1526-
(xstep, ystep, zstep), toobig)
1578+
result = InterpolationTable(tab, start, step, order=1, undef=undef,
1579+
table_indexing="kji")(dat)
15271580
\end{python}
15281581

15291582

0 commit comments

Comments
 (0)