1404 lines
54 KiB
Python
1404 lines
54 KiB
Python
import numpy as np
|
|
from numpy.testing import (
|
|
assert_array_equal, assert_array_almost_equal, assert_array_less)
|
|
import numpy.ma.testutils as matest
|
|
import pytest
|
|
|
|
import matplotlib as mpl
|
|
import matplotlib.pyplot as plt
|
|
import matplotlib.tri as mtri
|
|
from matplotlib.path import Path
|
|
from matplotlib.testing.decorators import image_comparison, check_figures_equal
|
|
|
|
|
|
class TestTriangulationParams:
|
|
x = [-1, 0, 1, 0]
|
|
y = [0, -1, 0, 1]
|
|
triangles = [[0, 1, 2], [0, 2, 3]]
|
|
mask = [False, True]
|
|
|
|
@pytest.mark.parametrize('args, kwargs, expected', [
|
|
([x, y], {}, [x, y, None, None]),
|
|
([x, y, triangles], {}, [x, y, triangles, None]),
|
|
([x, y], dict(triangles=triangles), [x, y, triangles, None]),
|
|
([x, y], dict(mask=mask), [x, y, None, mask]),
|
|
([x, y, triangles], dict(mask=mask), [x, y, triangles, mask]),
|
|
([x, y], dict(triangles=triangles, mask=mask), [x, y, triangles, mask])
|
|
])
|
|
def test_extract_triangulation_params(self, args, kwargs, expected):
|
|
other_args = [1, 2]
|
|
other_kwargs = {'a': 3, 'b': '4'}
|
|
x_, y_, triangles_, mask_, args_, kwargs_ = \
|
|
mtri.Triangulation._extract_triangulation_params(
|
|
args + other_args, {**kwargs, **other_kwargs})
|
|
x, y, triangles, mask = expected
|
|
assert x_ is x
|
|
assert y_ is y
|
|
assert_array_equal(triangles_, triangles)
|
|
assert mask_ is mask
|
|
assert args_ == other_args
|
|
assert kwargs_ == other_kwargs
|
|
|
|
|
|
def test_extract_triangulation_positional_mask():
|
|
# mask cannot be passed positionally
|
|
mask = [True]
|
|
args = [[0, 2, 1], [0, 0, 1], [[0, 1, 2]], mask]
|
|
x_, y_, triangles_, mask_, args_, kwargs_ = \
|
|
mtri.Triangulation._extract_triangulation_params(args, {})
|
|
assert mask_ is None
|
|
assert args_ == [mask]
|
|
# the positional mask must be caught downstream because this must pass
|
|
# unknown args through
|
|
|
|
|
|
def test_triangulation_init():
|
|
x = [-1, 0, 1, 0]
|
|
y = [0, -1, 0, 1]
|
|
with pytest.raises(ValueError, match="x and y must be equal-length"):
|
|
mtri.Triangulation(x, [1, 2])
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r"triangles must be a \(N, 3\) int array, but found shape "
|
|
r"\(3,\)"):
|
|
mtri.Triangulation(x, y, [0, 1, 2])
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r"triangles must be a \(N, 3\) int array, not 'other'"):
|
|
mtri.Triangulation(x, y, 'other')
|
|
with pytest.raises(ValueError, match="found value 99"):
|
|
mtri.Triangulation(x, y, [[0, 1, 99]])
|
|
with pytest.raises(ValueError, match="found value -1"):
|
|
mtri.Triangulation(x, y, [[0, 1, -1]])
|
|
|
|
|
|
def test_triangulation_set_mask():
|
|
x = [-1, 0, 1, 0]
|
|
y = [0, -1, 0, 1]
|
|
triangles = [[0, 1, 2], [2, 3, 0]]
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
|
|
# Check neighbors, which forces creation of C++ triangulation
|
|
assert_array_equal(triang.neighbors, [[-1, -1, 1], [-1, -1, 0]])
|
|
|
|
# Set mask
|
|
triang.set_mask([False, True])
|
|
assert_array_equal(triang.mask, [False, True])
|
|
|
|
# Reset mask
|
|
triang.set_mask(None)
|
|
assert triang.mask is None
|
|
|
|
msg = r"mask array must have same length as triangles array"
|
|
for mask in ([False, True, False], [False], [True], False, True):
|
|
with pytest.raises(ValueError, match=msg):
|
|
triang.set_mask(mask)
|
|
|
|
|
|
def test_delaunay():
|
|
# No duplicate points, regular grid.
|
|
nx = 5
|
|
ny = 4
|
|
x, y = np.meshgrid(np.linspace(0.0, 1.0, nx), np.linspace(0.0, 1.0, ny))
|
|
x = x.ravel()
|
|
y = y.ravel()
|
|
npoints = nx*ny
|
|
ntriangles = 2 * (nx-1) * (ny-1)
|
|
nedges = 3*nx*ny - 2*nx - 2*ny + 1
|
|
|
|
# Create delaunay triangulation.
|
|
triang = mtri.Triangulation(x, y)
|
|
|
|
# The tests in the remainder of this function should be passed by any
|
|
# triangulation that does not contain duplicate points.
|
|
|
|
# Points - floating point.
|
|
assert_array_almost_equal(triang.x, x)
|
|
assert_array_almost_equal(triang.y, y)
|
|
|
|
# Triangles - integers.
|
|
assert len(triang.triangles) == ntriangles
|
|
assert np.min(triang.triangles) == 0
|
|
assert np.max(triang.triangles) == npoints-1
|
|
|
|
# Edges - integers.
|
|
assert len(triang.edges) == nedges
|
|
assert np.min(triang.edges) == 0
|
|
assert np.max(triang.edges) == npoints-1
|
|
|
|
# Neighbors - integers.
|
|
# Check that neighbors calculated by C++ triangulation class are the same
|
|
# as those returned from delaunay routine.
|
|
neighbors = triang.neighbors
|
|
triang._neighbors = None
|
|
assert_array_equal(triang.neighbors, neighbors)
|
|
|
|
# Is each point used in at least one triangle?
|
|
assert_array_equal(np.unique(triang.triangles), np.arange(npoints))
|
|
|
|
|
|
def test_delaunay_duplicate_points():
|
|
npoints = 10
|
|
duplicate = 7
|
|
duplicate_of = 3
|
|
|
|
np.random.seed(23)
|
|
x = np.random.random(npoints)
|
|
y = np.random.random(npoints)
|
|
x[duplicate] = x[duplicate_of]
|
|
y[duplicate] = y[duplicate_of]
|
|
|
|
# Create delaunay triangulation.
|
|
triang = mtri.Triangulation(x, y)
|
|
|
|
# Duplicate points should be ignored, so the index of the duplicate points
|
|
# should not appear in any triangle.
|
|
assert_array_equal(np.unique(triang.triangles),
|
|
np.delete(np.arange(npoints), duplicate))
|
|
|
|
|
|
def test_delaunay_points_in_line():
|
|
# Cannot triangulate points that are all in a straight line, but check
|
|
# that delaunay code fails gracefully.
|
|
x = np.linspace(0.0, 10.0, 11)
|
|
y = np.linspace(0.0, 10.0, 11)
|
|
with pytest.raises(RuntimeError):
|
|
mtri.Triangulation(x, y)
|
|
|
|
# Add an extra point not on the line and the triangulation is OK.
|
|
x = np.append(x, 2.0)
|
|
y = np.append(y, 8.0)
|
|
mtri.Triangulation(x, y)
|
|
|
|
|
|
@pytest.mark.parametrize('x, y', [
|
|
# Triangulation should raise a ValueError if passed less than 3 points.
|
|
([], []),
|
|
([1], [5]),
|
|
([1, 2], [5, 6]),
|
|
# Triangulation should also raise a ValueError if passed duplicate points
|
|
# such that there are less than 3 unique points.
|
|
([1, 2, 1], [5, 6, 5]),
|
|
([1, 2, 2], [5, 6, 6]),
|
|
([1, 1, 1, 2, 1, 2], [5, 5, 5, 6, 5, 6]),
|
|
])
|
|
def test_delaunay_insufficient_points(x, y):
|
|
with pytest.raises(ValueError):
|
|
mtri.Triangulation(x, y)
|
|
|
|
|
|
def test_delaunay_robust():
|
|
# Fails when mtri.Triangulation uses matplotlib.delaunay, works when using
|
|
# qhull.
|
|
tri_points = np.array([
|
|
[0.8660254037844384, -0.5000000000000004],
|
|
[0.7577722283113836, -0.5000000000000004],
|
|
[0.6495190528383288, -0.5000000000000003],
|
|
[0.5412658773652739, -0.5000000000000003],
|
|
[0.811898816047911, -0.40625000000000044],
|
|
[0.7036456405748561, -0.4062500000000004],
|
|
[0.5953924651018013, -0.40625000000000033]])
|
|
test_points = np.asarray([
|
|
[0.58, -0.46],
|
|
[0.65, -0.46],
|
|
[0.65, -0.42],
|
|
[0.7, -0.48],
|
|
[0.7, -0.44],
|
|
[0.75, -0.44],
|
|
[0.8, -0.48]])
|
|
|
|
# Utility function that indicates if a triangle defined by 3 points
|
|
# (xtri, ytri) contains the test point xy. Avoid calling with a point that
|
|
# lies on or very near to an edge of the triangle.
|
|
def tri_contains_point(xtri, ytri, xy):
|
|
tri_points = np.vstack((xtri, ytri)).T
|
|
return Path(tri_points).contains_point(xy)
|
|
|
|
# Utility function that returns how many triangles of the specified
|
|
# triangulation contain the test point xy. Avoid calling with a point that
|
|
# lies on or very near to an edge of any triangle in the triangulation.
|
|
def tris_contain_point(triang, xy):
|
|
return sum(tri_contains_point(triang.x[tri], triang.y[tri], xy)
|
|
for tri in triang.triangles)
|
|
|
|
# Using matplotlib.delaunay, an invalid triangulation is created with
|
|
# overlapping triangles; qhull is OK.
|
|
triang = mtri.Triangulation(tri_points[:, 0], tri_points[:, 1])
|
|
for test_point in test_points:
|
|
assert tris_contain_point(triang, test_point) == 1
|
|
|
|
# If ignore the first point of tri_points, matplotlib.delaunay throws a
|
|
# KeyError when calculating the convex hull; qhull is OK.
|
|
triang = mtri.Triangulation(tri_points[1:, 0], tri_points[1:, 1])
|
|
|
|
|
|
@image_comparison(['tripcolor1.png'])
|
|
def test_tripcolor():
|
|
x = np.asarray([0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1, 0.75])
|
|
y = np.asarray([0, 0, 0, 0.5, 0.5, 0.5, 1, 1, 1, 0.75])
|
|
triangles = np.asarray([
|
|
[0, 1, 3], [1, 4, 3],
|
|
[1, 2, 4], [2, 5, 4],
|
|
[3, 4, 6], [4, 7, 6],
|
|
[4, 5, 9], [7, 4, 9], [8, 7, 9], [5, 8, 9]])
|
|
|
|
# Triangulation with same number of points and triangles.
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
|
|
Cpoints = x + 0.5*y
|
|
|
|
xmid = x[triang.triangles].mean(axis=1)
|
|
ymid = y[triang.triangles].mean(axis=1)
|
|
Cfaces = 0.5*xmid + ymid
|
|
|
|
plt.subplot(121)
|
|
plt.tripcolor(triang, Cpoints, edgecolors='k')
|
|
plt.title('point colors')
|
|
|
|
plt.subplot(122)
|
|
plt.tripcolor(triang, facecolors=Cfaces, edgecolors='k')
|
|
plt.title('facecolors')
|
|
|
|
|
|
def test_tripcolor_color():
|
|
x = [-1, 0, 1, 0]
|
|
y = [0, -1, 0, 1]
|
|
fig, ax = plt.subplots()
|
|
with pytest.raises(TypeError, match=r"tripcolor\(\) missing 1 required "):
|
|
ax.tripcolor(x, y)
|
|
with pytest.raises(ValueError, match="The length of c must match either"):
|
|
ax.tripcolor(x, y, [1, 2, 3])
|
|
with pytest.raises(ValueError,
|
|
match="length of facecolors must match .* triangles"):
|
|
ax.tripcolor(x, y, facecolors=[1, 2, 3, 4])
|
|
with pytest.raises(ValueError,
|
|
match="'gouraud' .* at the points.* not at the faces"):
|
|
ax.tripcolor(x, y, facecolors=[1, 2], shading='gouraud')
|
|
with pytest.raises(ValueError,
|
|
match="'gouraud' .* at the points.* not at the faces"):
|
|
ax.tripcolor(x, y, [1, 2], shading='gouraud') # faces
|
|
with pytest.raises(TypeError,
|
|
match="positional.*'c'.*keyword-only.*'facecolors'"):
|
|
ax.tripcolor(x, y, C=[1, 2, 3, 4])
|
|
with pytest.raises(TypeError, match="Unexpected positional parameter"):
|
|
ax.tripcolor(x, y, [1, 2], 'unused_positional')
|
|
|
|
# smoke test for valid color specifications (via C or facecolors)
|
|
ax.tripcolor(x, y, [1, 2, 3, 4]) # edges
|
|
ax.tripcolor(x, y, [1, 2, 3, 4], shading='gouraud') # edges
|
|
ax.tripcolor(x, y, [1, 2]) # faces
|
|
ax.tripcolor(x, y, facecolors=[1, 2]) # faces
|
|
|
|
|
|
def test_tripcolor_clim():
|
|
np.random.seed(19680801)
|
|
a, b, c = np.random.rand(10), np.random.rand(10), np.random.rand(10)
|
|
|
|
ax = plt.figure().add_subplot()
|
|
clim = (0.25, 0.75)
|
|
norm = ax.tripcolor(a, b, c, clim=clim).norm
|
|
assert (norm.vmin, norm.vmax) == clim
|
|
|
|
|
|
def test_tripcolor_warnings():
|
|
x = [-1, 0, 1, 0]
|
|
y = [0, -1, 0, 1]
|
|
c = [0.4, 0.5]
|
|
fig, ax = plt.subplots()
|
|
# facecolors takes precedence over c
|
|
with pytest.warns(UserWarning, match="Positional parameter c .*no effect"):
|
|
ax.tripcolor(x, y, c, facecolors=c)
|
|
with pytest.warns(UserWarning, match="Positional parameter c .*no effect"):
|
|
ax.tripcolor(x, y, 'interpreted as c', facecolors=c)
|
|
|
|
|
|
def test_no_modify():
|
|
# Test that Triangulation does not modify triangles array passed to it.
|
|
triangles = np.array([[3, 2, 0], [3, 1, 0]], dtype=np.int32)
|
|
points = np.array([(0, 0), (0, 1.1), (1, 0), (1, 1)])
|
|
|
|
old_triangles = triangles.copy()
|
|
mtri.Triangulation(points[:, 0], points[:, 1], triangles).edges
|
|
assert_array_equal(old_triangles, triangles)
|
|
|
|
|
|
def test_trifinder():
|
|
# Test points within triangles of masked triangulation.
|
|
x, y = np.meshgrid(np.arange(4), np.arange(4))
|
|
x = x.ravel()
|
|
y = y.ravel()
|
|
triangles = [[0, 1, 4], [1, 5, 4], [1, 2, 5], [2, 6, 5], [2, 3, 6],
|
|
[3, 7, 6], [4, 5, 8], [5, 9, 8], [5, 6, 9], [6, 10, 9],
|
|
[6, 7, 10], [7, 11, 10], [8, 9, 12], [9, 13, 12], [9, 10, 13],
|
|
[10, 14, 13], [10, 11, 14], [11, 15, 14]]
|
|
mask = np.zeros(len(triangles))
|
|
mask[8:10] = 1
|
|
triang = mtri.Triangulation(x, y, triangles, mask)
|
|
trifinder = triang.get_trifinder()
|
|
|
|
xs = [0.25, 1.25, 2.25, 3.25]
|
|
ys = [0.25, 1.25, 2.25, 3.25]
|
|
xs, ys = np.meshgrid(xs, ys)
|
|
xs = xs.ravel()
|
|
ys = ys.ravel()
|
|
tris = trifinder(xs, ys)
|
|
assert_array_equal(tris, [0, 2, 4, -1, 6, -1, 10, -1,
|
|
12, 14, 16, -1, -1, -1, -1, -1])
|
|
tris = trifinder(xs-0.5, ys-0.5)
|
|
assert_array_equal(tris, [-1, -1, -1, -1, -1, 1, 3, 5,
|
|
-1, 7, -1, 11, -1, 13, 15, 17])
|
|
|
|
# Test points exactly on boundary edges of masked triangulation.
|
|
xs = [0.5, 1.5, 2.5, 0.5, 1.5, 2.5, 1.5, 1.5, 0.0, 1.0, 2.0, 3.0]
|
|
ys = [0.0, 0.0, 0.0, 3.0, 3.0, 3.0, 1.0, 2.0, 1.5, 1.5, 1.5, 1.5]
|
|
tris = trifinder(xs, ys)
|
|
assert_array_equal(tris, [0, 2, 4, 13, 15, 17, 3, 14, 6, 7, 10, 11])
|
|
|
|
# Test points exactly on boundary corners of masked triangulation.
|
|
xs = [0.0, 3.0]
|
|
ys = [0.0, 3.0]
|
|
tris = trifinder(xs, ys)
|
|
assert_array_equal(tris, [0, 17])
|
|
|
|
#
|
|
# Test triangles with horizontal colinear points. These are not valid
|
|
# triangulations, but we try to deal with the simplest violations.
|
|
#
|
|
|
|
# If +ve, triangulation is OK, if -ve triangulation invalid,
|
|
# if zero have colinear points but should pass tests anyway.
|
|
delta = 0.0
|
|
|
|
x = [1.5, 0, 1, 2, 3, 1.5, 1.5]
|
|
y = [-1, 0, 0, 0, 0, delta, 1]
|
|
triangles = [[0, 2, 1], [0, 3, 2], [0, 4, 3], [1, 2, 5], [2, 3, 5],
|
|
[3, 4, 5], [1, 5, 6], [4, 6, 5]]
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
trifinder = triang.get_trifinder()
|
|
|
|
xs = [-0.1, 0.4, 0.9, 1.4, 1.9, 2.4, 2.9]
|
|
ys = [-0.1, 0.1]
|
|
xs, ys = np.meshgrid(xs, ys)
|
|
tris = trifinder(xs, ys)
|
|
assert_array_equal(tris, [[-1, 0, 0, 1, 1, 2, -1],
|
|
[-1, 6, 6, 6, 7, 7, -1]])
|
|
|
|
#
|
|
# Test triangles with vertical colinear points. These are not valid
|
|
# triangulations, but we try to deal with the simplest violations.
|
|
#
|
|
|
|
# If +ve, triangulation is OK, if -ve triangulation invalid,
|
|
# if zero have colinear points but should pass tests anyway.
|
|
delta = 0.0
|
|
|
|
x = [-1, -delta, 0, 0, 0, 0, 1]
|
|
y = [1.5, 1.5, 0, 1, 2, 3, 1.5]
|
|
triangles = [[0, 1, 2], [0, 1, 5], [1, 2, 3], [1, 3, 4], [1, 4, 5],
|
|
[2, 6, 3], [3, 6, 4], [4, 6, 5]]
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
trifinder = triang.get_trifinder()
|
|
|
|
xs = [-0.1, 0.1]
|
|
ys = [-0.1, 0.4, 0.9, 1.4, 1.9, 2.4, 2.9]
|
|
xs, ys = np.meshgrid(xs, ys)
|
|
tris = trifinder(xs, ys)
|
|
assert_array_equal(tris, [[-1, -1], [0, 5], [0, 5], [0, 6], [1, 6], [1, 7],
|
|
[-1, -1]])
|
|
|
|
# Test that changing triangulation by setting a mask causes the trifinder
|
|
# to be reinitialised.
|
|
x = [0, 1, 0, 1]
|
|
y = [0, 0, 1, 1]
|
|
triangles = [[0, 1, 2], [1, 3, 2]]
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
trifinder = triang.get_trifinder()
|
|
|
|
xs = [-0.2, 0.2, 0.8, 1.2]
|
|
ys = [0.5, 0.5, 0.5, 0.5]
|
|
tris = trifinder(xs, ys)
|
|
assert_array_equal(tris, [-1, 0, 1, -1])
|
|
|
|
triang.set_mask([1, 0])
|
|
assert trifinder == triang.get_trifinder()
|
|
tris = trifinder(xs, ys)
|
|
assert_array_equal(tris, [-1, -1, 1, -1])
|
|
|
|
|
|
def test_triinterp():
|
|
# Test points within triangles of masked triangulation.
|
|
x, y = np.meshgrid(np.arange(4), np.arange(4))
|
|
x = x.ravel()
|
|
y = y.ravel()
|
|
z = 1.23*x - 4.79*y
|
|
triangles = [[0, 1, 4], [1, 5, 4], [1, 2, 5], [2, 6, 5], [2, 3, 6],
|
|
[3, 7, 6], [4, 5, 8], [5, 9, 8], [5, 6, 9], [6, 10, 9],
|
|
[6, 7, 10], [7, 11, 10], [8, 9, 12], [9, 13, 12], [9, 10, 13],
|
|
[10, 14, 13], [10, 11, 14], [11, 15, 14]]
|
|
mask = np.zeros(len(triangles))
|
|
mask[8:10] = 1
|
|
triang = mtri.Triangulation(x, y, triangles, mask)
|
|
linear_interp = mtri.LinearTriInterpolator(triang, z)
|
|
cubic_min_E = mtri.CubicTriInterpolator(triang, z)
|
|
cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
|
|
|
|
xs = np.linspace(0.25, 2.75, 6)
|
|
ys = [0.25, 0.75, 2.25, 2.75]
|
|
xs, ys = np.meshgrid(xs, ys) # Testing arrays with array.ndim = 2
|
|
for interp in (linear_interp, cubic_min_E, cubic_geom):
|
|
zs = interp(xs, ys)
|
|
assert_array_almost_equal(zs, (1.23*xs - 4.79*ys))
|
|
|
|
# Test points outside triangulation.
|
|
xs = [-0.25, 1.25, 1.75, 3.25]
|
|
ys = xs
|
|
xs, ys = np.meshgrid(xs, ys)
|
|
for interp in (linear_interp, cubic_min_E, cubic_geom):
|
|
zs = linear_interp(xs, ys)
|
|
assert_array_equal(zs.mask, [[True]*4]*4)
|
|
|
|
# Test mixed configuration (outside / inside).
|
|
xs = np.linspace(0.25, 1.75, 6)
|
|
ys = [0.25, 0.75, 1.25, 1.75]
|
|
xs, ys = np.meshgrid(xs, ys)
|
|
for interp in (linear_interp, cubic_min_E, cubic_geom):
|
|
zs = interp(xs, ys)
|
|
matest.assert_array_almost_equal(zs, (1.23*xs - 4.79*ys))
|
|
mask = (xs >= 1) * (xs <= 2) * (ys >= 1) * (ys <= 2)
|
|
assert_array_equal(zs.mask, mask)
|
|
|
|
# 2nd order patch test: on a grid with an 'arbitrary shaped' triangle,
|
|
# patch test shall be exact for quadratic functions and cubic
|
|
# interpolator if *kind* = user
|
|
(a, b, c) = (1.23, -4.79, 0.6)
|
|
|
|
def quad(x, y):
|
|
return a*(x-0.5)**2 + b*(y-0.5)**2 + c*x*y
|
|
|
|
def gradient_quad(x, y):
|
|
return (2*a*(x-0.5) + c*y, 2*b*(y-0.5) + c*x)
|
|
|
|
x = np.array([0.2, 0.33367, 0.669, 0., 1., 1., 0.])
|
|
y = np.array([0.3, 0.80755, 0.4335, 0., 0., 1., 1.])
|
|
triangles = np.array([[0, 1, 2], [3, 0, 4], [4, 0, 2], [4, 2, 5],
|
|
[1, 5, 2], [6, 5, 1], [6, 1, 0], [6, 0, 3]])
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
z = quad(x, y)
|
|
dz = gradient_quad(x, y)
|
|
# test points for 2nd order patch test
|
|
xs = np.linspace(0., 1., 5)
|
|
ys = np.linspace(0., 1., 5)
|
|
xs, ys = np.meshgrid(xs, ys)
|
|
cubic_user = mtri.CubicTriInterpolator(triang, z, kind='user', dz=dz)
|
|
interp_zs = cubic_user(xs, ys)
|
|
assert_array_almost_equal(interp_zs, quad(xs, ys))
|
|
(interp_dzsdx, interp_dzsdy) = cubic_user.gradient(x, y)
|
|
(dzsdx, dzsdy) = gradient_quad(x, y)
|
|
assert_array_almost_equal(interp_dzsdx, dzsdx)
|
|
assert_array_almost_equal(interp_dzsdy, dzsdy)
|
|
|
|
# Cubic improvement: cubic interpolation shall perform better than linear
|
|
# on a sufficiently dense mesh for a quadratic function.
|
|
n = 11
|
|
x, y = np.meshgrid(np.linspace(0., 1., n+1), np.linspace(0., 1., n+1))
|
|
x = x.ravel()
|
|
y = y.ravel()
|
|
z = quad(x, y)
|
|
triang = mtri.Triangulation(x, y, triangles=meshgrid_triangles(n+1))
|
|
xs, ys = np.meshgrid(np.linspace(0.1, 0.9, 5), np.linspace(0.1, 0.9, 5))
|
|
xs = xs.ravel()
|
|
ys = ys.ravel()
|
|
linear_interp = mtri.LinearTriInterpolator(triang, z)
|
|
cubic_min_E = mtri.CubicTriInterpolator(triang, z)
|
|
cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
|
|
zs = quad(xs, ys)
|
|
diff_lin = np.abs(linear_interp(xs, ys) - zs)
|
|
for interp in (cubic_min_E, cubic_geom):
|
|
diff_cubic = np.abs(interp(xs, ys) - zs)
|
|
assert np.max(diff_lin) >= 10 * np.max(diff_cubic)
|
|
assert (np.dot(diff_lin, diff_lin) >=
|
|
100 * np.dot(diff_cubic, diff_cubic))
|
|
|
|
|
|
def test_triinterpcubic_C1_continuity():
|
|
# Below the 4 tests which demonstrate C1 continuity of the
|
|
# TriCubicInterpolator (testing the cubic shape functions on arbitrary
|
|
# triangle):
|
|
#
|
|
# 1) Testing continuity of function & derivatives at corner for all 9
|
|
# shape functions. Testing also function values at same location.
|
|
# 2) Testing C1 continuity along each edge (as gradient is polynomial of
|
|
# 2nd order, it is sufficient to test at the middle).
|
|
# 3) Testing C1 continuity at triangle barycenter (where the 3 subtriangles
|
|
# meet)
|
|
# 4) Testing C1 continuity at median 1/3 points (midside between 2
|
|
# subtriangles)
|
|
|
|
# Utility test function check_continuity
|
|
def check_continuity(interpolator, loc, values=None):
|
|
"""
|
|
Checks the continuity of interpolator (and its derivatives) near
|
|
location loc. Can check the value at loc itself if *values* is
|
|
provided.
|
|
|
|
*interpolator* TriInterpolator
|
|
*loc* location to test (x0, y0)
|
|
*values* (optional) array [z0, dzx0, dzy0] to check the value at *loc*
|
|
"""
|
|
n_star = 24 # Number of continuity points in a boundary of loc
|
|
epsilon = 1.e-10 # Distance for loc boundary
|
|
k = 100. # Continuity coefficient
|
|
(loc_x, loc_y) = loc
|
|
star_x = loc_x + epsilon*np.cos(np.linspace(0., 2*np.pi, n_star))
|
|
star_y = loc_y + epsilon*np.sin(np.linspace(0., 2*np.pi, n_star))
|
|
z = interpolator([loc_x], [loc_y])[0]
|
|
(dzx, dzy) = interpolator.gradient([loc_x], [loc_y])
|
|
if values is not None:
|
|
assert_array_almost_equal(z, values[0])
|
|
assert_array_almost_equal(dzx[0], values[1])
|
|
assert_array_almost_equal(dzy[0], values[2])
|
|
diff_z = interpolator(star_x, star_y) - z
|
|
(tab_dzx, tab_dzy) = interpolator.gradient(star_x, star_y)
|
|
diff_dzx = tab_dzx - dzx
|
|
diff_dzy = tab_dzy - dzy
|
|
assert_array_less(diff_z, epsilon*k)
|
|
assert_array_less(diff_dzx, epsilon*k)
|
|
assert_array_less(diff_dzy, epsilon*k)
|
|
|
|
# Drawing arbitrary triangle (a, b, c) inside a unit square.
|
|
(ax, ay) = (0.2, 0.3)
|
|
(bx, by) = (0.33367, 0.80755)
|
|
(cx, cy) = (0.669, 0.4335)
|
|
x = np.array([ax, bx, cx, 0., 1., 1., 0.])
|
|
y = np.array([ay, by, cy, 0., 0., 1., 1.])
|
|
triangles = np.array([[0, 1, 2], [3, 0, 4], [4, 0, 2], [4, 2, 5],
|
|
[1, 5, 2], [6, 5, 1], [6, 1, 0], [6, 0, 3]])
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
|
|
for idof in range(9):
|
|
z = np.zeros(7, dtype=np.float64)
|
|
dzx = np.zeros(7, dtype=np.float64)
|
|
dzy = np.zeros(7, dtype=np.float64)
|
|
values = np.zeros([3, 3], dtype=np.float64)
|
|
case = idof//3
|
|
values[case, idof % 3] = 1.0
|
|
if case == 0:
|
|
z[idof] = 1.0
|
|
elif case == 1:
|
|
dzx[idof % 3] = 1.0
|
|
elif case == 2:
|
|
dzy[idof % 3] = 1.0
|
|
interp = mtri.CubicTriInterpolator(triang, z, kind='user',
|
|
dz=(dzx, dzy))
|
|
# Test 1) Checking values and continuity at nodes
|
|
check_continuity(interp, (ax, ay), values[:, 0])
|
|
check_continuity(interp, (bx, by), values[:, 1])
|
|
check_continuity(interp, (cx, cy), values[:, 2])
|
|
# Test 2) Checking continuity at midside nodes
|
|
check_continuity(interp, ((ax+bx)*0.5, (ay+by)*0.5))
|
|
check_continuity(interp, ((ax+cx)*0.5, (ay+cy)*0.5))
|
|
check_continuity(interp, ((cx+bx)*0.5, (cy+by)*0.5))
|
|
# Test 3) Checking continuity at barycenter
|
|
check_continuity(interp, ((ax+bx+cx)/3., (ay+by+cy)/3.))
|
|
# Test 4) Checking continuity at median 1/3-point
|
|
check_continuity(interp, ((4.*ax+bx+cx)/6., (4.*ay+by+cy)/6.))
|
|
check_continuity(interp, ((ax+4.*bx+cx)/6., (ay+4.*by+cy)/6.))
|
|
check_continuity(interp, ((ax+bx+4.*cx)/6., (ay+by+4.*cy)/6.))
|
|
|
|
|
|
def test_triinterpcubic_cg_solver():
|
|
# Now 3 basic tests of the Sparse CG solver, used for
|
|
# TriCubicInterpolator with *kind* = 'min_E'
|
|
# 1) A commonly used test involves a 2d Poisson matrix.
|
|
def poisson_sparse_matrix(n, m):
|
|
"""
|
|
Return the sparse, (n*m, n*m) matrix in coo format resulting from the
|
|
discretisation of the 2-dimensional Poisson equation according to a
|
|
finite difference numerical scheme on a uniform (n, m) grid.
|
|
"""
|
|
l = m*n
|
|
rows = np.concatenate([
|
|
np.arange(l, dtype=np.int32),
|
|
np.arange(l-1, dtype=np.int32), np.arange(1, l, dtype=np.int32),
|
|
np.arange(l-n, dtype=np.int32), np.arange(n, l, dtype=np.int32)])
|
|
cols = np.concatenate([
|
|
np.arange(l, dtype=np.int32),
|
|
np.arange(1, l, dtype=np.int32), np.arange(l-1, dtype=np.int32),
|
|
np.arange(n, l, dtype=np.int32), np.arange(l-n, dtype=np.int32)])
|
|
vals = np.concatenate([
|
|
4*np.ones(l, dtype=np.float64),
|
|
-np.ones(l-1, dtype=np.float64), -np.ones(l-1, dtype=np.float64),
|
|
-np.ones(l-n, dtype=np.float64), -np.ones(l-n, dtype=np.float64)])
|
|
# In fact +1 and -1 diags have some zeros
|
|
vals[l:2*l-1][m-1::m] = 0.
|
|
vals[2*l-1:3*l-2][m-1::m] = 0.
|
|
return vals, rows, cols, (n*m, n*m)
|
|
|
|
# Instantiating a sparse Poisson matrix of size 48 x 48:
|
|
(n, m) = (12, 4)
|
|
mat = mtri._triinterpolate._Sparse_Matrix_coo(*poisson_sparse_matrix(n, m))
|
|
mat.compress_csc()
|
|
mat_dense = mat.to_dense()
|
|
# Testing a sparse solve for all 48 basis vector
|
|
for itest in range(n*m):
|
|
b = np.zeros(n*m, dtype=np.float64)
|
|
b[itest] = 1.
|
|
x, _ = mtri._triinterpolate._cg(A=mat, b=b, x0=np.zeros(n*m),
|
|
tol=1.e-10)
|
|
assert_array_almost_equal(np.dot(mat_dense, x), b)
|
|
|
|
# 2) Same matrix with inserting 2 rows - cols with null diag terms
|
|
# (but still linked with the rest of the matrix by extra-diag terms)
|
|
(i_zero, j_zero) = (12, 49)
|
|
vals, rows, cols, _ = poisson_sparse_matrix(n, m)
|
|
rows = rows + 1*(rows >= i_zero) + 1*(rows >= j_zero)
|
|
cols = cols + 1*(cols >= i_zero) + 1*(cols >= j_zero)
|
|
# adding extra-diag terms
|
|
rows = np.concatenate([rows, [i_zero, i_zero-1, j_zero, j_zero-1]])
|
|
cols = np.concatenate([cols, [i_zero-1, i_zero, j_zero-1, j_zero]])
|
|
vals = np.concatenate([vals, [1., 1., 1., 1.]])
|
|
mat = mtri._triinterpolate._Sparse_Matrix_coo(vals, rows, cols,
|
|
(n*m + 2, n*m + 2))
|
|
mat.compress_csc()
|
|
mat_dense = mat.to_dense()
|
|
# Testing a sparse solve for all 50 basis vec
|
|
for itest in range(n*m + 2):
|
|
b = np.zeros(n*m + 2, dtype=np.float64)
|
|
b[itest] = 1.
|
|
x, _ = mtri._triinterpolate._cg(A=mat, b=b, x0=np.ones(n * m + 2),
|
|
tol=1.e-10)
|
|
assert_array_almost_equal(np.dot(mat_dense, x), b)
|
|
|
|
# 3) Now a simple test that summation of duplicate (i.e. with same rows,
|
|
# same cols) entries occurs when compressed.
|
|
vals = np.ones(17, dtype=np.float64)
|
|
rows = np.array([0, 1, 2, 0, 0, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1],
|
|
dtype=np.int32)
|
|
cols = np.array([0, 1, 2, 1, 1, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
|
|
dtype=np.int32)
|
|
dim = (3, 3)
|
|
mat = mtri._triinterpolate._Sparse_Matrix_coo(vals, rows, cols, dim)
|
|
mat.compress_csc()
|
|
mat_dense = mat.to_dense()
|
|
assert_array_almost_equal(mat_dense, np.array([
|
|
[1., 2., 0.], [2., 1., 5.], [0., 5., 1.]], dtype=np.float64))
|
|
|
|
|
|
def test_triinterpcubic_geom_weights():
|
|
# Tests to check computation of weights for _DOF_estimator_geom:
|
|
# The weight sum per triangle can be 1. (in case all angles < 90 degrees)
|
|
# or (2*w_i) where w_i = 1-alpha_i/np.pi is the weight of apex i; alpha_i
|
|
# is the apex angle > 90 degrees.
|
|
(ax, ay) = (0., 1.687)
|
|
x = np.array([ax, 0.5*ax, 0., 1.])
|
|
y = np.array([ay, -ay, 0., 0.])
|
|
z = np.zeros(4, dtype=np.float64)
|
|
triangles = [[0, 2, 3], [1, 3, 2]]
|
|
sum_w = np.zeros([4, 2]) # 4 possibilities; 2 triangles
|
|
for theta in np.linspace(0., 2*np.pi, 14): # rotating the figure...
|
|
x_rot = np.cos(theta)*x + np.sin(theta)*y
|
|
y_rot = -np.sin(theta)*x + np.cos(theta)*y
|
|
triang = mtri.Triangulation(x_rot, y_rot, triangles)
|
|
cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
|
|
dof_estimator = mtri._triinterpolate._DOF_estimator_geom(cubic_geom)
|
|
weights = dof_estimator.compute_geom_weights()
|
|
# Testing for the 4 possibilities...
|
|
sum_w[0, :] = np.sum(weights, 1) - 1
|
|
for itri in range(3):
|
|
sum_w[itri+1, :] = np.sum(weights, 1) - 2*weights[:, itri]
|
|
assert_array_almost_equal(np.min(np.abs(sum_w), axis=0),
|
|
np.array([0., 0.], dtype=np.float64))
|
|
|
|
|
|
def test_triinterp_colinear():
|
|
# Tests interpolating inside a triangulation with horizontal colinear
|
|
# points (refer also to the tests :func:`test_trifinder` ).
|
|
#
|
|
# These are not valid triangulations, but we try to deal with the
|
|
# simplest violations (i. e. those handled by default TriFinder).
|
|
#
|
|
# Note that the LinearTriInterpolator and the CubicTriInterpolator with
|
|
# kind='min_E' or 'geom' still pass a linear patch test.
|
|
# We also test interpolation inside a flat triangle, by forcing
|
|
# *tri_index* in a call to :meth:`_interpolate_multikeys`.
|
|
|
|
# If +ve, triangulation is OK, if -ve triangulation invalid,
|
|
# if zero have colinear points but should pass tests anyway.
|
|
delta = 0.
|
|
|
|
x0 = np.array([1.5, 0, 1, 2, 3, 1.5, 1.5])
|
|
y0 = np.array([-1, 0, 0, 0, 0, delta, 1])
|
|
|
|
# We test different affine transformations of the initial figure; to
|
|
# avoid issues related to round-off errors we only use integer
|
|
# coefficients (otherwise the Triangulation might become invalid even with
|
|
# delta == 0).
|
|
transformations = [[1, 0], [0, 1], [1, 1], [1, 2], [-2, -1], [-2, 1]]
|
|
for transformation in transformations:
|
|
x_rot = transformation[0]*x0 + transformation[1]*y0
|
|
y_rot = -transformation[1]*x0 + transformation[0]*y0
|
|
(x, y) = (x_rot, y_rot)
|
|
z = 1.23*x - 4.79*y
|
|
triangles = [[0, 2, 1], [0, 3, 2], [0, 4, 3], [1, 2, 5], [2, 3, 5],
|
|
[3, 4, 5], [1, 5, 6], [4, 6, 5]]
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
xs = np.linspace(np.min(triang.x), np.max(triang.x), 20)
|
|
ys = np.linspace(np.min(triang.y), np.max(triang.y), 20)
|
|
xs, ys = np.meshgrid(xs, ys)
|
|
xs = xs.ravel()
|
|
ys = ys.ravel()
|
|
mask_out = (triang.get_trifinder()(xs, ys) == -1)
|
|
zs_target = np.ma.array(1.23*xs - 4.79*ys, mask=mask_out)
|
|
|
|
linear_interp = mtri.LinearTriInterpolator(triang, z)
|
|
cubic_min_E = mtri.CubicTriInterpolator(triang, z)
|
|
cubic_geom = mtri.CubicTriInterpolator(triang, z, kind='geom')
|
|
|
|
for interp in (linear_interp, cubic_min_E, cubic_geom):
|
|
zs = interp(xs, ys)
|
|
assert_array_almost_equal(zs_target, zs)
|
|
|
|
# Testing interpolation inside the flat triangle number 4: [2, 3, 5]
|
|
# by imposing *tri_index* in a call to :meth:`_interpolate_multikeys`
|
|
itri = 4
|
|
pt1 = triang.triangles[itri, 0]
|
|
pt2 = triang.triangles[itri, 1]
|
|
xs = np.linspace(triang.x[pt1], triang.x[pt2], 10)
|
|
ys = np.linspace(triang.y[pt1], triang.y[pt2], 10)
|
|
zs_target = 1.23*xs - 4.79*ys
|
|
for interp in (linear_interp, cubic_min_E, cubic_geom):
|
|
zs, = interp._interpolate_multikeys(
|
|
xs, ys, tri_index=itri*np.ones(10, dtype=np.int32))
|
|
assert_array_almost_equal(zs_target, zs)
|
|
|
|
|
|
def test_triinterp_transformations():
|
|
# 1) Testing that the interpolation scheme is invariant by rotation of the
|
|
# whole figure.
|
|
# Note: This test is non-trivial for a CubicTriInterpolator with
|
|
# kind='min_E'. It does fail for a non-isotropic stiffness matrix E of
|
|
# :class:`_ReducedHCT_Element` (tested with E=np.diag([1., 1., 1.])), and
|
|
# provides a good test for :meth:`get_Kff_and_Ff`of the same class.
|
|
#
|
|
# 2) Also testing that the interpolation scheme is invariant by expansion
|
|
# of the whole figure along one axis.
|
|
n_angles = 20
|
|
n_radii = 10
|
|
min_radius = 0.15
|
|
|
|
def z(x, y):
|
|
r1 = np.hypot(0.5 - x, 0.5 - y)
|
|
theta1 = np.arctan2(0.5 - x, 0.5 - y)
|
|
r2 = np.hypot(-x - 0.2, -y - 0.2)
|
|
theta2 = np.arctan2(-x - 0.2, -y - 0.2)
|
|
z = -(2*(np.exp((r1/10)**2)-1)*30. * np.cos(7.*theta1) +
|
|
(np.exp((r2/10)**2)-1)*30. * np.cos(11.*theta2) +
|
|
0.7*(x**2 + y**2))
|
|
return (np.max(z)-z)/(np.max(z)-np.min(z))
|
|
|
|
# First create the x and y coordinates of the points.
|
|
radii = np.linspace(min_radius, 0.95, n_radii)
|
|
angles = np.linspace(0 + n_angles, 2*np.pi + n_angles,
|
|
n_angles, endpoint=False)
|
|
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
|
|
angles[:, 1::2] += np.pi/n_angles
|
|
x0 = (radii*np.cos(angles)).flatten()
|
|
y0 = (radii*np.sin(angles)).flatten()
|
|
triang0 = mtri.Triangulation(x0, y0) # Delaunay triangulation
|
|
z0 = z(x0, y0)
|
|
|
|
# Then create the test points
|
|
xs0 = np.linspace(-1., 1., 23)
|
|
ys0 = np.linspace(-1., 1., 23)
|
|
xs0, ys0 = np.meshgrid(xs0, ys0)
|
|
xs0 = xs0.ravel()
|
|
ys0 = ys0.ravel()
|
|
|
|
interp_z0 = {}
|
|
for i_angle in range(2):
|
|
# Rotating everything
|
|
theta = 2*np.pi / n_angles * i_angle
|
|
x = np.cos(theta)*x0 + np.sin(theta)*y0
|
|
y = -np.sin(theta)*x0 + np.cos(theta)*y0
|
|
xs = np.cos(theta)*xs0 + np.sin(theta)*ys0
|
|
ys = -np.sin(theta)*xs0 + np.cos(theta)*ys0
|
|
triang = mtri.Triangulation(x, y, triang0.triangles)
|
|
linear_interp = mtri.LinearTriInterpolator(triang, z0)
|
|
cubic_min_E = mtri.CubicTriInterpolator(triang, z0)
|
|
cubic_geom = mtri.CubicTriInterpolator(triang, z0, kind='geom')
|
|
dic_interp = {'lin': linear_interp,
|
|
'min_E': cubic_min_E,
|
|
'geom': cubic_geom}
|
|
# Testing that the interpolation is invariant by rotation...
|
|
for interp_key in ['lin', 'min_E', 'geom']:
|
|
interp = dic_interp[interp_key]
|
|
if i_angle == 0:
|
|
interp_z0[interp_key] = interp(xs0, ys0) # storage
|
|
else:
|
|
interpz = interp(xs, ys)
|
|
matest.assert_array_almost_equal(interpz,
|
|
interp_z0[interp_key])
|
|
|
|
scale_factor = 987654.3210
|
|
for scaled_axis in ('x', 'y'):
|
|
# Scaling everything (expansion along scaled_axis)
|
|
if scaled_axis == 'x':
|
|
x = scale_factor * x0
|
|
y = y0
|
|
xs = scale_factor * xs0
|
|
ys = ys0
|
|
else:
|
|
x = x0
|
|
y = scale_factor * y0
|
|
xs = xs0
|
|
ys = scale_factor * ys0
|
|
triang = mtri.Triangulation(x, y, triang0.triangles)
|
|
linear_interp = mtri.LinearTriInterpolator(triang, z0)
|
|
cubic_min_E = mtri.CubicTriInterpolator(triang, z0)
|
|
cubic_geom = mtri.CubicTriInterpolator(triang, z0, kind='geom')
|
|
dic_interp = {'lin': linear_interp,
|
|
'min_E': cubic_min_E,
|
|
'geom': cubic_geom}
|
|
# Test that the interpolation is invariant by expansion along 1 axis...
|
|
for interp_key in ['lin', 'min_E', 'geom']:
|
|
interpz = dic_interp[interp_key](xs, ys)
|
|
matest.assert_array_almost_equal(interpz, interp_z0[interp_key])
|
|
|
|
|
|
@image_comparison(['tri_smooth_contouring.png'], remove_text=True, tol=0.072)
|
|
def test_tri_smooth_contouring():
|
|
# Image comparison based on example tricontour_smooth_user.
|
|
n_angles = 20
|
|
n_radii = 10
|
|
min_radius = 0.15
|
|
|
|
def z(x, y):
|
|
r1 = np.hypot(0.5 - x, 0.5 - y)
|
|
theta1 = np.arctan2(0.5 - x, 0.5 - y)
|
|
r2 = np.hypot(-x - 0.2, -y - 0.2)
|
|
theta2 = np.arctan2(-x - 0.2, -y - 0.2)
|
|
z = -(2*(np.exp((r1/10)**2)-1)*30. * np.cos(7.*theta1) +
|
|
(np.exp((r2/10)**2)-1)*30. * np.cos(11.*theta2) +
|
|
0.7*(x**2 + y**2))
|
|
return (np.max(z)-z)/(np.max(z)-np.min(z))
|
|
|
|
# First create the x and y coordinates of the points.
|
|
radii = np.linspace(min_radius, 0.95, n_radii)
|
|
angles = np.linspace(0 + n_angles, 2*np.pi + n_angles,
|
|
n_angles, endpoint=False)
|
|
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
|
|
angles[:, 1::2] += np.pi/n_angles
|
|
x0 = (radii*np.cos(angles)).flatten()
|
|
y0 = (radii*np.sin(angles)).flatten()
|
|
triang0 = mtri.Triangulation(x0, y0) # Delaunay triangulation
|
|
z0 = z(x0, y0)
|
|
triang0.set_mask(np.hypot(x0[triang0.triangles].mean(axis=1),
|
|
y0[triang0.triangles].mean(axis=1))
|
|
< min_radius)
|
|
|
|
# Then the plot
|
|
refiner = mtri.UniformTriRefiner(triang0)
|
|
tri_refi, z_test_refi = refiner.refine_field(z0, subdiv=4)
|
|
levels = np.arange(0., 1., 0.025)
|
|
plt.triplot(triang0, lw=0.5, color='0.5')
|
|
plt.tricontour(tri_refi, z_test_refi, levels=levels, colors="black")
|
|
|
|
|
|
@image_comparison(['tri_smooth_gradient.png'], remove_text=True, tol=0.092)
|
|
def test_tri_smooth_gradient():
|
|
# Image comparison based on example trigradient_demo.
|
|
|
|
def dipole_potential(x, y):
|
|
"""An electric dipole potential V."""
|
|
r_sq = x**2 + y**2
|
|
theta = np.arctan2(y, x)
|
|
z = np.cos(theta)/r_sq
|
|
return (np.max(z)-z) / (np.max(z)-np.min(z))
|
|
|
|
# Creating a Triangulation
|
|
n_angles = 30
|
|
n_radii = 10
|
|
min_radius = 0.2
|
|
radii = np.linspace(min_radius, 0.95, n_radii)
|
|
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
|
|
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
|
|
angles[:, 1::2] += np.pi/n_angles
|
|
x = (radii*np.cos(angles)).flatten()
|
|
y = (radii*np.sin(angles)).flatten()
|
|
V = dipole_potential(x, y)
|
|
triang = mtri.Triangulation(x, y)
|
|
triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
|
|
y[triang.triangles].mean(axis=1))
|
|
< min_radius)
|
|
|
|
# Refine data - interpolates the electrical potential V
|
|
refiner = mtri.UniformTriRefiner(triang)
|
|
tri_refi, z_test_refi = refiner.refine_field(V, subdiv=3)
|
|
|
|
# Computes the electrical field (Ex, Ey) as gradient of -V
|
|
tci = mtri.CubicTriInterpolator(triang, -V)
|
|
Ex, Ey = tci.gradient(triang.x, triang.y)
|
|
E_norm = np.hypot(Ex, Ey)
|
|
|
|
# Plot the triangulation, the potential iso-contours and the vector field
|
|
plt.figure()
|
|
plt.gca().set_aspect('equal')
|
|
plt.triplot(triang, color='0.8')
|
|
|
|
levels = np.arange(0., 1., 0.01)
|
|
cmap = mpl.colormaps['hot']
|
|
plt.tricontour(tri_refi, z_test_refi, levels=levels, cmap=cmap,
|
|
linewidths=[2.0, 1.0, 1.0, 1.0])
|
|
# Plots direction of the electrical vector field
|
|
plt.quiver(triang.x, triang.y, Ex/E_norm, Ey/E_norm,
|
|
units='xy', scale=10., zorder=3, color='blue',
|
|
width=0.007, headwidth=3., headlength=4.)
|
|
# We are leaving ax.use_sticky_margins as True, so the
|
|
# view limits are the contour data limits.
|
|
|
|
|
|
def test_tritools():
|
|
# Tests TriAnalyzer.scale_factors on masked triangulation
|
|
# Tests circle_ratios on equilateral and right-angled triangle.
|
|
x = np.array([0., 1., 0.5, 0., 2.])
|
|
y = np.array([0., 0., 0.5*np.sqrt(3.), -1., 1.])
|
|
triangles = np.array([[0, 1, 2], [0, 1, 3], [1, 2, 4]], dtype=np.int32)
|
|
mask = np.array([False, False, True], dtype=bool)
|
|
triang = mtri.Triangulation(x, y, triangles, mask=mask)
|
|
analyser = mtri.TriAnalyzer(triang)
|
|
assert_array_almost_equal(analyser.scale_factors, [1, 1/(1+3**.5/2)])
|
|
assert_array_almost_equal(
|
|
analyser.circle_ratios(rescale=False),
|
|
np.ma.masked_array([0.5, 1./(1.+np.sqrt(2.)), np.nan], mask))
|
|
|
|
# Tests circle ratio of a flat triangle
|
|
x = np.array([0., 1., 2.])
|
|
y = np.array([1., 1.+3., 1.+6.])
|
|
triangles = np.array([[0, 1, 2]], dtype=np.int32)
|
|
triang = mtri.Triangulation(x, y, triangles)
|
|
analyser = mtri.TriAnalyzer(triang)
|
|
assert_array_almost_equal(analyser.circle_ratios(), np.array([0.]))
|
|
|
|
# Tests TriAnalyzer.get_flat_tri_mask
|
|
# Creates a triangulation of [-1, 1] x [-1, 1] with contiguous groups of
|
|
# 'flat' triangles at the 4 corners and at the center. Checks that only
|
|
# those at the borders are eliminated by TriAnalyzer.get_flat_tri_mask
|
|
n = 9
|
|
|
|
def power(x, a):
|
|
return np.abs(x)**a*np.sign(x)
|
|
|
|
x = np.linspace(-1., 1., n+1)
|
|
x, y = np.meshgrid(power(x, 2.), power(x, 0.25))
|
|
x = x.ravel()
|
|
y = y.ravel()
|
|
|
|
triang = mtri.Triangulation(x, y, triangles=meshgrid_triangles(n+1))
|
|
analyser = mtri.TriAnalyzer(triang)
|
|
mask_flat = analyser.get_flat_tri_mask(0.2)
|
|
verif_mask = np.zeros(162, dtype=bool)
|
|
corners_index = [0, 1, 2, 3, 14, 15, 16, 17, 18, 19, 34, 35, 126, 127,
|
|
142, 143, 144, 145, 146, 147, 158, 159, 160, 161]
|
|
verif_mask[corners_index] = True
|
|
assert_array_equal(mask_flat, verif_mask)
|
|
|
|
# Now including a hole (masked triangle) at the center. The center also
|
|
# shall be eliminated by get_flat_tri_mask.
|
|
mask = np.zeros(162, dtype=bool)
|
|
mask[80] = True
|
|
triang.set_mask(mask)
|
|
mask_flat = analyser.get_flat_tri_mask(0.2)
|
|
center_index = [44, 45, 62, 63, 78, 79, 80, 81, 82, 83, 98, 99, 116, 117]
|
|
verif_mask[center_index] = True
|
|
assert_array_equal(mask_flat, verif_mask)
|
|
|
|
|
|
def test_trirefine():
|
|
# Testing subdiv=2 refinement
|
|
n = 3
|
|
subdiv = 2
|
|
x = np.linspace(-1., 1., n+1)
|
|
x, y = np.meshgrid(x, x)
|
|
x = x.ravel()
|
|
y = y.ravel()
|
|
mask = np.zeros(2*n**2, dtype=bool)
|
|
mask[n**2:] = True
|
|
triang = mtri.Triangulation(x, y, triangles=meshgrid_triangles(n+1),
|
|
mask=mask)
|
|
refiner = mtri.UniformTriRefiner(triang)
|
|
refi_triang = refiner.refine_triangulation(subdiv=subdiv)
|
|
x_refi = refi_triang.x
|
|
y_refi = refi_triang.y
|
|
|
|
n_refi = n * subdiv**2
|
|
x_verif = np.linspace(-1., 1., n_refi+1)
|
|
x_verif, y_verif = np.meshgrid(x_verif, x_verif)
|
|
x_verif = x_verif.ravel()
|
|
y_verif = y_verif.ravel()
|
|
ind1d = np.isin(np.around(x_verif*(2.5+y_verif), 8),
|
|
np.around(x_refi*(2.5+y_refi), 8))
|
|
assert_array_equal(ind1d, True)
|
|
|
|
# Testing the mask of the refined triangulation
|
|
refi_mask = refi_triang.mask
|
|
refi_tri_barycenter_x = np.sum(refi_triang.x[refi_triang.triangles],
|
|
axis=1) / 3.
|
|
refi_tri_barycenter_y = np.sum(refi_triang.y[refi_triang.triangles],
|
|
axis=1) / 3.
|
|
tri_finder = triang.get_trifinder()
|
|
refi_tri_indices = tri_finder(refi_tri_barycenter_x,
|
|
refi_tri_barycenter_y)
|
|
refi_tri_mask = triang.mask[refi_tri_indices]
|
|
assert_array_equal(refi_mask, refi_tri_mask)
|
|
|
|
# Testing that the numbering of triangles does not change the
|
|
# interpolation result.
|
|
x = np.asarray([0.0, 1.0, 0.0, 1.0])
|
|
y = np.asarray([0.0, 0.0, 1.0, 1.0])
|
|
triang = [mtri.Triangulation(x, y, [[0, 1, 3], [3, 2, 0]]),
|
|
mtri.Triangulation(x, y, [[0, 1, 3], [2, 0, 3]])]
|
|
z = np.hypot(x - 0.3, y - 0.4)
|
|
# Refining the 2 triangulations and reordering the points
|
|
xyz_data = []
|
|
for i in range(2):
|
|
refiner = mtri.UniformTriRefiner(triang[i])
|
|
refined_triang, refined_z = refiner.refine_field(z, subdiv=1)
|
|
xyz = np.dstack((refined_triang.x, refined_triang.y, refined_z))[0]
|
|
xyz = xyz[np.lexsort((xyz[:, 1], xyz[:, 0]))]
|
|
xyz_data += [xyz]
|
|
assert_array_almost_equal(xyz_data[0], xyz_data[1])
|
|
|
|
|
|
@pytest.mark.parametrize('interpolator',
|
|
[mtri.LinearTriInterpolator,
|
|
mtri.CubicTriInterpolator],
|
|
ids=['linear', 'cubic'])
|
|
def test_trirefine_masked(interpolator):
|
|
# Repeated points means we will have fewer triangles than points, and thus
|
|
# get masking.
|
|
x, y = np.mgrid[:2, :2]
|
|
x = np.repeat(x.flatten(), 2)
|
|
y = np.repeat(y.flatten(), 2)
|
|
|
|
z = np.zeros_like(x)
|
|
tri = mtri.Triangulation(x, y)
|
|
refiner = mtri.UniformTriRefiner(tri)
|
|
interp = interpolator(tri, z)
|
|
refiner.refine_field(z, triinterpolator=interp, subdiv=2)
|
|
|
|
|
|
def meshgrid_triangles(n):
|
|
"""
|
|
Return (2*(N-1)**2, 3) array of triangles to mesh (N, N)-point np.meshgrid.
|
|
"""
|
|
tri = []
|
|
for i in range(n-1):
|
|
for j in range(n-1):
|
|
a = i + j*n
|
|
b = (i+1) + j*n
|
|
c = i + (j+1)*n
|
|
d = (i+1) + (j+1)*n
|
|
tri += [[a, b, d], [a, d, c]]
|
|
return np.array(tri, dtype=np.int32)
|
|
|
|
|
|
def test_triplot_return():
|
|
# Check that triplot returns the artists it adds
|
|
ax = plt.figure().add_subplot()
|
|
triang = mtri.Triangulation(
|
|
[0.0, 1.0, 0.0, 1.0], [0.0, 0.0, 1.0, 1.0],
|
|
triangles=[[0, 1, 3], [3, 2, 0]])
|
|
assert ax.triplot(triang, "b-") is not None, \
|
|
'triplot should return the artist it adds'
|
|
|
|
|
|
def test_trirefiner_fortran_contiguous_triangles():
|
|
# github issue 4180. Test requires two arrays of triangles that are
|
|
# identical except that one is C-contiguous and one is fortran-contiguous.
|
|
triangles1 = np.array([[2, 0, 3], [2, 1, 0]])
|
|
assert not np.isfortran(triangles1)
|
|
|
|
triangles2 = np.array(triangles1, copy=True, order='F')
|
|
assert np.isfortran(triangles2)
|
|
|
|
x = np.array([0.39, 0.59, 0.43, 0.32])
|
|
y = np.array([33.99, 34.01, 34.19, 34.18])
|
|
triang1 = mtri.Triangulation(x, y, triangles1)
|
|
triang2 = mtri.Triangulation(x, y, triangles2)
|
|
|
|
refiner1 = mtri.UniformTriRefiner(triang1)
|
|
refiner2 = mtri.UniformTriRefiner(triang2)
|
|
|
|
fine_triang1 = refiner1.refine_triangulation(subdiv=1)
|
|
fine_triang2 = refiner2.refine_triangulation(subdiv=1)
|
|
|
|
assert_array_equal(fine_triang1.triangles, fine_triang2.triangles)
|
|
|
|
|
|
def test_qhull_triangle_orientation():
|
|
# github issue 4437.
|
|
xi = np.linspace(-2, 2, 100)
|
|
x, y = map(np.ravel, np.meshgrid(xi, xi))
|
|
w = (x > y - 1) & (x < -1.95) & (y > -1.2)
|
|
x, y = x[w], y[w]
|
|
theta = np.radians(25)
|
|
x1 = x*np.cos(theta) - y*np.sin(theta)
|
|
y1 = x*np.sin(theta) + y*np.cos(theta)
|
|
|
|
# Calculate Delaunay triangulation using Qhull.
|
|
triang = mtri.Triangulation(x1, y1)
|
|
|
|
# Neighbors returned by Qhull.
|
|
qhull_neighbors = triang.neighbors
|
|
|
|
# Obtain neighbors using own C++ calculation.
|
|
triang._neighbors = None
|
|
own_neighbors = triang.neighbors
|
|
|
|
assert_array_equal(qhull_neighbors, own_neighbors)
|
|
|
|
|
|
def test_trianalyzer_mismatched_indices():
|
|
# github issue 4999.
|
|
x = np.array([0., 1., 0.5, 0., 2.])
|
|
y = np.array([0., 0., 0.5*np.sqrt(3.), -1., 1.])
|
|
triangles = np.array([[0, 1, 2], [0, 1, 3], [1, 2, 4]], dtype=np.int32)
|
|
mask = np.array([False, False, True], dtype=bool)
|
|
triang = mtri.Triangulation(x, y, triangles, mask=mask)
|
|
analyser = mtri.TriAnalyzer(triang)
|
|
# numpy >= 1.10 raises a VisibleDeprecationWarning in the following line
|
|
# prior to the fix.
|
|
analyser._get_compressed_triangulation()
|
|
|
|
|
|
def test_tricontourf_decreasing_levels():
|
|
# github issue 5477.
|
|
x = [0.0, 1.0, 1.0]
|
|
y = [0.0, 0.0, 1.0]
|
|
z = [0.2, 0.4, 0.6]
|
|
plt.figure()
|
|
with pytest.raises(ValueError):
|
|
plt.tricontourf(x, y, z, [1.0, 0.0])
|
|
|
|
|
|
def test_internal_cpp_api():
|
|
# Following github issue 8197.
|
|
from matplotlib import _tri # noqa: ensure lazy-loaded module *is* loaded.
|
|
|
|
# C++ Triangulation.
|
|
with pytest.raises(
|
|
TypeError,
|
|
match=r'__init__\(\): incompatible constructor arguments.'):
|
|
mpl._tri.Triangulation()
|
|
|
|
with pytest.raises(
|
|
ValueError, match=r'x and y must be 1D arrays of the same length'):
|
|
mpl._tri.Triangulation([], [1], [[]], (), (), (), False)
|
|
|
|
x = [0, 1, 1]
|
|
y = [0, 0, 1]
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r'triangles must be a 2D array of shape \(\?,3\)'):
|
|
mpl._tri.Triangulation(x, y, [[0, 1]], (), (), (), False)
|
|
|
|
tris = [[0, 1, 2]]
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r'mask must be a 1D array with the same length as the '
|
|
r'triangles array'):
|
|
mpl._tri.Triangulation(x, y, tris, [0, 1], (), (), False)
|
|
|
|
with pytest.raises(
|
|
ValueError, match=r'edges must be a 2D array with shape \(\?,2\)'):
|
|
mpl._tri.Triangulation(x, y, tris, (), [[1]], (), False)
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r'neighbors must be a 2D array with the same shape as the '
|
|
r'triangles array'):
|
|
mpl._tri.Triangulation(x, y, tris, (), (), [[-1]], False)
|
|
|
|
triang = mpl._tri.Triangulation(x, y, tris, (), (), (), False)
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r'z must be a 1D array with the same length as the '
|
|
r'triangulation x and y arrays'):
|
|
triang.calculate_plane_coefficients([])
|
|
|
|
for mask in ([0, 1], None):
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r'mask must be a 1D array with the same length as the '
|
|
r'triangles array'):
|
|
triang.set_mask(mask)
|
|
|
|
triang.set_mask([True])
|
|
assert_array_equal(triang.get_edges(), np.empty((0, 2)))
|
|
|
|
triang.set_mask(()) # Equivalent to Python Triangulation mask=None
|
|
assert_array_equal(triang.get_edges(), [[1, 0], [2, 0], [2, 1]])
|
|
|
|
# C++ TriContourGenerator.
|
|
with pytest.raises(
|
|
TypeError,
|
|
match=r'__init__\(\): incompatible constructor arguments.'):
|
|
mpl._tri.TriContourGenerator()
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r'z must be a 1D array with the same length as the x and y '
|
|
r'arrays'):
|
|
mpl._tri.TriContourGenerator(triang, [1])
|
|
|
|
z = [0, 1, 2]
|
|
tcg = mpl._tri.TriContourGenerator(triang, z)
|
|
|
|
with pytest.raises(
|
|
ValueError, match=r'filled contour levels must be increasing'):
|
|
tcg.create_filled_contour(1, 0)
|
|
|
|
# C++ TrapezoidMapTriFinder.
|
|
with pytest.raises(
|
|
TypeError,
|
|
match=r'__init__\(\): incompatible constructor arguments.'):
|
|
mpl._tri.TrapezoidMapTriFinder()
|
|
|
|
trifinder = mpl._tri.TrapezoidMapTriFinder(triang)
|
|
|
|
with pytest.raises(
|
|
ValueError, match=r'x and y must be array-like with same shape'):
|
|
trifinder.find_many([0], [0, 1])
|
|
|
|
|
|
def test_qhull_large_offset():
|
|
# github issue 8682.
|
|
x = np.asarray([0, 1, 0, 1, 0.5])
|
|
y = np.asarray([0, 0, 1, 1, 0.5])
|
|
|
|
offset = 1e10
|
|
triang = mtri.Triangulation(x, y)
|
|
triang_offset = mtri.Triangulation(x + offset, y + offset)
|
|
assert len(triang.triangles) == len(triang_offset.triangles)
|
|
|
|
|
|
def test_tricontour_non_finite_z():
|
|
# github issue 10167.
|
|
x = [0, 1, 0, 1]
|
|
y = [0, 0, 1, 1]
|
|
triang = mtri.Triangulation(x, y)
|
|
plt.figure()
|
|
|
|
with pytest.raises(ValueError, match='z array must not contain non-finite '
|
|
'values within the triangulation'):
|
|
plt.tricontourf(triang, [0, 1, 2, np.inf])
|
|
|
|
with pytest.raises(ValueError, match='z array must not contain non-finite '
|
|
'values within the triangulation'):
|
|
plt.tricontourf(triang, [0, 1, 2, -np.inf])
|
|
|
|
with pytest.raises(ValueError, match='z array must not contain non-finite '
|
|
'values within the triangulation'):
|
|
plt.tricontourf(triang, [0, 1, 2, np.nan])
|
|
|
|
with pytest.raises(ValueError, match='z must not contain masked points '
|
|
'within the triangulation'):
|
|
plt.tricontourf(triang, np.ma.array([0, 1, 2, 3], mask=[1, 0, 0, 0]))
|
|
|
|
|
|
def test_tricontourset_reuse():
|
|
# If TriContourSet returned from one tricontour(f) call is passed as first
|
|
# argument to another the underlying C++ contour generator will be reused.
|
|
x = [0.0, 0.5, 1.0]
|
|
y = [0.0, 1.0, 0.0]
|
|
z = [1.0, 2.0, 3.0]
|
|
fig, ax = plt.subplots()
|
|
tcs1 = ax.tricontourf(x, y, z)
|
|
tcs2 = ax.tricontour(x, y, z)
|
|
assert tcs2._contour_generator != tcs1._contour_generator
|
|
tcs3 = ax.tricontour(tcs1, z)
|
|
assert tcs3._contour_generator == tcs1._contour_generator
|
|
|
|
|
|
@check_figures_equal()
|
|
def test_triplot_with_ls(fig_test, fig_ref):
|
|
x = [0, 2, 1]
|
|
y = [0, 0, 1]
|
|
data = [[0, 1, 2]]
|
|
fig_test.subplots().triplot(x, y, data, ls='--')
|
|
fig_ref.subplots().triplot(x, y, data, linestyle='--')
|
|
|
|
|
|
def test_triplot_label():
|
|
x = [0, 2, 1]
|
|
y = [0, 0, 1]
|
|
data = [[0, 1, 2]]
|
|
fig, ax = plt.subplots()
|
|
lines, markers = ax.triplot(x, y, data, label='label')
|
|
handles, labels = ax.get_legend_handles_labels()
|
|
assert labels == ['label']
|
|
assert len(handles) == 1
|
|
assert handles[0] is lines
|
|
|
|
|
|
def test_tricontour_path():
|
|
x = [0, 4, 4, 0, 2]
|
|
y = [0, 0, 4, 4, 2]
|
|
triang = mtri.Triangulation(x, y)
|
|
_, ax = plt.subplots()
|
|
|
|
# Line strip from boundary to boundary
|
|
cs = ax.tricontour(triang, [1, 0, 0, 0, 0], levels=[0.5])
|
|
paths = cs.get_paths()
|
|
assert len(paths) == 1
|
|
expected_vertices = [[2, 0], [1, 1], [0, 2]]
|
|
assert_array_almost_equal(paths[0].vertices, expected_vertices)
|
|
assert_array_equal(paths[0].codes, [1, 2, 2])
|
|
assert_array_almost_equal(
|
|
paths[0].to_polygons(closed_only=False), [expected_vertices])
|
|
|
|
# Closed line loop inside domain
|
|
cs = ax.tricontour(triang, [0, 0, 0, 0, 1], levels=[0.5])
|
|
paths = cs.get_paths()
|
|
assert len(paths) == 1
|
|
expected_vertices = [[3, 1], [3, 3], [1, 3], [1, 1], [3, 1]]
|
|
assert_array_almost_equal(paths[0].vertices, expected_vertices)
|
|
assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79])
|
|
assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices])
|
|
|
|
|
|
def test_tricontourf_path():
|
|
x = [0, 4, 4, 0, 2]
|
|
y = [0, 0, 4, 4, 2]
|
|
triang = mtri.Triangulation(x, y)
|
|
_, ax = plt.subplots()
|
|
|
|
# Polygon inside domain
|
|
cs = ax.tricontourf(triang, [0, 0, 0, 0, 1], levels=[0.5, 1.5])
|
|
paths = cs.get_paths()
|
|
assert len(paths) == 1
|
|
expected_vertices = [[3, 1], [3, 3], [1, 3], [1, 1], [3, 1]]
|
|
assert_array_almost_equal(paths[0].vertices, expected_vertices)
|
|
assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79])
|
|
assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices])
|
|
|
|
# Polygon following boundary and inside domain
|
|
cs = ax.tricontourf(triang, [1, 0, 0, 0, 0], levels=[0.5, 1.5])
|
|
paths = cs.get_paths()
|
|
assert len(paths) == 1
|
|
expected_vertices = [[2, 0], [1, 1], [0, 2], [0, 0], [2, 0]]
|
|
assert_array_almost_equal(paths[0].vertices, expected_vertices)
|
|
assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79])
|
|
assert_array_almost_equal(paths[0].to_polygons(), [expected_vertices])
|
|
|
|
# Polygon is outer boundary with hole
|
|
cs = ax.tricontourf(triang, [0, 0, 0, 0, 1], levels=[-0.5, 0.5])
|
|
paths = cs.get_paths()
|
|
assert len(paths) == 1
|
|
expected_vertices = [[0, 0], [4, 0], [4, 4], [0, 4], [0, 0],
|
|
[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]
|
|
assert_array_almost_equal(paths[0].vertices, expected_vertices)
|
|
assert_array_equal(paths[0].codes, [1, 2, 2, 2, 79, 1, 2, 2, 2, 79])
|
|
assert_array_almost_equal(paths[0].to_polygons(), np.split(expected_vertices, [5]))
|