Skip to content
This repository

Linear tri interpolator #1582

Merged
merged 5 commits into from over 1 year ago

5 participants

Ian Thomas Benjamin Root Michael Droettboom Varoquaux Eric Firing
Ian Thomas
Collaborator

This is the first PR for issue #1521. It adds a TrapezoidMapTriFinder that is used to determine the triangles in which (x,y) points lie and a LinearTriInterpolator to interpolate scalar fields defined on triangular grids. Have included tests for both classes, a pylab_example for the interpolator and an whizzy interactive event_handling example for the trifinder, and have updated sphinx docs, whats_new and CHANGELOG.

I'll repeat the observation from #1521 that the delaunay module already has a linear interpolator, but it cannot be used with user-specified triangulations unlike the one in this PR which can be used with user-specified and delaunay-calculated triangulations. The new one is much more flexible in accepting arrays of points to iterate over as they can be any shape, e.g. 1D for a transect through a triangulation, 2D to create a quad mesh to pass to contourf, etc.

It is possible that the TrapezoidMapTriFinder is overkill for what we need. It is O(log N) but may be outperformed by a simpler 'walking across the triangulation' algorithm if most users are passing in hi-res regular 2D grids to interpolate to. My preference is to release this code out into the wild as it is and get feedback from users before adding/modifying the trifinder.

Ian Thomas
Collaborator

In the absence of comments from anyone else, I will check this in myself. I'll leave it a few days more in case there are any objections.

Benjamin Root
Collaborator

Just gave this a visual inspection, and the code looks very well commented and fairly easy to follow. Good job, @ianthomas23

lib/matplotlib/tri/_tri.cpp
((75 lines not shown))
  454
+                {
  455
+                    // The 3 triangle points have identical x and y!  The best
  456
+                    // we can do here is take normal = (0,0,1) and for the
  457
+                    // constant p take the mean of the 3 points' z-values.
  458
+                    normal = XYZ(0.0, 0.0, 1.0);
  459
+                    point0.z = (point0.z + point1.z + point2.z) / 3.0;
  460
+                }
  461
+            }
  462
+
  463
+            *planes++ = -normal.x / normal.z;           // x
  464
+            *planes++ = -normal.y / normal.z;           // y
  465
+            *planes++ = normal.dot(point0) / normal.z;  // constant
  466
+        }
  467
+    }
  468
+
  469
+    return Py::asObject((PyObject*)planes_array);
1
Michael Droettboom Owner
mdboom added a note January 07, 2013

This function should probably be wrapped in a try ... catch to ensure that Py_DECREF(z) is called. It seems unlikely an exception would get thrown here, but a future change may introduce something that could.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Michael Droettboom
Owner

Looks good on quick experimentation. :+1: from me.

examples/pylab_examples/triinterp_demo.py
... ...
@@ -0,0 +1,36 @@
  1
+"""
  2
+Interpolation from triangular grid to quad grid.
  3
+"""
  4
+import matplotlib.pyplot as plt
  5
+import matplotlib.tri as mtri
  6
+import numpy as np
  7
+import math
  8
+
  9
+
  10
+# Create triangulation.
  11
+x = np.asarray([0, 1, 2, 3, 0.5, 1.5, 2.5, 1, 2, 1.5])
  12
+y = np.asarray([0, 0, 0, 0, 1,   1,   1,   2, 2, 3])
  13
+triangles = [[0,1,4], [1,2,5], [2,3,6], [1,5,4], [2,6,5], [4,5,7], [5,6,8],
6
Varoquaux Collaborator
NelleV added a note January 08, 2013

Tiny nitpicks: this file is not completely pep8 compliant: there should be spaces after ,, and spaces around * (l. 18)

Ian Thomas Collaborator

I think this line is more readable without the spaces after commas, but as I am not particularly bothered either way I am happy to change it.

No extras spaces are needed around any of the * in the line
z = np.cos(1.5*x)*np.cos(1.5*y)
From PEP8: "If operators with different priorities are used, consider adding whitespace around the operators with the lowest priority(ies). Use your own judgement". There are no operator priorities causing confusion here; the line is fine as it is. I assume you are using the pep8 tool which is sometimes a little too enthusiastic - it's recommendation of
z = np.cos(1.5 * x) * np.cos(1.5 * y)
is very poor.

Varoquaux Collaborator
NelleV added a note January 08, 2013

I don't have to use the pep8 tool to see this isn't pep8. I prefer z = np.cos(1.5 * x) * np.cos(1.5 * y) above z = np.cos(1.5*x)*np.cos(1.5*y)

This is a matter of choosing whether matplotlib follows strict pep8 convention or not. I always follow the strict pep8 conventation, and many scientific python projects do (with the pep8 tool or flake8 to check the compliance). I believe matplotlib should do the same (it avoids the long discussions on whether X is better than Y and the numerous commits to reformat the code such as the ones I've been doing recently on matplotlib's codebase), but I don't think there has been a clear decision on that.

Ian Thomas Collaborator

I think you are missing the point. I quoted the line from PEP8 that specifically states "use your own judgement". It is impossible to strictly follow the PEP8 document without exercising judgement, i.e. there is no definite right or wrong answer here.

The coding guidelines state that I should follow PEP8. I have. If you think the coding guidelines should be changed to follow pep8 (the tool, not the document) then you should start such a discussion on the matplotlib-devel mailing list.

Eric Firing Owner
efiring added a note January 08, 2013
Varoquaux Collaborator
NelleV added a note January 09, 2013

This is a very recent change in PEP8 I was not aware of.

Else, yes, there are stricter ways to consider the document than others (I don't consider Twisted as following PEP8, and others might). I also believe the less the developper need to think and the more tools are here to do the work and check whether the code is valid, the better it is. When it comes to styling, it's not a matter of judgement, but of culture and personal preferences, which one should generally avoid in a project.

A fix has been integrated to pep8 (the tool) to reflect the change in the PEP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/matplotlib/tests/test_triangulation.py
... ...
@@ -131,3 +131,122 @@ def test_no_modify():
131 131
     tri = mtri.Triangulation(points[:,0], points[:,1], triangles)
132 132
     edges = tri.edges
133 133
     assert_array_equal(old_triangles, triangles)
  134
+
  135
+def test_trifinder():
  136
+    # Test points within triangles of masked triangulation.
  137
+    x,y = np.meshgrid(np.arange(4), np.arange(4))
  138
+    x = x.ravel()
  139
+    y = y.ravel()
  140
+    triangles = [[0,1,4], [1,5,4], [1,2,5], [2,6,5], [2,3,6], [3,7,6], [4,5,8],
2
Varoquaux Collaborator
NelleV added a note January 08, 2013

Same as above.

Ian Thomas Collaborator

Will change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Varoquaux NelleV commented on the diff January 08, 2013
lib/matplotlib/tri/triinterpolate.py
((26 lines not shown))
  26
+        if trifinder is not None and not isinstance(trifinder, TriFinder):
  27
+            raise ValueError('Expected a TriFinder object')
  28
+        self._trifinder = trifinder or self._triangulation.get_trifinder()
  29
+
  30
+
  31
+class LinearTriInterpolator(TriInterpolator):
  32
+    """
  33
+    A LinearTriInterpolator performs linear interpolation on a triangular grid.
  34
+
  35
+    Each triangle is represented by a plane so that an interpolated value at
  36
+    point (x,y) lies on the plane of the triangle containing (x,y).
  37
+    Interpolated values are therefore continuous across the triangulation, but
  38
+    their first derivatives are discontinuous at edges between triangles.
  39
+    """
  40
+    def __init__(self, triangulation, z, trifinder=None):
  41
+        """
4
Varoquaux Collaborator
NelleV added a note January 08, 2013

I've noticed matplotlib has it's own guidelines from docstrings (by that I mean that it doesn't uses numpy's guideline). Are these documented somewhere ?

Michael Droettboom Owner
mdboom added a note January 08, 2013

It uses "raw" Sphinx/rst without the Numpy extensions. To the extent that they are documented, they are documented in the Sphinx documentation. But there is no sense of structure as defined in the Numpy docstring standard. MEP10 hopes to address this in the future.

Varoquaux Collaborator
NelleV added a note January 08, 2013

I can help on this MEP, both with the finalization of the document and the implementation. Is there anyone in charge I should ask?

Michael Droettboom Owner
mdboom added a note January 08, 2013

I wrote the original MEP -- I just haven't had much time to see it through. If you're game, I'd love to see you take this on. We can continue the discussion on matplotlib-devel if you'd like...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Ian Thomas ianthomas23 merged commit e4d4984 into from January 10, 2013
Ian Thomas ianthomas23 closed this January 10, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.