Пример #1
0
    def compute_hypothetical_errors(self, point_indeces):
        """
        Computes the hypothetical errors of each point in the TIN, if they were to be removed.
        """
        indices, indptr = self.dt.vertex_neighbor_vertices

        for pt_index in point_indeces:
            current_vertex = self.dt.points[pt_index]
            point = self.grid.get(current_vertex[0], current_vertex[1])

            if point in self.grid.get_corner_set():
                continue

            # Find neighboring vertices of the vertex at pt_index & create hypothetical triangulation
            neighbor_indeces = indptr[indices[pt_index]:indices[pt_index + 1]]
            neighbors = self.dt.points[neighbor_indeces]
            hypothetical_triangulation = Delaunay(neighbors)

            # Store neighbor point data in the point for later
            point_neighbors = set()
            for pt in neighbors:
                point_neighbors.add(self.grid.get(pt[0], pt[1]))
            point.neighbors = point_neighbors

            # Locate current point in new triangulation & compute error
            simplex = hypothetical_triangulation.find_simplex(current_vertex)
            triangle_pts = hypothetical_triangulation.points[
                hypothetical_triangulation.simplices[simplex]]
            self.compute_point_error(point, triangle_pts)
Пример #2
0
    def __init__(self, coords, values=None, neighbors=3, dist_transform=2):
        super().__init__(coords, values)

        # Construct a convex hull of passed coords. Cast coords to float32, otherwise cv2 may fail. cv2 is used since
        # QHull can't handle degenerate hulls.
        self.coords_hull = cv2.convexHull(self.coords.astype(np.float32), returnPoints=True)

        # Construct an IDW interpolator to use outside the constructed hull
        self.idw_interpolator = IDWInterpolator(coords, values, neighbors=neighbors, dist_transform=dist_transform)

        # Triangulate input points
        try:
            self.tri = Delaunay(self.coords, incremental=False)
        except QhullError:
            # Delaunay fails in case of linearly dependent coordinates. Create artificial points in the corners of
            # given coordinate grid in order for Delaunay to work with a full rank matrix.
            min_x, min_y = np.min(self.coords, axis=0) - 1
            max_x, max_y = np.max(self.coords, axis=0) + 1
            corner_coords = [(min_x, min_y), (min_x, max_y), (max_x, min_y), (max_x, max_y)]
            self.coords = np.concatenate([self.coords, corner_coords])
            if self.values is not None:
                mean_values = np.mean(self.values, axis=0, keepdims=True, dtype=self.values.dtype)
                corner_values = np.repeat(mean_values, 4, axis=0)
                self.values = np.concatenate([self.values, corner_values])
            self.tri = Delaunay(self.coords, incremental=False)

        # Perform the first auxiliary call of the tri for it to work properly in different processes.
        # Otherwise interpolation may fail if called in a pipeline with prefetch with mpc target.
        _ = self.tri.find_simplex((0, 0))
Пример #3
0
def _interp(orig_val: np.ndarray, orig_lat: np.ndarray, orig_lon: np.ndarray,
            lat: np.ndarray, lon: np.ndarray) -> np.ndarray:
    """
        格点插值
    :param orig_val: 等距离文件中的格点数组
    :param orig_lat: 等距离文件的lat数组
    :param orig_lon: 等距离文件的lon数组
    :param lat: 所需要的等经纬度的lat数组
    :param lon: 所需要的等经纬度的lon数组
    :return:
    """
    if orig_val.ndim != 2:
        raise ValueError('interp only receive 2-dim variable')
    if lat.ndim == 1 or lon.ndim == 1:
        lon, lat = np.meshgrid(lon, lat)

    if _INTERP_SWITCH == 1:
        tri = Delaunay(np.asarray([orig_lon.ravel(), orig_lat.ravel()]).T)
        interpolator = LinearNDInterpolator(tri, orig_val.ravel())
        val = interpolator((lon, lat))
    elif _INTERP_SWITCH == 2:
        val = griddata(
            np.asarray([orig_lon.ravel(), orig_lat.ravel()]).T,
            orig_val.ravel(), (lon, lat))
    else:
        raise ValueError('INTERP_SWITCH only can be 1 or 2')

    return val
Пример #4
0
    def _add_boundary_points_and_construct_delaunay(self):
        """
        This method add the corners of [0, 1]^dimension hypercube to the existing samples, which are used to construct a
        Delaunay Triangulation.
        """
        from scipy.spatial.qhull import Delaunay

        self.mesh_vertices = self.training_points.copy()
        self.points_to_samplesU01 = np.arange(0, self.training_points.shape[0])
        for i in range(np.shape(self.strata_object.voronoi.vertices)[0]):
            if any(np.logical_and(self.strata_object.voronoi.vertices[i, :] >= -1e-10,
                                  self.strata_object.voronoi.vertices[i, :] <= 1e-10)) or \
                    any(np.logical_and(self.strata_object.voronoi.vertices[i, :] >= 1 - 1e-10,
                                       self.strata_object.voronoi.vertices[i, :] <= 1 + 1e-10)):
                self.mesh_vertices = np.vstack([
                    self.mesh_vertices,
                    self.strata_object.voronoi.vertices[i, :]
                ])
                self.points_to_samplesU01 = np.hstack([
                    np.array([-1]),
                    self.points_to_samplesU01,
                ])

        # Define the simplex mesh to be used for gradient estimation and sampling
        self.mesh = Delaunay(self.mesh_vertices,
                             furthest_site=False,
                             incremental=True,
                             qhull_options=None)
        self.points = getattr(self.mesh, 'points')
Пример #5
0
def GridInterpolation(pos, data, Xi, Yi):
    from scipy.spatial.qhull import Delaunay
    from scipy.interpolate import CloughTocher2DInterpolator
    import itertools

    extremes = np.array([pos.min(axis=0), pos.max(axis=0)])
    diffs = extremes[1] - extremes[0]
    extremes[0] -= diffs
    extremes[1] += diffs

    eidx = np.array(
        list(
            itertools.product(*([[0] * (pos.shape[1] - 1) + [1]] *
                                pos.shape[1]))))
    pidx = np.tile(np.arange(pos.shape[1])[np.newaxis], (len(eidx), 1))
    n_extra = pidx.shape[0]
    outer_pts = extremes[eidx, pidx]
    pos = np.concatenate((pos, outer_pts))
    tri = Delaunay(pos)

    data = np.concatenate((data, np.zeros(n_extra)))
    Interpolator = CloughTocher2DInterpolator(tri, data)
    args = [Xi, Yi]
    Zi = Interpolator(*args)
    return Zi
Пример #6
0
def create_tree(features, neighs=200):
    tri = Delaunay(features)
    tree = DistanceTree(features)

    for entries in tri.simplices:
        row = []
        for entry in entries:
            row.append(features[entry].tolist())

        dist1 = distance.euclidean(row[0], row[1])
        dist2 = distance.euclidean(row[0], row[2])
        dist3 = distance.euclidean(row[1], row[2])

        edge1 = Edge(entries[0], entries[1], dist1, Vertex(row[0]),
                     Vertex(row[1]))
        edge2 = Edge(entries[0], entries[2], dist2, Vertex(row[0]),
                     Vertex(row[2]))
        edge3 = Edge(entries[1], entries[2], dist3, Vertex(row[1]),
                     Vertex(row[2]))

        tree.add_edge(edge1)
        tree.add_edge(edge2)
        tree.add_edge(edge3)

    tree.calc_neighbours(neighs)
    tree.wasser_cost_calc()
    tree.sort_wasser()

    return tree
Пример #7
0
def single_surface(star_container=None, points=None):
    """
    calculates triangulation of given set of points, if points are not given, star surface points are used. Returns
    set of triple indices of surface pints that make up given triangle

    :param star_container: StarContainer;
    :param points: np.array:

    ::

        numpy.array([[x1 y1 z1],
                     [x2 y2 z2],
                     ...
                    [xN yN zN]])

    :return: np.array():

    ::

        numpy.array([[point_index1 point_index2 point_index3],
                     [...],
                      ...
                     [...]])
    """
    if points is None:
        points = star_container.points
    triangulation = Delaunay(points)
    triangles_indices = triangulation.convex_hull
    return triangles_indices
Пример #8
0
    def optimal_mu(self, error=None, k=1):
        """
        Return the parametric points where new high-fidelity solutions have to
        be computed in order to globally reduce the estimated error. These
        points are the barycentric center of the region (simplex) with higher
        error.

        :param numpy.ndarray error: the estimated error evaluated for each
            snapshot; if error array is not passed, it is computed using
            :func:`loo_error` with the default function. Default value is None.
        :param int k: the number of optimal points to return. Default value is
            1.
        :return: the optimal points
        :rtype: list(numpy.ndarray)
        """
        if error is None:
            error = self.loo_error()

        mu = self.database.parameters
        tria = Delaunay(mu)

        error_on_simplex = np.array([
            np.sum(error[smpx]) * self._simplex_volume(mu[smpx])
            for smpx in tria.simplices
        ])

        barycentric_point = []
        for index in np.argpartition(error_on_simplex, -k)[-k:]:
            worst_tria_pts = mu[tria.simplices[index]]
            worst_tria_err = error[tria.simplices[index]]

            barycentric_point.append(
                np.average(worst_tria_pts, axis=0, weights=worst_tria_err))

        return barycentric_point
Пример #9
0
def conv(points):
    "Indices of the convex hull."
    if len(points) <= 2:
        return points
    else:
        tri = Delaunay(np.array([(x.x, x.y) for x in points]))
        hul = list({v for x in tri.convex_hull for v in x})
        return [points[i] for i in hul]
Пример #10
0
def generate_delaunay_triangulation(k):
    x = np.random.rand(k, 1)
    y = np.random.rand(k, 1)
    X = np.concatenate((x - 1, x - 1, x - 1, x, x, x, x + 1, x + 1, x + 1))
    Y = np.concatenate((y - 1, y, y + 1, y - 1, y, y + 1, y - 1, y, y + 1))
    points = np.dstack((X, Y)).reshape((-1, 2))
    tri = Delaunay(points)
    return tri
Пример #11
0
def detached_system_surface(system, components_distance, points=None, component="all"):
    """
    Calculates surface faces from the given component's points in case of detached or semi-contact system.

    :param system: elisa.binary_system.container.OrbitalPositionContainer;
    :param components_distance: float;
    :param points: numpy.array;
    :param component: str;
    :return: numpy.array; N x 3 array of vertices indices
    """
    component_instance = getattr(system, component)
    if points is None:
        points = component_instance.points

    if not np.any(points):
        raise ValueError(f"{component} component, with class instance name {component_instance.name} do not "
                         "contain any valid surface point to triangulate")
    # there is a problem with triangulation of near over-contact system, delaunay is not good with pointy surfaces
    critical_pot = system.primary.critical_surface_potential if component == 'primary' \
        else system.secondary.critical_surface_potential
    potential = system.primary.surface_potential if component == 'primary' \
        else system.secondary.surface_potential
    if potential - critical_pot > 0.01:
        logger.debug(f'triangulating surface of {component} component using standard method')
        triangulation = Delaunay(points)
        triangles_indices = triangulation.convex_hull
    else:
        logger.debug(f'surface of {component} component is near or at critical potential; therefore custom '
                     f'triangulation method for (near)critical potential surfaces will be used')
        # calculating closest point to the barycentre
        r_near = np.max(points[:, 0]) if component == 'primary' else np.min(points[:, 0])
        # projection of component's far side surface into ``sphere`` with radius r1

        points_to_transform = copy(points)
        if component == 'secondary':
            points_to_transform[:, 0] -= components_distance
        projected_points = \
            r_near * points_to_transform / np.linalg.norm(points_to_transform, axis=1)[:, None]
        if component == 'secondary':
            projected_points[:, 0] += components_distance

        triangulation = Delaunay(projected_points)
        triangles_indices = triangulation.convex_hull

    return triangles_indices
Пример #12
0
class BaseDelaunayInterpolator(SpatialInterpolator):
    """A base class for interpolators built on top of Delaunay triangulation of data points.

    The class triangulates input `coords` and stores the result in `tri` attribute of the created instance. It also
    constructs an IDW interpolator which is used to perform extrapolation for coordinates lying outside the convex hull
    of data points. Each concrete subclass must implement `_interpolate_inside_hull` method.
    """
    def __init__(self, coords, values=None, neighbors=3, dist_transform=2):
        super().__init__(coords, values)

        # Construct a convex hull of passed coords. Cast coords to float32, otherwise cv2 may fail. cv2 is used since
        # QHull can't handle degenerate hulls.
        self.coords_hull = cv2.convexHull(self.coords.astype(np.float32), returnPoints=True)

        # Construct an IDW interpolator to use outside the constructed hull
        self.idw_interpolator = IDWInterpolator(coords, values, neighbors=neighbors, dist_transform=dist_transform)

        # Triangulate input points
        try:
            self.tri = Delaunay(self.coords, incremental=False)
        except QhullError:
            # Delaunay fails in case of linearly dependent coordinates. Create artificial points in the corners of
            # given coordinate grid in order for Delaunay to work with a full rank matrix.
            min_x, min_y = np.min(self.coords, axis=0) - 1
            max_x, max_y = np.max(self.coords, axis=0) + 1
            corner_coords = [(min_x, min_y), (min_x, max_y), (max_x, min_y), (max_x, max_y)]
            self.coords = np.concatenate([self.coords, corner_coords])
            if self.values is not None:
                mean_values = np.mean(self.values, axis=0, keepdims=True, dtype=self.values.dtype)
                corner_values = np.repeat(mean_values, 4, axis=0)
                self.values = np.concatenate([self.values, corner_values])
            self.tri = Delaunay(self.coords, incremental=False)

        # Perform the first auxiliary call of the tri for it to work properly in different processes.
        # Otherwise interpolation may fail if called in a pipeline with prefetch with mpc target.
        _ = self.tri.find_simplex((0, 0))

    def _is_in_hull(self, coords):
        """Check whether items in `coords` lie within the convex hull of data points."""
        coords = coords.astype(np.float32)  # Cast coords to float32 to match the type of points in the convex hull
        return np.array([cv2.pointPolygonTest(self.coords_hull, coord, measureDist=False) >= 0 for coord in coords])

    def _interpolate_inside_hull(self, coords):
        """Perform interpolation for coordinates lying inside convex hull of data points. `coords` are guaranteed to be
        2-dimensional with shape (n_coords, 2)."""
        _ = coords
        raise NotImplementedError

    def _interpolate(self, coords):
        """Perform interpolation at given `coords`. Falls back to an IDW interpolator for points lying outside the
        convex hull of data points. `coords` are guaranteed to be 2-dimensional with shape (n_coords, 2)."""
        inside_hull_mask = self._is_in_hull(coords)
        values = np.empty((len(coords), self.values.shape[1]), dtype=self.values.dtype)
        values[inside_hull_mask] = self._interpolate_inside_hull(coords[inside_hull_mask])
        # pylint: disable-next=protected-access
        values[~inside_hull_mask] = self.idw_interpolator._interpolate(coords[~inside_hull_mask])
        return values
Пример #13
0
def conv(points):
    "Indices of the convex hull."
    _points = points
    points = np.array([(x.m, x.b) for x in points])
    if len(points) <= 2:
        hul = range(len(points))
    else:
        tri = Delaunay(points)
        hul = list({v for x in tri.convex_hull for v in x})
    return list(np.array(_points)[hul])
Пример #14
0
def alphaShape(coordinates: TCoordinates, alpha=0.5):
    """
    Compute the `alpha shape`_ of a set of points. Based on `this implementation`_. In contrast to the standard
    definition of the parameter alpha here we normalize it by the mean edge size of the cluster. This results in
    similar "concavity properties" of the resulting shapes for different coordinate sets and a fixed alpha.

    .. _this implementation: https://sgillies.net/2012/10/13/the-fading-shape-of-alpha.html
    .. _alpha shape: https://en.wikipedia.org/wiki/Alpha_shape

    :param coordinates: a suitable iterable of 2-dimensional coordinates
    :param alpha: alpha value to influence the gooeyness of the border. Larger numbers
        don't fall inward as much as smaller numbers.
    :return: a shapely Polygon
    """
    coordinates = extractCoordinatesArray(coordinates)

    edge_index_pairs = set()
    edge_vertex_pairs = []
    graph = delaunayGraph(coordinates)
    mean_edge_size = graph.size(weight="weight") / graph.number_of_edges()

    def add_edge(edge_index_pair, edge_vertex_pair):
        """
        Add a line between the i-th and j-th points,
        if not in the list already
        """
        edge_index_pair = tuple(sorted(edge_index_pair))
        if edge_index_pair in edge_index_pairs:
            # already added
            return
        edge_index_pairs.add(edge_index_pair)
        edge_vertex_pairs.append(edge_vertex_pair)

    tri = Delaunay(coordinates)
    for simplex in tri.simplices:
        vertices = tri.points[simplex]
        area = Polygon(vertices).area
        edges = combinations(vertices, 2)
        product_edges_lengths = 1
        for vertex_1, vertex_2 in edges:
            product_edges_lengths *= euclidean(vertex_1, vertex_2)
        # this is the radius of the circumscribed circle of the triangle
        # see https://en.wikipedia.org/wiki/Circumscribed_circle#Triangles
        circum_r = product_edges_lengths / (4.0 * area)

        if circum_r < mean_edge_size / alpha:
            for index_pair in combinations(simplex, 2):
                add_edge(index_pair, tri.points[np.array(index_pair)])

    remaining_edges = MultiLineString(edge_vertex_pairs)

    return unary_union(list(polygonize(remaining_edges)))
Пример #15
0
    def draw_delaunay_triangulation(self):
        """
        Draws the Delaunay triangulation of cell centers.
        """
        points = [center.loc for center in self.centers]
        dual = Delaunay(points)

        simplices = [[dual.points[i] for i in simplex]
                     for simplex in dual.simplices]

        for simplex in simplices:
            centroid = tuple((sum(simplex) / 3).astype(int))[::-1]
            polygon(self.window, IMAGE[centroid], simplex)
Пример #16
0
    def _rebuild_hull(self):

        numpy_parameters = self._training_parameters.numpy()

        if numpy_parameters.shape[0] < numpy_parameters.shape[1] + 2:
            # Check we have enough points to build a Delaunay hull.
            return

        # We need to remove any 'flat' degrees of freedom (i.e any
        # parameters where all training data has the same value, such
        # as removing temperatures if all training points were measured
        # at the same temperature).
        self._flat_parameter_indices = numpy.argwhere(
            numpy.all(numpy_parameters == numpy_parameters[0, :], axis=0))

        self._flat_parameter_values = numpy_parameters[
            0, self._flat_parameter_indices]

        index_mask = numpy.ones(numpy_parameters.shape[1], numpy.bool)
        index_mask[self._flat_parameter_indices] = 0

        hull_parameters = numpy_parameters[:, index_mask]

        self._convex_hull = Delaunay(hull_parameters)
Пример #17
0
    def invalidate(self, proj):
        try:
            from scipy.spatial.qhull import Delaunay
        except ImportError:
            print('DelaunayLayer needs scipy >= 0.12')
            raise

        self.painter = BatchPainter()
        x, y = proj.lonlat_to_screen(self.data['lon'], self.data['lat'])
        points = list(set(zip(x, y)))
        dela = Delaunay(points)

        edges = set()
        for tria in dela.vertices:
            edges.add((tria[0], tria[1]))
            edges.add((tria[1], tria[2]))
            edges.add((tria[2], tria[0]))

        allx0 = []
        ally0 = []
        allx1 = []
        ally1 = []
        colors = []

        for a, b in edges:
            x0, y0 = dela.points[a]
            x1, y1 = dela.points[b]

            allx0.append(x0)
            ally0.append(y0)
            allx1.append(x1)
            ally1.append(y1)

            if self.line_color:
                colors.append(self.line_color)
                colors.append(self.line_color)
            elif self.cmap:
                l = math.sqrt((x0 - x1)**2 + (y0 - y1)**2)
                c = self.cmap.to_color(l, self.max_lenght, 'log')
                colors.append(c)
                colors.append(c)

        self.painter.lines(allx0,
                           ally0,
                           allx1,
                           ally1,
                           colors,
                           width=self.line_width)
Пример #18
0
def delaunay_pos_graph(pos):
    """Compute a graph containing the Delaunay triangulation of the positons."""
    # Duplicate the positions left and right
    # Needed for the triangulation to wrap around earth
    new_pos = pos.copy()
    duplicate_right = pos.copy()
    duplicate_right[:, 1] = duplicate_right[:, 1] + 360
    new_pos = np.vstack((new_pos, duplicate_right))

    duplicate_left = pos.copy()
    duplicate_left[:, 1] = duplicate_left[:, 1] - 360
    new_pos = np.vstack((new_pos, duplicate_left))

    def reconvert_node(node_num, n_ixps):
        """Convert from the position in the duplicate matrix to the original."""
        return node_num - n_ixps * (node_num // n_ixps)

    # Make the Delaunay triangulation
    triangulation = Delaunay(new_pos)

    # Load the links from the triangles in a graph, with geographic distances
    n_pos = pos.shape[0]  # 578
    G = nx.Graph()
    G.add_nodes_from(np.arange(n_pos))

    for cur_tri in triangulation.simplices:
        a1, b1, c1 = cur_tri
        a = reconvert_node(a1, n_pos)
        b = reconvert_node(b1, n_pos)
        c = reconvert_node(c1, n_pos)
        ab = great_circle(pos[a, :], pos[b, :]).km
        bc = great_circle(pos[b, :], pos[c, :]).km
        ca = great_circle(pos[c, :], pos[a, :]).km
        if not ab > 0:
            print(f"{a} - {b} is {ab} in {a1, b1, c1}")
            # raise ValueError(f"{a} - {b} is {ab} in {a1, b1, c1}")
        if not bc > 0:
            print(f"{b} - {c} is {bc} in {a1, b1, c1}")
            # raise ValueError(f"{b} - {c} is {bc} in {a1, b1, c1}")
        if not ca > 0:
            print(f"{c} - {a} is {ca} in {a1, b1, c1}")
            # raise ValueError(f"{c} - {a} is {ca} in {a1, b1, c1}")
        G.add_edge(a, b, length=ab)
        G.add_edge(b, c, length=bc)
        G.add_edge(c, a, length=ca)

    return G
Пример #19
0
def polygon_sample(vertices):
    """Uniform sampling of points inside a convex polygon. Non convex polygons 
    will be treated as the input would be their convex hull.
    
    Steps of the algorithm
    
    1) `Delaunay triangulation`_ to break the polygon into triangular mesh.
    2) Draw random uniform triangle weighted by its area.
    3) Draw random uniform sample from inside the triangle.
    
    .. _Delaunay triangulation: https://en.wikipedia.org/wiki/Delaunay_triangulation
    
    Args:
        vertices (numpy.ndarray): 
            Array of polygon vertices. Shape of (n, 2).

    Yields:
        numpy.ndarray: 
            Random point inside the polygon.

    References:
        - http://gis.stackexchange.com/questions/6412/generate-points-that-lie-inside-polygon
    
    Todo:
        - Algorithm for sampling non-convex polygons
    """
    assert len(vertices.shape) == 2
    assert vertices.shape[1] == 2
    #np.random.seed(seed)

    delaunay = Delaunay(vertices)  # Triangulation
    mesh = vertices[delaunay.simplices]  # Triangle mesh

    # Weights for choosing random uniform triangle from the mesh.
    # Weight are normalized to values in interval [0, 1].
    weights = triangle_area_cumsum(mesh)
    weights /= weights[-1]

    while True:
        x = np.random.random()  # Random variable from interval [0, 1]
        i = np.searchsorted(weights, x)  # Uniformly drawn random triangle
        a, b, c = mesh[i]
        #print(a)
        #print(b)
        #print(c)
        #yield random_sample_triangle(a, b, c)
        yield random_sample_triangle(a, b, c, seed)
    def __init__(self, polygon):
        """Polygon to sample.

        :param polygon: Shapely polygon or numpy array of polygon vertices.
        """
        if isinstance(polygon, Polygon):
            self.vertices = np.asarray(polygon.exterior)
        elif isinstance(polygon, np.ndarray):
            self.vertices = polygon
        else:
            raise Exception("")

        # Triangular mesh by Delaunay triangulation algorithm
        self.delaunay = Delaunay(self.vertices)
        self.mesh = self.vertices[self.delaunay.simplices]

        # Cumulative sum of areas of the triangles
        self.weights = triangle_area_cumsum(self.mesh)
        self.weights /= self.weights[-1]  # Normalize values to interval [0, 1]
Пример #21
0
    def __init__(self, pos, res=64):
        from scipy.spatial.qhull import Delaunay
        import itertools

        extremes = np.array([pos.min(axis=0), pos.max(axis=0)])
        diffs = extremes[1] - extremes[0]
        extremes[0] -= diffs
        extremes[1] += diffs

        eidx = np.array(
            list(
                itertools.product(*([[0] * (pos.shape[1] - 1) + [1]] *
                                    pos.shape[1]))))
        pidx = np.tile(np.arange(pos.shape[1])[np.newaxis], (len(eidx), 1))
        self.n_extra = pidx.shape[0]
        outer_pts = extremes[eidx, pidx]
        pos = np.concatenate((pos, outer_pts))
        self.tri = Delaunay(pos)

        self.set_Grid()
Пример #22
0
    def draw_voronoi_dual(self):
        """
        Draws the Delaunay triangulation of cell centers.
        """

        points = [center.loc for center in self.centers]
        points.append(self.color_center.loc)
        try:
            dual = Delaunay(points)
        except (QhullError, ValueError):
            #Either too few points or points are degenerate.
            return

        simplices = [[dual.points[i].astype(int) for i in simplex] for simplex in dual.simplices]

        if self.fill:
            for simplex in simplices:
                polygon(self.WINDOW, self.color(simplex, dual=True), simplex)

        if self.outline:
            for simplex in simplices:
                aalines(self.WINDOW, (255, 255, 255), True, simplex, 1)
Пример #23
0
def simplex_edge_difference(X):
    tri = Delaunay(X, qhull_options="QJ")

    simplices = []
    for e in tri.simplices:
        diff = X[e[1:]] - X[e[0]]
        det = np.linalg.det(diff)
        if det > 1e-6:
            simplices.append(e)

    val = []
    for triangle in simplices:
        dists = np.zeros(len(triangle))

        for i in range(len(triangle)):
            a, b = triangle[i], triangle[(i + 1) % len(triangle)]
            dists[i] = np.linalg.norm(X[a] - X[b])

        val.append(dists.max() - dists.min())

    val = np.array(val)

    return val.mean()
Пример #24
0
        if (
            draw_polyhedra
            and len(connected_sites) > 3
            and not connected_sites_not_drawn
            and not any(not_most_electro_negative)
        ):
            if explicitly_calculate_polyhedra_hull:

                try:

                    # all_positions = [[0, 0, 0], [0, 0, 10], [0, 10, 0], [10, 0, 0]]
                    # gives...
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(all_positions).convex_hull
                except Exception as e:
                    vertices_indices = []

                vertices = [
                    all_positions[idx] for idx in chain.from_iterable(vertices_indices)
                ]

                polyhedron = [Surface(positions=vertices, color=site_color)]

            else:

                polyhedron = [Convex(positions=all_positions, color=site_color)]

    return Scene(
        self.species_string,
Пример #25
0
def get_site_scene(
    self,
    connected_sites: List[ConnectedSite] = None,
    # connected_site_metadata: None,
    # connected_sites_to_draw,
    connected_sites_not_drawn: List[ConnectedSite] = None,
    hide_incomplete_edges: bool = False,
    incomplete_edge_length_scale: Optional[float] = 1.0,
    connected_sites_colors: Optional[List[str]] = None,
    connected_sites_not_drawn_colors: Optional[List[str]] = None,
    origin: Optional[List[float]] = None,
    draw_polyhedra: bool = True,
    explicitly_calculate_polyhedra_hull: bool = False,
    bond_radius: float = 0.1,
    legend: Optional[Legend] = None,
) -> Scene:
    """

    Args:
        connected_sites:
        connected_sites_not_drawn:
        hide_incomplete_edges:
        incomplete_edge_length_scale:
        connected_sites_colors:
        connected_sites_not_drawn_colors:
        origin:
        explicitly_calculate_polyhedra_hull:
        legend:

    Returns:

    """

    atoms = []
    bonds = []
    polyhedron = []

    legend = legend or Legend(self)

    # for disordered structures
    is_ordered = self.is_ordered
    phiStart, phiEnd = None, None
    occu_start = 0.0

    position = self.coords.tolist()

    for idx, (sp, occu) in enumerate(self.species.items()):

        if isinstance(sp, DummySpecie):

            cube = Cubes(positions=[position],
                         color=legend.get_color(sp, site=self),
                         width=0.4)
            atoms.append(cube)

        else:

            color = legend.get_color(sp, site=self)
            radius = legend.get_radius(sp, site=self)

            # TODO: make optional/default to None
            # in disordered structures, we fractionally color-code spheres,
            # drawing a sphere segment from phi_end to phi_start
            # (think a sphere pie chart)
            if not is_ordered:
                phi_frac_end = occu_start + occu
                phi_frac_start = occu_start
                occu_start = phi_frac_end
                phiStart = phi_frac_start * np.pi * 2
                phiEnd = phi_frac_end * np.pi * 2

            name = str(sp)
            if occu != 1.0:
                name += " ({}% occupancy)".format(occu)
            name += f" ({position[0]:.3f}, {position[1]:.3f}, {position[2]:.3f})"

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
                clickable=True,
                tooltip=name,
            )
            atoms.append(sphere)

    if not is_ordered and not np.isclose(phiEnd, np.pi * 2):
        # if site occupancy doesn't sum to 100%, cap sphere
        sphere = Spheres(
            positions=[position],
            color="#ffffff",
            radius=self.properties["display_radius"][0],
            phiStart=phiEnd,
            phiEnd=np.pi * 2,
        )
        atoms.append(sphere)

    if connected_sites:

        # TODO: more graceful solution here
        # if ambiguous (disordered), re-use last color used
        site_color = color

        # TODO: can cause a bug if all vertices almost co-planar
        # necessary to include center site in case it's outside polyhedra
        all_positions = [self.coords]

        for idx, connected_site in enumerate(connected_sites):

            connected_position = connected_site.site.coords
            bond_midpoint = np.add(position, connected_position) / 2

            if connected_sites_colors:
                color = connected_sites_colors[idx]
            else:
                color = site_color

            cylinder = Cylinders(
                positionPairs=[[position, bond_midpoint.tolist()]],
                color=color,
                radius=bond_radius,
            )
            bonds.append(cylinder)
            all_positions.append(connected_position.tolist())

        if connected_sites_not_drawn and not hide_incomplete_edges:

            for idx, connected_site in enumerate(connected_sites_not_drawn):

                connected_position = connected_site.site.coords
                bond_midpoint = (incomplete_edge_length_scale *
                                 np.add(position, connected_position) / 2)

                if connected_sites_not_drawn_colors:
                    color = connected_sites_not_drawn_colors[idx]
                else:
                    color = site_color

                cylinder = Cylinders(
                    positionPairs=[[position, bond_midpoint.tolist()]],
                    color=color,
                    radius=bond_radius,
                )
                bonds.append(cylinder)
                all_positions.append(connected_position.tolist())

        # ensure intersecting polyhedra are not shown, defaults to choose by electronegativity
        not_most_electro_negative = map(
            lambda x:
            (x.site.specie < self.specie) or (x.site.specie == self.specie),
            connected_sites,
        )

        all_positions = [list(p) for p in all_positions]

        if (draw_polyhedra and len(connected_sites) > 3
                and not connected_sites_not_drawn
                and not any(not_most_electro_negative)):
            if explicitly_calculate_polyhedra_hull:

                try:

                    # all_positions = [[0, 0, 0], [0, 0, 10], [0, 10, 0], [10, 0, 0]]
                    # gives...
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(all_positions).convex_hull
                except Exception as e:
                    vertices_indices = []

                vertices = [
                    all_positions[idx]
                    for idx in chain.from_iterable(vertices_indices)
                ]

                polyhedron = [Surface(positions=vertices, color=site_color)]

            else:

                polyhedron = [
                    Convex(positions=all_positions, color=site_color)
                ]

    return Scene(
        self.species_string,
        [
            Scene("atoms", contents=atoms),
            Scene("bonds", contents=bonds),
            Scene("polyhedra", contents=polyhedron),
        ],
        origin=origin,
    )
import numpy as np
import json
from scipy.spatial.qhull import Delaunay

with open("position.json", 'r') as w:
    original_data = json.load(w)[0]['Argentina']

points = np.zeros([10, 2])
for i in range(10):
    points[i, 0] = original_data[i]['x']
    points[i, 1] = original_data[i]['y']
tri = Delaunay(points)
import matplotlib.pyplot as plt
plt.triplot(points[:, 0], points[:, 1], tri.simplices.copy())
plt.plot(points[:, 0], points[:, 1], 'o')
plt.show()
Пример #27
0
 def _is_inside_scaffold(scaffold_positions: np.ndarray,
                         new_position: np.ndarray):
     hull = ConvexHull(scaffold_positions, incremental=False)
     vertices = scaffold_positions[hull.vertices]
     delaunay = Delaunay(vertices)
     return delaunay.find_simplex(new_position) >= 0
Пример #28
0
def cube_frac2cart(cvalues, v1, v2, v3, centre=(0., 0., 0.), min_voxels=None, max_voxels=1000000, interp='linear',
                   make_cubic=False, bval=False):
    """convert a 3d cube of values, whose indexes relate to fractional coordinates of v1,v2,v3,
    into a cube of values in the cartesian basis
    (using a background value for coordinates outside the bounding box of v1,v2,v3)

    NB: there may be some edge effects for smaller cubes

    Properties
    ----------
    values : array((N,M,L))
        values in fractional basis
    v1 : array((3,))
    v2 : array((3,))
    v3 : array((3,))
    centre : array((3,))
        cartesian coordinates for centre of v1, v2, v3
    min_voxels : int or None
        minimum number of voxels in returned cube. If None, compute base on input cube
    max_voxels : int or None
        maximum number of voxels in returned cube. If None, compute base on input cube
    interp : str
        interpolation mode; 'nearest' or 'linear'
    make_cubic: bool
        if True, ensure all final cartesian cube sides are of the same length
    bval: float
        background value to use outside the bounding box of the cube.
        If False, use numpy.nan

    Returns
    -------
    B : array((P,Q,R))
        where P,Q,R <= longest_side
    min_bounds : array((3,))
        xmin,ymin,zmin
    max_bounds : array((3,))
        xmax,ymax,zmax

    Example
    -------

    >>> from pprint import pprint
    >>> import numpy as np
    >>> fcube = np.array(
    ...    [[[1.,5.],
    ...      [3.,7.]],
    ...     [[2.,6.],
    ...      [4.,8.]]])
    ...
    >>> ncube, min_bound, max_bound = cube_frac2cart(fcube, [1.,0.,0.], [0.,1.,0.], [0.,0.,1.], min_voxels=30)
    >>> min_bound.tolist()
    [-0.5, -0.5, -0.5]
    >>> max_bound.tolist()
    [0.5, 0.5, 0.5]
    >>> pprint(ncube.round(1).tolist())
    [[[1.0, 1.0, 3.0, 5.0],
      [1.0, 1.0, 3.0, 5.0],
      [2.0, 2.0, 4.0, 6.0],
      [3.0, 3.0, 5.0, 7.0]],
     [[1.0, 1.0, 3.0, 5.0],
      [1.0, 1.0, 3.0, 5.0],
      [2.0, 2.0, 4.0, 6.0],
      [3.0, 3.0, 5.0, 7.0]],
     [[1.5, 1.5, 3.5, 5.5],
      [1.5, 1.5, 3.5, 5.5],
      [2.5, 2.5, 4.5, 6.5],
      [3.5, 3.5, 5.5, 7.5]],
     [[2.0, 2.0, 4.0, 6.0],
      [2.0, 2.0, 4.0, 6.0],
      [3.0, 3.0, 5.0, 7.0],
      [4.0, 4.0, 6.0, 8.0]]]

    >>> ncube, min_bound, max_bound = cube_frac2cart(fcube, [2.,0.,0.], [0.,1.,0.], [0.,0.,1.], min_voxels=30)
    >>> min_bound.tolist()
    [-1.0, -0.5, -0.5]
    >>> max_bound.tolist()
    [1.0, 0.5, 0.5]
    >>> pprint(ncube.round(1).tolist())
    [[[1.0, 1.7, 4.3], [1.3, 2.0, 4.7], [2.7, 3.3, 6.0]],
     [[1.0, 1.7, 4.3], [1.3, 2.0, 4.7], [2.7, 3.3, 6.0]],
     [[1.2, 1.8, 4.5], [1.5, 2.2, 4.8], [2.8, 3.5, 6.2]],
     [[1.5, 2.2, 4.8], [1.8, 2.5, 5.2], [3.2, 3.8, 6.5]],
     [[1.8, 2.5, 5.2], [2.2, 2.8, 5.5], [3.5, 4.2, 6.8]],
     [[2.0, 2.7, 5.3], [2.3, 3.0, 5.7], [3.7, 4.3, 7.0]]]

    >>> ncube, min_bound, max_bound = cube_frac2cart(fcube, [1.,0.,0.], [0.,2.,0.], [0.,0.,1.], min_voxels=30)
    >>> pprint(ncube.round(1).tolist())
    [[[1.0, 1.7, 4.3],
      [1.0, 1.7, 4.3],
      [1.3, 2.0, 4.7],
      [2.0, 2.7, 5.3],
      [2.7, 3.3, 6.0],
      [3.0, 3.7, 6.3]],
     [[1.2, 1.8, 4.5],
      [1.2, 1.8, 4.5],
      [1.5, 2.2, 4.8],
      [2.2, 2.8, 5.5],
      [2.8, 3.5, 6.2],
      [3.2, 3.8, 6.5]],
     [[1.8, 2.5, 5.2],
      [1.8, 2.5, 5.2],
      [2.2, 2.8, 5.5],
      [2.8, 3.5, 6.2],
      [3.5, 4.2, 6.8],
      [3.8, 4.5, 7.2]]]

    >>> ncube, min_bound, max_bound = cube_frac2cart(fcube, [1.,0.,0.], [0.,1.,0.], [0.,0.,2.], min_voxels=30)
    >>> pprint(ncube.round(1).tolist())
    [[[1.0, 1.0, 1.7, 3.0, 4.3, 5.0],
      [1.3, 1.3, 2.0, 3.3, 4.7, 5.3],
      [2.7, 2.7, 3.3, 4.7, 6.0, 6.7]],
     [[1.2, 1.2, 1.8, 3.2, 4.5, 5.2],
      [1.5, 1.5, 2.2, 3.5, 4.8, 5.5],
      [2.8, 2.8, 3.5, 4.8, 6.2, 6.8]],
     [[1.8, 1.8, 2.5, 3.8, 5.2, 5.8],
      [2.2, 2.2, 2.8, 4.2, 5.5, 6.2],
      [3.5, 3.5, 4.2, 5.5, 6.8, 7.5]]]

    >>> ncube, min_bound, max_bound = cube_frac2cart(fcube, [1.,0.,0.], [.7,.7,0.], [0.,0.,1.], min_voxels=30)
    >>> min_bound.tolist()
    [-0.85, -0.35, -0.5]
    >>> max_bound.tolist()
    [0.85, 0.35, 0.5]
    >>> pprint(ncube.round(1).tolist())
    [[[1.0, 1.7, 4.3], [nan, nan, nan]],
     [[1.1, 1.7, 4.4], [nan, nan, nan]],
     [[1.6, 2.3, 5.0], [2.0, 2.7, 5.3]],
     [[2.0, 2.7, 5.3], [2.5, 3.2, 5.8]],
     [[nan, nan, nan], [3.0, 3.7, 6.3]],
     [[nan, nan, nan], [nan, nan, nan]]]

    >>> ncube, min_bound, max_bound = cube_frac2cart(fcube, [2.,0.,0.], [0.,1.,0.], [0.,0.,1.], min_voxels=30, make_cubic=True)
    >>> min_bound.tolist()
    [-1.0, -0.5, -0.5]
    >>> max_bound.tolist()
    [1.0, 1.5, 1.5]
    >>> pprint(ncube.round(1).tolist())
    [[[1.0, 3.0, 5.0, nan],
      [2.0, 4.0, 6.0, nan],
      [3.0, 5.0, 7.0, nan],
      [nan, nan, nan, nan]],
     [[1.0, 3.0, 5.0, nan],
      [2.0, 4.0, 6.0, nan],
      [3.0, 5.0, 7.0, nan],
      [nan, nan, nan, nan]],
     [[1.5, 3.5, 5.5, nan],
      [2.5, 4.5, 6.5, nan],
      [3.5, 5.5, 7.5, nan],
      [nan, nan, nan, nan]],
     [[2.0, 4.0, 6.0, nan],
      [3.0, 5.0, 7.0, nan],
      [4.0, 6.0, 8.0, nan],
      [nan, nan, nan, nan]]]

   """
    cvalues = np.asarray(cvalues, dtype=float)

    min_voxels = min_voxels if min_voxels is not None else 1
    longest_side = max(cvalues.shape)
    if (min_voxels is not None) and (max_voxels is not None) and min_voxels > max_voxels:
        raise ValueError(
            "minimum dimension ({0}) must be less than or equal to maximum distance ({1})".format(min_voxels,
                                                                                                  max_voxels))
    if min_voxels is not None:
        longest_side = max(longest_side, int(min_voxels ** (1 / 3.)))
    if max_voxels is not None:
        longest_side = min(longest_side, int(max_voxels ** (1 / 3.)))

    # convert to numpy arrays
    origin = np.asarray([0, 0, 0], dtype=float)
    v1 = np.asarray(v1)
    v2 = np.asarray(v2)
    v3 = np.asarray(v3)

    # --------------
    # expand cube by one unit in all directions (for interpolation)
    cvalues = np.concatenate((np.array(cvalues[0], ndmin=3), cvalues, np.array(cvalues[-1], ndmin=3)), axis=0)
    start = np.transpose(np.array(cvalues[:, :, 0], ndmin=3), axes=[1, 2, 0])
    end = np.transpose(np.array(cvalues[:, :, -1], ndmin=3), axes=[1, 2, 0])
    cvalues = np.concatenate((start, cvalues, end), axis=2)
    start = np.transpose(np.array(cvalues[:, 0, :], ndmin=3), axes=[1, 0, 2])
    end = np.transpose(np.array(cvalues[:, -1, :], ndmin=3), axes=[1, 0, 2])
    cvalues = np.concatenate((start, cvalues, end), axis=1)
    # --------------

    # --------------
    # create fractional coordinate axes for cube
    f_axes = []
    for i, v in enumerate([v1, v2, v3]):
        step = 1. / (cvalues.shape[i] - 2.)
        ax = np.linspace(0, 1 + step, cvalues.shape[i]) - step / 2.
        f_axes.append(ax)
    # --------------

    # --------------
    # get bounding box for cartesian vectors and compute its volume and extents
    bbox_pts = np.asarray([origin, v1, v2, v3, v1 + v2, v1 + v3, v1 + v2 + v3, v2 + v3])
    hull = Delaunay(bbox_pts)
    bbox_x, bbox_y, bbox_z = bbox_pts.T
    xmin, xmax, ymin, ymax, zmin, zmax = (bbox_x.min(), bbox_x.max(), bbox_y.min(),
                                          bbox_y.max(), bbox_z.min(), bbox_z.max())  # l,r,bottom,top
    x_length = abs(xmin - xmax)
    y_length = abs(ymin - ymax)
    z_length = abs(zmin - zmax)
    if make_cubic:
        # min_bound, max_bound = min(xmin, ymin, zmin), max(xmax, ymax, zmin)
        max_length = max(x_length, y_length, z_length)
        xmax += max_length - (xmin + x_length)
        ymax += max_length - (ymin + y_length)
        zmax += max_length - (zmin + z_length)
        x_length = y_length = z_length = max_length

    # --------------

    # --------------
    # compute new cube size, in which the bounding box can fit
    xlen, ylen, zlen = 0, 0, 0
    while xlen * ylen * zlen < min_voxels:
        if x_length == max([x_length, y_length, z_length]):
            xlen = longest_side
            ylen = int(longest_side * y_length / float(x_length))
            zlen = int(longest_side * z_length / float(x_length))
        elif y_length == max([x_length, y_length, z_length]):
            ylen = longest_side
            xlen = int(longest_side * x_length / float(y_length))
            zlen = int(longest_side * z_length / float(y_length))
        else:
            zlen = longest_side
            xlen = int(longest_side * x_length / float(z_length))
            ylen = int(longest_side * y_length / float(z_length))
        longest_side += 1
    # --------------

    # --------------
    # create a new, initially empty cube
    new_array = np.full((xlen, ylen, zlen), bval if bval is not False else np.nan)
    # get the indexes for each voxel in cube
    xidx, yidx, zidx = np.meshgrid(range(new_array.shape[0]), range(new_array.shape[1]), range(new_array.shape[2]))
    xidx = xidx.flatten()
    yidx = yidx.flatten()
    zidx = zidx.flatten()
    xyzidx = np.concatenate((np.array(xidx, ndmin=2).T, np.array(yidx, ndmin=2).T, np.array(zidx, ndmin=2).T), axis=1)
    # --------------

    # --------------
    # get the cartesian coordinates for each voxel
    xyz = np.concatenate((np.array(xmin + (xyzidx[:, 0] * abs(xmin - xmax) / float(xlen)), ndmin=2).T,
                          np.array(ymin + (xyzidx[:, 1] * abs(ymin - ymax) / float(ylen)), ndmin=2).T,
                          np.array(zmin + (xyzidx[:, 2] * abs(zmin - zmax) / float(zlen)), ndmin=2).T), axis=1)
    # create a mask for filtering all cartesian coordinates which sit inside the bounding box
    inside_mask = hull.find_simplex(xyz) >= 0
    # --------------

    # --------------
    # for all coordinates inside the bounding box, get their equivalent fractional position and set interpolated value
    basis_transform = np.linalg.inv(np.transpose([v1, v2, v3]))
    uvw = np.einsum('...jk,...k->...j', basis_transform, xyz[inside_mask])
    mask_i, mask_j, mask_k = xyzidx[inside_mask][:, 0], xyzidx[inside_mask][:, 1], xyzidx[inside_mask][:, 2]
    new_array[mask_i, mask_j, mask_k] = interpn(f_axes, cvalues, uvw, bounds_error=True, method=interp)
    # --------------

    mins = np.array((xmin, ymin, zmin)) - 0.5 * (v1 + v2 + v3) + np.array(centre)
    maxes = np.array((xmax, ymax, zmax)) - 0.5 * (v1 + v2 + v3) + np.array(centre)
    return new_array, mins, maxes
Пример #29
0
    (dcoords, cv_icoords, cv_xcoords, icoords, xcoords, ccoord))
point_type = np.concatenate(
    (0 * np.ones(dcoords.shape[0]), 1 * np.ones(cv_icoords.shape[0]),
     2 * np.ones(cv_xcoords.shape[0]), 3 * np.ones(icoords.shape[0]),
     4 * np.ones(xcoords.shape[0]), 5 * np.ones(ccoord.shape[0])))

tcoords = np.vstack((dcoords, cv_icoords, cv_xcoords, icoords, ccoord))
point_type = np.concatenate(
    (0 * np.ones(dcoords.shape[0]), 1 * np.ones(cv_icoords.shape[0]),
     2 * np.ones(cv_xcoords.shape[0]), 3 * np.ones(icoords.shape[0]),
     5 * np.ones(ccoord.shape[0])))

tcoords = np.vstack((dcoords, ccoord))
point_type = np.concatenate(
    (0 * np.ones(dcoords.shape[0]), 5 * np.ones(ccoord.shape[0])))

trian = Delaunay(tcoords)

df = pd.DataFrame(point_type[trian.simplices])
df.to_excel("delaunay.xlsx", index=False)
modified_simplices = trian.simplices[np.sum(
    trian.simplices == trian.simplices.max(), axis=1) == 1]
a = tl.DelaunaySingle(trian.points, trian.simplices, point_type, 2)
#a = tl.DelaunaySingle(trian.points, trian.simplices,point_type,2)
# import pdb; pdb.set_trace()
#ncoords = np.vstack((dcoords, extra_coords_internal, extra_coords_external))

# a = tl.DelaunaySingle(, good_tetra, neighbors, x)
# # #perfoming delaunay triangulation
# # trian = Delaunay(dcoords)
Пример #30
0
def test_meshing():
    """
    meshing example
    demonstrates the use of multiplicity, and group.median
    """
    #set up some random points, and get their delaunay triangulation
    points = np.random.random((20000,2))*2-1
    points = points[np.linalg.norm(points,axis=1) < 1]
    from scipy.spatial.qhull import Delaunay
    d = Delaunay(points)
    tris = d.simplices

    #the operations provided in this module allow us to express potentially complex
    #computational geometry questions elegantly in numpy
    #Delaunay.neighbors could be used as well,
    #but the point is to express this in pure numpy, without additional library functionaoty
    edges = tris[:,[[0,1],[1,2],[2,0]]].reshape(-1,2)
    sorted_edges = np.where(edges[:,0:1]<edges[:,1:2], edges, edges[:,::-1])
    #we test to see how often each edge occurs, or how many indicent simplices it has
    #this is a very general method of finding the boundary of any topology
    #and we can do so here with only one simple and readable command, multiplicity == 1
    if backwards_compatible:
        boundary_edges = edges[multiplicity(sorted_edges, axis=0)==1]
    else:
        boundary_edges = edges[multiplicity(sorted_edges)==1]
    boundary_points = unique(boundary_edges)

    if False:
        print (boundary_edges)
        print (incidence(boundary_edges))


    #create some random values on faces
    #we want to smooth them over the mesh to create a nice hilly landscape
    face_values   = np.random.normal(size=d.nsimplex)
    #add some salt and pepper noise, to make our problem more interesting
    face_values[np.random.randint(d.nsimplex, size=10)] += 1000

    #start with a median step, to remove salt-and-pepper noise
    #toggle to mean to see the effect of the median filter
    g = group_by(tris.flatten())
    prestep = g.median if True else g.mean
    vertex_values = prestep(np.repeat(face_values, 3))[1]
    vertex_values[boundary_points] = 0

    #actually, we can compute the mean without grouping
    tris_per_vert = g.count
    def scatter(x):
        r = np.zeros(d.npoints, x.dtype)
        for idx in tris.T: np.add.at(r, idx, x)
        return r / tris_per_vert
    def gather(x):
        return x[tris].mean(axis=1)

    #iterate a little
    for i in range(100):
        face_values   = gather(vertex_values)
        vertex_values = scatter(face_values)
        vertex_values[boundary_points] = 0


    #display our nicely rolling hills and their boundary
    import matplotlib.pyplot as plt
    x, y = points.T
    plt.tripcolor(x,y, triangles = tris, facecolors = face_values)
    plt.scatter(x[boundary_points], y[boundary_points])
    plt.xlim(-1,1)
    plt.ylim(-1,1)
    plt.axis('equal')
    plt.show()