# Path: rbf/utils.py
# def assert_shape(arr, shape, label='array'):
#     '''
#     Raises a ValueError if `arr` does not have the specified shape
# 
#     Parameters
#     ----------
#     arr : array-like
# 
#     shape : tuple
#         The shape requirement for `arr`. This can have `None` to indicate that
#         an axis can have any length. This can also have an Ellipsis to only
#         enforce the shape for the first and/or last dimensions of `arr`
# 
#     label : str
#         What to call `arr` in the error
# 
#     '''
#     if not _SHAPE_ASSERTIONS:
#         return
# 
#     if hasattr(arr, 'shape'):
#         # if `arr` has a shape attribute then use it rather than finding the
#         # shape with np.shape, which converts `arr` to a numpy array
#         arr_shape = arr.shape
#     else:
#         arr_shape = np.shape(arr)
# 
#     arr_ndim = len(arr_shape)
#     if Ellipsis in shape:
#         # If `shape` has an ellipsis then only the first and/or last dimensions
#         # will be checked
#         start_shape = shape[:shape.index(Ellipsis)]
#         end_shape = shape[shape.index(Ellipsis) + 1:]
#         start_ndim = len(start_shape)
#         end_ndim = len(end_shape)
#         if arr_ndim < (start_ndim + end_ndim):
#             raise ValueError(
#                 '%s is %d dimensional but it should have at least %d '
#                 'dimensions' % (label, arr_ndim, start_ndim + end_ndim))
# 
#         arr_start_shape = arr_shape[:start_ndim]
#         arr_end_shape = arr_shape[arr_ndim - end_ndim:]
#         for axis, (i, j) in enumerate(zip(arr_start_shape, start_shape)):
#             if j is None:
#                 continue
# 
#             if i != j:
#                 raise ValueError(
#                     'axis %d of %s has length %d but it should have length %d'
#                     % (axis, label, i, j))
# 
#         for axis, (i, j) in enumerate(zip(arr_end_shape, end_shape)):
#             if j is None:
#                 continue
# 
#             if i != j:
#                 raise ValueError(
#                     'axis %d of %s has length %d but it should have length %d'
#                     % (arr_ndim - end_ndim + axis, label, i, j))
# 
#     else:
#         ndim = len(shape)
#         if arr_ndim != ndim:
#             raise ValueError(
#                 '%s is %d dimensional but it should have %d dimensions'
#                 % (label, arr_ndim, ndim))
# 
#         for axis, (i, j) in enumerate(zip(arr_shape, shape)):
#             if j is None:
#                 continue
# 
#             if i != j:
#                 raise ValueError(
#                     'axis %d of %s has length %d but it should have length %d'
#                     % (axis, label, i, j))
# 
#     return
# 
# class KDTree(cKDTree):
#     '''
#     Same as `scipy.spatial.cKDTree`, except when calling `query` with `k=1`,
#     the output does not get squeezed to 1D. Also, an error will be raised if
#     `query` is called with `k` larger than the number of points in the tree.
#     '''
#     def query(self, x, k=1, **kwargs):
#         '''query the KD-tree for nearest neighbors'''
#         if k > self.n:
#             raise ValueError(
#                 'Cannot find the %s nearest points among a set of %s points'
#                 % (k, self.n))
# 
#         dist, indices = cKDTree.query(self, x, k=k, **kwargs)
#         if k == 1:
#             dist = dist[..., None]
#             indices = indices[..., None]
# 
#         return dist, indices
# 
# Path: rbf/pde/domain.py
# def as_domain(obj):
#     '''
#     Coerces the input to a `Domain` instance. The input can be a tuple
#     of vertices and simplices, or it can already be a `Domain`
#     instance. In the latter case, the object will be returned
#     unaltered.
#     '''
#     if issubclass(type(obj), Domain):
#         return obj
#     else:
#         return Domain(*obj)


import logging
import numpy as np
from scipy.sparse import csc_matrix
from scipy.sparse.csgraph import reverse_cuthill_mckee
from rbf.utils import assert_shape, KDTree
from rbf.pde.domain import as_domain
from rbf.pde.sampling import rejection_sampling, poisson_discs
        If `True`, then the vertices will be included in the output nodes. Each
        vertex will be assigned to the boundary group that its adjoining
        simplices are part of. If the simplices are in multiple groups, then
        the vertex will be assigned to the group containing the simplex that
        comes first in `smp`.

    orient_simplices : bool, optional
        If `False` then it is assumed that the simplices are already oriented
        such that their normal vectors point outward.

    Returns
    -------
    (m, d) float array
        Nodes positions

    dict
        The indices of nodes belonging to each group. There will always be a
        group called 'interior' containing the nodes that are not on the
        boundary. By default there is a group containing all the boundary nodes
        called 'boundary:all'. If `boundary_groups` was specified, then those
        groups will be included in this dictionary and their names will be
        given a 'boundary:' prefix. If `boundary_groups_with_ghosts` was
        specified then those groups of ghost nodes will be included in this
        dictionary and their names will be given a 'ghosts:' prefix.

    (n, d) float array
        Outward normal vectors for each node. If a node is not on the boundary
        then its corresponding row will contain NaNs.

    '''

# Path: rbf/utils.py
# def assert_shape(arr, shape, label='array'):
#     '''
#     Raises a ValueError if `arr` does not have the specified shape
# 
#     Parameters
#     ----------
#     arr : array-like
# 
#     shape : tuple
#         The shape requirement for `arr`. This can have `None` to indicate that
#         an axis can have any length. This can also have an Ellipsis to only
#         enforce the shape for the first and/or last dimensions of `arr`
# 
#     label : str
#         What to call `arr` in the error
# 
#     '''
#     if not _SHAPE_ASSERTIONS:
#         return
# 
#     if hasattr(arr, 'shape'):
#         # if `arr` has a shape attribute then use it rather than finding the
#         # shape with np.shape, which converts `arr` to a numpy array
#         arr_shape = arr.shape
#     else:
#         arr_shape = np.shape(arr)
# 
#     arr_ndim = len(arr_shape)
#     if Ellipsis in shape:
#         # If `shape` has an ellipsis then only the first and/or last dimensions
#         # will be checked
#         start_shape = shape[:shape.index(Ellipsis)]
#         end_shape = shape[shape.index(Ellipsis) + 1:]
#         start_ndim = len(start_shape)
#         end_ndim = len(end_shape)
#         if arr_ndim < (start_ndim + end_ndim):
#             raise ValueError(
#                 '%s is %d dimensional but it should have at least %d '
#                 'dimensions' % (label, arr_ndim, start_ndim + end_ndim))
# 
#         arr_start_shape = arr_shape[:start_ndim]
#         arr_end_shape = arr_shape[arr_ndim - end_ndim:]
#         for axis, (i, j) in enumerate(zip(arr_start_shape, start_shape)):
#             if j is None:
#                 continue
# 
#             if i != j:
#                 raise ValueError(
#                     'axis %d of %s has length %d but it should have length %d'
#                     % (axis, label, i, j))
# 
#         for axis, (i, j) in enumerate(zip(arr_end_shape, end_shape)):
#             if j is None:
#                 continue
# 
#             if i != j:
#                 raise ValueError(
#                     'axis %d of %s has length %d but it should have length %d'
#                     % (arr_ndim - end_ndim + axis, label, i, j))
# 
#     else:
#         ndim = len(shape)
#         if arr_ndim != ndim:
#             raise ValueError(
#                 '%s is %d dimensional but it should have %d dimensions'
#                 % (label, arr_ndim, ndim))
# 
#         for axis, (i, j) in enumerate(zip(arr_shape, shape)):
#             if j is None:
#                 continue
# 
#             if i != j:
#                 raise ValueError(
#                     'axis %d of %s has length %d but it should have length %d'
#                     % (axis, label, i, j))
# 
#     return
# 
# class KDTree(cKDTree):
#     '''
#     Same as `scipy.spatial.cKDTree`, except when calling `query` with `k=1`,
#     the output does not get squeezed to 1D. Also, an error will be raised if
#     `query` is called with `k` larger than the number of points in the tree.
#     '''
#     def query(self, x, k=1, **kwargs):
#         '''query the KD-tree for nearest neighbors'''
#         if k > self.n:
#             raise ValueError(
#                 'Cannot find the %s nearest points among a set of %s points'
#                 % (k, self.n))
# 
#         dist, indices = cKDTree.query(self, x, k=k, **kwargs)
#         if k == 1:
#             dist = dist[..., None]
#             indices = indices[..., None]
# 
#         return dist, indices
# 
# Path: rbf/pde/domain.py
# def as_domain(obj):
#     '''
#     Coerces the input to a `Domain` instance. The input can be a tuple
#     of vertices and simplices, or it can already be a `Domain`
#     instance. In the latter case, the object will be returned
#     unaltered.
#     '''
#     if issubclass(type(obj), Domain):
#         return obj
#     else:
#         return Domain(*obj)


# Path: rbf/pde/nodes.py
import logging
import numpy as np
from scipy.sparse import csc_matrix
from scipy.sparse.csgraph import reverse_cuthill_mckee
from rbf.utils import assert_shape, KDTree
from rbf.pde.domain import as_domain
from rbf.pde.sampling import rejection_sampling, poisson_discs

        If `True`, then the vertices will be included in the output nodes. Each
        vertex will be assigned to the boundary group that its adjoining
        simplices are part of. If the simplices are in multiple groups, then
        the vertex will be assigned to the group containing the simplex that
        comes first in `smp`.

    orient_simplices : bool, optional
        If `False` then it is assumed that the simplices are already oriented
        such that their normal vectors point outward.

    Returns
    -------
    (m, d) float array
        Nodes positions

    dict
        The indices of nodes belonging to each group. There will always be a
        group called 'interior' containing the nodes that are not on the
        boundary. By default there is a group containing all the boundary nodes
        called 'boundary:all'. If `boundary_groups` was specified, then those
        groups will be included in this dictionary and their names will be
        given a 'boundary:' prefix. If `boundary_groups_with_ghosts` was
        specified then those groups of ghost nodes will be included in this
        dictionary and their names will be given a 'ghosts:' prefix.

    (n, d) float array
        Outward normal vectors for each node. If a node is not on the boundary
        then its corresponding row will contain NaNs.

    '''

    domain = as_domain(domain)