Example #1
0
def _repel_bounce(free_nodes, vert, smp, rho, fix_nodes, itr, n, delta, max_bounces, bound_force):
    """ 
  nodes are repelled by eachother and bounce off boundaries
  """
    free_nodes = np.array(free_nodes, dtype=float, copy=True)

    # if bound_force then use the domain boundary as the force
    # boundary
    if bound_force:
        bound_vert = vert
        bound_smp = smp
    else:
        bound_vert = np.zeros((0, vert.shape[1]), dtype=float)
        bound_smp = np.zeros((0, vert.shape[1]), dtype=int)

    # this is used for the lengthscale of the domain
    scale = vert.ptp()

    # ensure that the number of nodes used to determine repulsion force
    # is less than or equal to the total number of nodes
    n = min(n, free_nodes.shape[0] + fix_nodes.shape[0])

    for k in range(itr):
        # node positions after repulsion
        free_nodes_new = _repel_step(free_nodes, rho, fix_nodes, n, delta, bound_vert, bound_smp)

        # boolean array of nodes which are now outside the domain
        crossed = ~gm.contains(free_nodes_new, vert, smp)
        bounces = 0
        while np.any(crossed):
            # point where nodes intersected the boundary
            inter = gm.intersection_point(free_nodes[crossed], free_nodes_new[crossed], vert, smp)

            # normal vector to intersection point
            norms = gm.intersection_normal(free_nodes[crossed], free_nodes_new[crossed], vert, smp)

            # distance that the node wanted to travel beyond the boundary
            res = free_nodes_new[crossed] - inter

            # move the previous node position to just within the boundary
            free_nodes[crossed] = inter - 1e-10 * scale * norms

            # 3 is the number of bounces allowed
            if bounces > max_bounces:
                free_nodes_new[crossed] = inter - 1e-10 * scale * norms
                break

            else:
                # bouce node off the boundary
                free_nodes_new[crossed] -= 2 * norms * np.sum(res * norms, 1)[:, None]
                # check to see if the bounced node is now within the domain,
                # if not then iterations continue
                crossed = ~gm.contains(free_nodes_new, vert, smp)
                bounces += 1

        free_nodes = free_nodes_new

    return free_nodes
Example #2
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
Example #3
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
Example #4
0
def _repel_bounce(free_nodes,vert,smp,rho,   
                  fix_nodes,itr,n,delta,
                  max_bounces,bound_force):
  ''' 
  nodes are repelled by eachother and bounce off boundaries
  '''
  free_nodes = np.array(free_nodes,dtype=float,copy=True)

  # if bound_force then use the domain boundary as the force 
  # boundary
  if bound_force:
    bound_vert = vert
    bound_smp = smp
  else:
    bound_vert = np.zeros((0,vert.shape[1]),dtype=float)
    bound_smp = np.zeros((0,vert.shape[1]),dtype=int)

  # this is used for the lengthscale of the domain
  scale = vert.ptp()

  # ensure that the number of nodes used to determine repulsion force
  # is less than or equal to the total number of nodes
  n = min(n,free_nodes.shape[0]+fix_nodes.shape[0])

  for k in range(itr):
    # node positions after repulsion 
    free_nodes_new = _repel_step(free_nodes,rho,fix_nodes,
                                 n,delta,bound_vert,bound_smp)

    # boolean array of nodes which are now outside the domain
    crossed = ~gm.contains(free_nodes_new,vert,smp)
    bounces = 0
    while np.any(crossed):
      # point where nodes intersected the boundary
      inter = gm.intersection_point(
                free_nodes[crossed],     
                free_nodes_new[crossed],
                vert,smp)

      # normal vector to intersection point
      norms = gm.intersection_normal(
                free_nodes[crossed],     
                free_nodes_new[crossed],
                vert,smp)
      
      # distance that the node wanted to travel beyond the boundary
      res = free_nodes_new[crossed] - inter

      # move the previous node position to just within the boundary
      free_nodes[crossed] = inter - 1e-10*scale*norms

      # 3 is the number of bounces allowed   
      if bounces > max_bounces:
        free_nodes_new[crossed] = inter - 1e-10*scale*norms
        break

      else: 
        # bouce node off the boundary
        free_nodes_new[crossed] -= 2*norms*np.sum(res*norms,1)[:,None]        
        # check to see if the bounced node is now within the domain, 
        # if not then iterations continue
        crossed = ~gm.contains(free_nodes_new,vert,smp)
        bounces += 1

    free_nodes = free_nodes_new  

  return free_nodes
Example #5
0
def _repel_stick(free_nodes,vert,smp,rho,   
                 fix_nodes,itr,n,delta,
                 bound_force):
  ''' 
  nodes are repelled by eachother and then become fixed when they hit 
  a boundary
  '''
  free_nodes = np.array(free_nodes,dtype=float,copy=True)

  # if bound_force then use the domain boundary as the force 
  # boundary
  if bound_force:
    bound_vert = vert
    bound_smp = smp
  else:
    bound_vert = np.zeros((0,vert.shape[1]),dtype=float)
    bound_smp = np.zeros((0,vert.shape[1]),dtype=int)

  # Keeps track of whether nodes in the interior or boundary. -1 
  # indicates interior and >= 0 indicates boundary. If its on the 
  # boundary then the number is the index of the simplex that the node 
  # is on
  smpid = np.repeat(-1,free_nodes.shape[0])

  # length scale of the domain
  scale = vert.ptp()

  # ensure that the number of nodes used to compute repulsion force is
  # less than or equal to the total number of nodes
  n = min(n,free_nodes.shape[0]+fix_nodes.shape[0])

  for k in range(itr):
    # indices of all interior nodes
    interior, = (smpid==-1).nonzero()

    # indices of nodes associated with a simplex (i.e. nodes which 
    # intersected a boundary
    boundary, = (smpid>=0).nonzero()

    # nodes which are stationary 
    all_fix_nodes = np.vstack((fix_nodes,free_nodes[boundary]))

    # new position of free nodes
    free_nodes_new = np.array(free_nodes,copy=True)
    # shift positions of interior nodes
    free_nodes_new[interior] = _repel_step(free_nodes[interior],
                                 rho,all_fix_nodes,n,delta,
                                 bound_vert,bound_smp)

    # indices of free nodes which crossed a boundary
    crossed = ~gm.contains(free_nodes_new,vert,smp)
  
    # if a node intersected a boundary then associate it with a simplex
    smpid[crossed] = gm.intersection_index(
                       free_nodes[crossed],     
                       free_nodes_new[crossed], 
                       vert,smp)

    # outward normal vector at intesection points
    norms = gm.intersection_normal(
              free_nodes[crossed],     
              free_nodes_new[crossed], 
              vert,smp)

    # intersection point for nodes which crossed a boundary
    inter = gm.intersection_point(
              free_nodes[crossed],     
              free_nodes_new[crossed],
              vert,smp)

    # new position of nodes which crossed the boundary is just within
    # the intersection point
    free_nodes_new[crossed] = inter - 1e-10*scale*norms
    free_nodes = free_nodes_new

  return free_nodes,smpid
Example #6
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
Example #7
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
Example #8
0
def _repel_stick(free_nodes, vert, smp, rho, fix_nodes, itr, n, delta, bound_force):
    """ 
  nodes are repelled by eachother and then become fixed when they hit 
  a boundary
  """
    free_nodes = np.array(free_nodes, dtype=float, copy=True)

    # if bound_force then use the domain boundary as the force
    # boundary
    if bound_force:
        bound_vert = vert
        bound_smp = smp
    else:
        bound_vert = np.zeros((0, vert.shape[1]), dtype=float)
        bound_smp = np.zeros((0, vert.shape[1]), dtype=int)

    # Keeps track of whether nodes in the interior or boundary. -1
    # indicates interior and >= 0 indicates boundary. If its on the
    # boundary then the number is the index of the simplex that the node
    # is on
    smpid = np.repeat(-1, free_nodes.shape[0])

    # length scale of the domain
    scale = vert.ptp()

    # ensure that the number of nodes used to compute repulsion force is
    # less than or equal to the total number of nodes
    n = min(n, free_nodes.shape[0] + fix_nodes.shape[0])

    for k in range(itr):
        # indices of all interior nodes
        interior, = (smpid == -1).nonzero()

        # indices of nodes associated with a simplex (i.e. nodes which
        # intersected a boundary
        boundary, = (smpid >= 0).nonzero()

        # nodes which are stationary
        all_fix_nodes = np.vstack((fix_nodes, free_nodes[boundary]))

        # new position of free nodes
        free_nodes_new = np.array(free_nodes, copy=True)
        # shift positions of interior nodes
        free_nodes_new[interior] = _repel_step(
            free_nodes[interior], rho, all_fix_nodes, n, delta, bound_vert, bound_smp
        )

        # indices of free nodes which crossed a boundary
        crossed = ~gm.contains(free_nodes_new, vert, smp)

        # if a node intersected a boundary then associate it with a simplex
        smpid[crossed] = gm.intersection_index(free_nodes[crossed], free_nodes_new[crossed], vert, smp)

        # outward normal vector at intesection points
        norms = gm.intersection_normal(free_nodes[crossed], free_nodes_new[crossed], vert, smp)

        # intersection point for nodes which crossed a boundary
        inter = gm.intersection_point(free_nodes[crossed], free_nodes_new[crossed], vert, smp)

        # new position of nodes which crossed the boundary is just within
        # the intersection point
        free_nodes_new[crossed] = inter - 1e-10 * scale * norms
        free_nodes = free_nodes_new

    return free_nodes, smpid