Beispiel #1
0
def _disperse_within_boundary(nodes,
                              vert,
                              smp,
                              rho=None,
                              pinned_nodes=None,
                              m=None,
                              delta=0.1,
                              bound_force=False):
    '''
  Returns `nodes` after beingly slightly dispersed within the
  boundaries defined by `vert` and `smp`. The disperson is analogous
  to electrostatic repulsion, where neighboring node exert a repulsive
  force on eachother. If a node is repelled into a boundary then it
  bounces back in.
  '''
    if bound_force:
        bound_vert, bound_smp = vert, smp
    else:
        bound_vert, bound_smp = None, None

    # node positions after repulsion
    out = _disperse(nodes,
                    rho=rho,
                    pinned_nodes=pinned_nodes,
                    m=m,
                    delta=delta,
                    vert=bound_vert,
                    smp=bound_smp)
    # boolean array of nodes which are now outside the domain
    crossed = intersection_count(nodes, out, vert, smp) > 0
    # point where nodes intersected the boundary
    inter = intersection_point(nodes[crossed], out[crossed], vert, smp)
    # normal vector to intersection point
    norms = intersection_normal(nodes[crossed], out[crossed], vert, smp)
    # distance that the node wanted to travel beyond the boundary
    res = out[crossed] - inter
    # bouce node off the boundary
    out[crossed] -= 2 * norms * np.sum(res * norms, 1)[:, None]
    # check to see if the bounced nodes still intersect the boundary. If
    # not then set the bounced nodes back to their original position
    crossed = intersection_count(nodes, out, vert, smp) > 0
    out[crossed] = nodes[crossed]
    return out
Beispiel #2
0
def _has_intersections(c, x, vert, smp):
    ''' 
  Check if any of the edges (`c`, `x[i]`) intersect the boundary 
  defined by `vert` and `smp`. 
  '''
    N = len(x)
    cext = np.repeat(c[None, :], N, axis=0)
    # number of times each edge intersects the boundary
    count = intersection_count(x, cext, vert, smp)
    # return True if there are intersections
    out = np.any(count > 0)
    return out
Beispiel #3
0
def _has_edge_intersections(c, x, vert, smp, check_all_edges):
    """ 
  Check if any of the edges (*c*,*x[i]*) intersect the boundary 
  defined by *vert* and *smp*. If *check_all_edges* is True then the 
  edges (*x[i]*,*x[j]*) are also tested.
  """
    N = len(x)
    cext = np.repeat(c[None, :], N, axis=0)
    # number of times each edge intersects the boundary
    count = intersection_count(x, cext, vert, smp)
    # return True if there are no intersections
    intersects = np.any(count > 0)
    if ~intersects & check_all_edges:
        a, b = [], []
        for i, j in combinations(range(N), 2):
            a += [i]
            b += [j]

        # number of times each edge intersects the boundary
        count = intersection_count(x[a], x[b], vert, smp)
        # return True if there are no intersections
        intersects = np.any(count > 0)

    return intersects
Beispiel #4
0
def _snap_to_boundary(nodes, vert, smp, delta=1.0):
    '''
  Snaps nodes to the boundary defined by `vert` and `smp`. This is
  done by slightly shifting each node along the basis directions and
  checking to see if that caused a boundary intersection. If so, then
  the intersecting node is reset at the point of intersection.

  Returns
  -------
  (N, D) float array
    New nodes positions.

  (N, D) int array
    Index of the simplex that each node is on. If a node is not on a
    simplex (i.e. it is an interior node) then the simplex index is
    -1.

  '''
    n, dim = nodes.shape
    # find the distance to the nearest node
    dx = _neighbors(nodes, 2)[1][:, 1]
    # allocate output arrays
    out_smpid = np.full(n, -1, dtype=int)
    out_nodes = np.array(nodes, copy=True)
    min_dist = np.full(n, np.inf, dtype=float)
    for i in range(dim):
        for sign in [-1.0, 1.0]:
            # `pert_nodes` is `nodes` shifted slightly along dimension `i`
            pert_nodes = np.array(nodes, copy=True)
            pert_nodes[:, i] += delta * sign * dx
            # find which segments intersect the boundary
            idx, = (intersection_count(nodes, pert_nodes, vert, smp) >
                    0).nonzero()
            # find the intersection points
            pnt = intersection_point(nodes[idx], pert_nodes[idx], vert, smp)
            # find the distance between `nodes` and the intersection point
            dist = np.linalg.norm(nodes[idx] - pnt, axis=1)
            # only snap nodes which have an intersection point that is
            # closer than any of their previously found intersection points
            snap = dist < min_dist[idx]
            snap_idx = idx[snap]
            out_smpid[snap_idx] = intersection_index(nodes[snap_idx],
                                                     pert_nodes[snap_idx],
                                                     vert, smp)
            out_nodes[snap_idx] = pnt[snap]
            min_dist[snap_idx] = dist[snap]

    return out_nodes, out_smpid
Beispiel #5
0
def disperse(nodes,
             vert=None,
             smp=None,
             rho=None,
             fix_nodes=None,
             m=None,
             delta=0.1,
             bound_force=False):
    '''   
  Returns *nodes* after beingly slightly dispersed. The disperson is 
  analogous to electrostatic repulsion, where neighboring node exert a 
  repulsive force on eachother. The repulsive force for each node is 
  constant, by default, but it can vary spatially by specifying *rho*. 
  If a node intersects the boundary defined by *vert* and *smp* then 
  it will bounce off the boundary elastically. This ensures that no 
  nodes will leave the domain, assuming the domain is closed and all 
  nodes are initially inside. Using the electrostatic analogy, this 
  function returns the nodes after a single *time step*, and greater 
  amounts of dispersion can be attained by calling this function 
  iteratively.
  
  Parameters
  ----------
  nodes : (N,D) float array
    Node positions.

  vert : (P,D) array, optional
    Boundary vertices.

  smp : (Q,D) array, optional
    Describes how the vertices are connected to form the boundary. 
    
  rho : function, optional
    Node density function. Takes a (N,D) array of coordinates in D 
    dimensional space and returns an (N,) array of densities which 
    have been normalized so that the maximum density in the domain 
    is 1.0. This function will still work if the maximum value is 
    normalized to something less than 1.0; however it will be less 
    efficient.

  fix_nodes : (F,D) array, optional
    Nodes which do not move and only provide a repulsion force.

  m : int, optional
    Number of neighboring nodes to use when calculating the repulsion 
    force. When *m* is small, the equilibrium state tends to be a 
    uniform node distribution (regardless of *rho*), when *m* is 
    large, nodes tend to get pushed up against the boundaries.

  delta : float, optional
    Scaling factor for the node step size. The step size is equal to 
    *delta* times the distance to the nearest neighbor.

  bound_force : bool, optional
    If True, then nodes cannot repel other nodes through the domain 
    boundary. Set to True if the domain has edges that nearly touch 
    eachother. Setting this to True may significantly increase 
    computation time.
    
  Returns
  -------
  out : (N,D) float array
    Nodes after being dispersed.
    
  '''
    nodes = np.asarray(nodes, dtype=float)
    if vert is None:
        vert = np.zeros((0, nodes.shape[1]), dtype=float)
    else:
        vert = np.asarray(vert, dtype=float)

    if smp is None:
        smp = np.zeros((0, nodes.shape[1]), dtype=int)
    else:
        smp = np.asarray(smp, dtype=int)

    if bound_force:
        bound_vert, bound_smp = vert, smp
    else:
        bound_vert, bound_smp = None, None

    if rho is None:

        def rho(p):
            return np.ones(p.shape[0])

    if fix_nodes is None:
        fix_nodes = np.zeros((0, nodes.shape[1]), dtype=float)
    else:
        fix_nodes = np.asarray(fix_nodes, dtype=float)

    if m is None:
        # number of neighbors defaults to 3 raised to the number of
        # spatial dimensions
        m = 3**nodes.shape[1]

    # ensure that the number of nodes used to determine repulsion force
    # is less than or equal to the total number of nodes
    m = min(m, nodes.shape[0] + fix_nodes.shape[0])
    # node positions after repulsion
    out = _disperse(nodes, fix_nodes, rho, m, delta, bound_vert, bound_smp)
    # boolean array of nodes which are now outside the domain
    crossed = intersection_count(nodes, out, vert, smp) > 0
    # point where nodes intersected the boundary
    inter = intersection_point(nodes[crossed], out[crossed], vert, smp)
    # normal vector to intersection point
    norms = intersection_normal(nodes[crossed], out[crossed], vert, smp)
    # distance that the node wanted to travel beyond the boundary
    res = out[crossed] - inter
    # bouce node off the boundary
    out[crossed] -= 2 * norms * np.sum(res * norms, 1)[:, None]
    # check to see if the bounced nodes still intersect the boundary. If
    # not then set the bounced nodes back to their original position
    crossed = intersection_count(nodes, out, vert, smp) > 0
    out[crossed] = nodes[crossed]
    return out
Beispiel #6
0
def snap_to_boundary(nodes, vert, smp, delta=1.0):
    ''' 
  Snaps nodes to the boundary defined by *vert* and *smp*. This is 
  done by slightly shifting each node along the basis directions and 
  checking to see if that caused a boundary intersection. If so, then 
  the intersecting node is reset at the point of intersection.

  Parameters
  ----------
  nodes : (N,D) float array
    Node positions.
  
  vert : (M,D) float array
    Vertices making up the boundary.
  
  smp : (P,D) int array
    Connectivity of the vertices to form the boundary. Each row 
    contains the indices of the vertices which form one simplex of the 
    boundary.
    
  delta : float, optional
    Controls the maximum snapping distance. The maximum snapping 
    distance for each node is *delta* times the distance to the 
    nearest neighbor. This defaults to 1.0.
  
  Returns
  -------
  out_nodes : (N,D) float array
    New nodes positions.
  
  out_smp : (N,D) int array
    Index of the simplex that each node is on. If a node is not on a 
    simplex (i.e. it is an interior node) then the simplex index is 
    -1.
    
  '''
    nodes = np.asarray(nodes, dtype=float)
    vert = np.asarray(vert, dtype=float)
    smp = np.asarray(smp, dtype=int)
    n, dim = nodes.shape
    # find the distance to the nearest node
    dx = neighbors(nodes, 2)[1][:, 1]
    # allocate output arrays
    out_smpid = np.full(n, -1, dtype=int)
    out_nodes = np.array(nodes, copy=True)
    min_dist = np.full(n, np.inf, dtype=float)
    for i in range(dim):
        for sign in [-1.0, 1.0]:
            # *pert_nodes* is *nodes* shifted slightly along dimension *i*
            pert_nodes = np.array(nodes, copy=True)
            pert_nodes[:, i] += delta * sign * dx
            # find which segments intersect the boundary
            idx, = (intersection_count(nodes, pert_nodes, vert, smp) >
                    0).nonzero()
            # find the intersection points
            pnt = intersection_point(nodes[idx], pert_nodes[idx], vert, smp)
            # find the distance between *nodes* and the intersection point
            dist = np.linalg.norm(nodes[idx] - pnt, axis=1)
            # only snap nodes which have an intersection point that is
            # closer than any of their previously found intersection points
            snap = dist < min_dist[idx]
            snap_idx = idx[snap]
            out_smpid[snap_idx] = intersection_index(nodes[snap_idx],
                                                     pert_nodes[snap_idx],
                                                     vert, smp)
            out_nodes[snap_idx] = pnt[snap]
            min_dist[snap_idx] = dist[snap]

    return out_nodes, out_smpid