pydfnWorks
python wrapper for dfnWorks
poisson_functions.py
Go to the documentation of this file.
1 # func.py
2 from pydfnworks.dfnGen.meshing.poisson_disc import poisson_class as pc
3 
4 from numpy import arange, array, ogrid, nonzero, zeros, append
5 from random import random, shuffle
6 from math import sqrt, floor, ceil, cos, sin, pi
7 from matplotlib import pyplot as plt
8 from scipy.sparse import lil_matrix
9 import timeit
10 import pickle
11 
12 #from memory_profiler import profile
13 
15 """
16 This file contains all functions called by main.py
17 The contained functions in order are:
18 
19 *called directly by main:
20  - main_init()
21  - intersect_sample()
22  - main_sample()
23  - search_undersampled_cells()
24  - dump_coordinates()
25  - plot_coordinates()
26  - print()
27 
28 *called by other functions:
29  - neighbor_cell()
30  - neighbor_grid_init()
31  - new_candidate()
32  - accept_candidate()
33  - exclusion_radius()
34  - intersect_distance()
35  - not_in_domain()
36  - neighboring_cells()
37  - read_vertices()
38  - read_intersections()
39  - boundary_sampling()
40  - sampling_along_line()
41  - intersect_cell()
42  - intersect_grid_init()
43  - intersect_mark_start_cells()
44  - intersect_direction()
45  - intersect_mark_next_cells()
46  - intersect_crossing_cell_wall()
47  - intersect_cell_sign()
48  - occupancy_cell()
49  - occupancy_undersampled()
50  - occupancy_grid_update()
51  - occupancy_mark()
52  - resample()
53  - lower_boundary()
54  - upper_boundary()
55  - distance()
56  - distance_sq()
57  - norm_sq()
58  - dot_product()
59 """
60 
61 
64 
65 
66 #@profile
67 def main_init(c): # polygons, intersections):
68  """ Reads inputs and initializes variables in c, i.e. initialized the polygon,
69  the intersections, samples along the boundary and initializes
70  neighbor-grid.
71 
72  Parameters
73  ---------
74  c : Poisson Disc Class
75  contains input parameters and widely used variables
76 
77  Returns
78  ---------
79  None
80 
81  Notes
82  -----
83 
84  """
85 
86  # initializes all geometry variables
87  c.vertices = read_vertices(c, c.path_poly)
88  c.intersect_endpts = read_intersections(c, c.path_inter)
89 
90  if c.well_flag:
91  well_pts = read_well_points(c)
92  for i in range(len(well_pts)):
93  c.intersect_endpts.append(well_pts[i])
94  #print(c.intersect_endpts)
95 
97  c.coordinates = boundary_sampling(c)
98  c.neighbor_grid = neighbor_grid_init(c)
99  c.no_of_nodes = len(c.coordinates)
100 
101 
102 
103 
104 
105 #@profile
106 def main_sample(c):
107  """ Runs over already accepted nodes and samples new candidates on an
108  annulus around them. valid candidates are added to c.coordinates
109  c.k candidates are sampled at once. If all k
110  are rejected, move on to next already accepted node. Terminate
111  after their are no new already accepted nodes.
112 
113 
114  Parameters
115  -----------
116  c : Poisson Disc Class
117  contains input parameters and widely used variables
118 
119  Returns
120  ---------
121  None
122 
123  Notes
124  -----
125  Proceeds from, where it terminated the previous time, if called
126  more than once.
127  """
128  while c.current_node < c.no_of_nodes: # sample around all accepted nodes
129  next_node = False
130  while not next_node:
131  next_node = True
132  for j in range(0, c.k): # sample k candidates around node
133  candidate = new_candidate(c, c.coordinates[c.current_node])
134  if accept_candidate(c, candidate):
135  c.no_of_nodes = c.no_of_nodes + 1
136  next_node = False # stay at node unless all k candidates are rejected
137  c.current_node = c.current_node + 1
138 
139 
140 
141 
142 
143 #@profile
145  """ Creates the occupancy-grid, searches for empty cells in it and
146  uniformly samples candidates in those empty cells. Accepted cells
147  are added to c.coordinates.
148 
149  Parameters
150  -----------
151  c : Poisson Disc Class
152  contains input parameters and widely used variables
153 
154  Returns
155  ---------
156  None
157 
158  Notes
159  -----
160  """
161 
162  undersampled_x, undersampled_y = occupancy_undersampled(c)
163  # indices of empty cells
164  no_of_undersampled = len(undersampled_x)
165  random_permutation = [m for m in range(0, no_of_undersampled)]
166  shuffle(random_permutation)
167  # go through empty cells in random order
168  # (due to the way empty cells defined, nodes sampled into
169  # empty cells only conflict with each other not any nodes
170  # sampled in main_sample)
171  while random_permutation:
172  random_integer = random_permutation.pop()
173  candidate = resample(c, undersampled_x[random_integer],
174  undersampled_y[random_integer])
175  if accept_candidate(c, candidate):
176  c.no_of_nodes = c.no_of_nodes + 1
177 
178 
179 
180 def dump_coordinates(c, output_file="points.xyz"):
181  """ Prints accepted coordinates to file
182 
183  Parameters
184  -----------
185  c : Poisson Disc Class
186  contains input parameters and widely used variables
187  output_file : string
188  coordinates will be printed to a file with this name
189 
190  Returns
191  ---------
192  None
193 
194  Notes
195  -----
196  None
197 
198  """
199 
200  col_format = "{:<30}" * 3 + "\n"
201  z = c.z_plane
202  with open(output_file, 'w') as file_o:
203  for element in c.coordinates:
204  file_o.write(col_format.format(*[element[0], element[1], z]))
205 
206 
207 
208 
209 
210 def plot_coordinates(c, output_file=""):
211  """ Plots accepted nodes to screen or file
212 
213  Parameters
214  -----------
215  c : Poisson Disc Class
216  contains input parameters and widely used variables
217  output_file : string
218  name of the file in which c.coordinated-plot will be saved.
219  c.coordinates are plotted to screen, if empty.
220 
221  Returns
222  ---------
223  None
224 
225  Notes
226  -----
227  creates the file "output_file", if this string is not empty.
228 
229  """
230 
231  xcoord = [x[0] for x in c.coordinates]
232  ycoord = [x[1] for x in c.coordinates]
233  plt.axis([
234  c.x_min - c.max_exclusion_radius, c.x_max + c.max_exclusion_radius,
235  c.y_min - c.max_exclusion_radius, c.y_max + c.max_exclusion_radius
236  ])
237  plt.scatter(xcoord, ycoord, s=.5)
238  if len(output_file) == 0:
239  plt.show()
240  else:
241  plt.savefig(output_file + 'png', dpi=150)
242  plt.close()
243 
244 
245 
247 
248 
249 def neighbor_cell(c, X):
250  """ Returns look up-Grid index of the point X
251 
252  Parameters
253  -----------
254  c : Poisson Disc Class
255  contains input parameters and widely used variables
256  X : ndarray(float)
257  2D coordinates of a point inside the neighbor-grid
258 
259  Returns
260  ---------
261  (x,y) : tuple(int,int)
262  horizontal and vertical neighbor-cell number
263 
264  Notes
265  -----
266 
267  """
268  x = floor((X[0] - c.x_min) * c.neighbor_cell_size_inv)
269  y = floor((X[1] - c.y_min) * c.neighbor_cell_size_inv)
270  return x, y
271 
272 
273 
274 
275 
277  """ Initializes background grid
278 
279  Parameters
280  ------------
281  c : Poisson Disc Class
282  contains input parameters and widely used variables
283 
284  Returns
285  ---------
286  neighbor_grid : ndarray(int)
287  array, where each components corresponds to a neighbor cell.
288  0, if corresponding cell is empty
289  i, if c.coordinate[i-1] occupies corresponding cell.
290 
291  Notes
292  -----
293 
294  """
295 
296  #!!!consider using sparse matrix instead to save space
297  #!!!slicing might be slower though
298 
299  c.no_horizontal_neighbor_cells = ceil(
300  (c.x_max - c.x_min) * c.neighbor_cell_size_inv)
301  c.no_vertical_neighbor_cells = ceil(
302  (c.y_max - c.y_min) * c.neighbor_cell_size_inv)
303  neighbor_grid = zeros((c.no_horizontal_neighbor_cells + 1,
304  c.no_vertical_neighbor_cells + 1)).astype(int)
305  for node_number in range(0, len(c.coordinates)):
306  neighbor_grid[neighbor_cell(
307  c, c.coordinates[node_number])] = node_number + 1
308  # every occupied cells is labelled with the node-number (start at 1)
309  # of the node occupying it. empty cells are 0.
310  return neighbor_grid
311 
312 
313 
315 
316 
317 def new_candidate(c, X):
318  """ Returns a random point in a annular neighborhood of X
319 
320  Parameters
321  ------------
322  c : Poisson Disc Class
323  contains input parameters and widely used variables
324  X : ndarray(float)
325  first two entries: x,y-coordinates of a already accepted node.
326  last entry: local exclusion_radius of that node.
327 
328  Returns
329  ---------
330  candidate : ndarray(float)
331  x,y- coordinates of a potential new node.
332 
333  Notes
334  -----
335 
336  """
337  radius = random() * c.max_exclusion_radius + X[2]
338  # last entry of an element of c.coordinates
339  # contains its local exclusion radius
340  # Note: setting radius to X[2]+epsilon gives denser samplings
341  # resulting in a per-node-speedup
342  angle = random() * pi * 2
343  candidate = X[0:2] + [radius * cos(angle), radius * sin(angle)]
344  return candidate
345 
346 
347 
348 
349 
350 def accept_candidate(c, candidate):
351  """ accepts a candidate p, if no conflicts with domain or already accepted nodes arise
352 
353  Parameters
354  ------------
355  c : Poisson Disc Class
356  contains input parameters and widely used variables
357  candidate : ndarray(float)
358  x,y-coordinates of a potential new node
359 
360  Returns
361  ---------
362  True/False : bool
363  True, if candidate is accepted as new node
364  False otherwise
365 
366  Notes
367  -----
368  If the candidate is accepted, it is added to c.coordinates
369  (including its local_exclusion_radius) and the neighbor grid
370  is updated.
371 
372  """
373 
374  # Checks if candidate is within rectangle defined by polygon
375  if candidate[0] < c.x_min:
376  return False
377  if candidate[0] > c.x_max:
378  return False
379  if candidate[1] < c.y_min:
380  return False
381  if candidate[1] > c.y_max:
382  return False
383  # neighbor_grid[neigbor_cell(...)] causes error if candidate
384  # is outside of rectangle bounded by x/y_min/max.
385 
386  # Checks if neighbor-cell is already occupied
387  candidates_neighbor_cell = neighbor_cell(c, candidate)
388  if c.neighbor_grid[candidates_neighbor_cell] != 0:
389  return False
390 
391  # Checks if p is within polygon
392  if not in_domain(c, candidate):
393  return False
394 
395  # Checks if any closeby points conflict
396  candidates_ex_rad = exclusion_radius(c, candidate)
397  candidates_ex_rad_sq = candidates_ex_rad**2
398  for node_number in neighboring_cells(c, candidates_neighbor_cell,
399  candidates_ex_rad):
400 
401  closeby_node = c.coordinates[node_number - 1]
402 
403  # last entry contains loc. ex_rad.
404  closeby_ex_rad_sq = closeby_node[2]**2
405  if distance_sq(candidate, closeby_node) < min(candidates_ex_rad_sq,
406  closeby_ex_rad_sq):
407  return False
408 
409  # Appends candidate and its loc. ex-rad to accepted nodes and updates
410  # neighbor-cells
411  c.coordinates.append(append(candidate, candidates_ex_rad))
412  c.neighbor_grid[candidates_neighbor_cell] = c.no_of_nodes + 1
413  return True
414 
415 
416 
417 
418 
420  """ returns the local min-distance of particle X
421 
422  Parameters
423  ------------
424  c : Poisson Disc Class
425  contains input parameters and widely used variables
426  X : ndarray(float)
427  first two entries: x,y-coordinates of a node
428 
429  Returns
430  ---------
431  local_exclusion_radius : float
432  exclusion radius at point X
433  Notes
434  -----
435  X can have more than 2 entries. Anything, but the first
436  two will be ignored
437 
438  """
439 
440  try:
441  closeby_intersections = c.intersect_cells[intersect_cell(c, X)]
442  # if accessing c.intersect_cells raises a KeyError nothing was
443  # assigned to the key intersect_cell(c,X) i.e. no intersection is
444  # close enough to influence the exclusion radius at X
445  # otherwise a list of intersection numbers is returned.
446  closest_intersect_distance_sq = intersect_distance_sq(
447  c, X, closeby_intersections)
448  if closest_intersect_distance_sq >= c.intersect_range_sq:
449  local_exclusion_radius = c.max_exclusion_radius
450  return local_exclusion_radius
451  else:
452  D = sqrt(closest_intersect_distance_sq)
453  # delaying this sqrt till here, means many cases didn't pass the
454  # if-statements reducing the overall times a sqrt is calculated
455  local_exclusion_radius = max(c.A * (D - c.F * c.H) + .5 * c.H,
456  .5 * c.H)
457  return local_exclusion_radius
458  except KeyError:
459  local_exclusion_radius = c.max_exclusion_radius
460  return local_exclusion_radius
461 
462 
463 
464 
465 
466 def intersect_distance_sq(c, X, closeby_intersections):
467  """ returns square distance to closest intersection
468 
469  Parameters
470  -----------
471  c : Poisson Disc Class
472  contains input parameters and widely used variables
473  X : ndarray(float)
474  x,y-coordinates of a node
475  closeby_intersections : list(int)
476  numbers of intersections, that pass X in a distance of 2*(H*(R+F)) or less
477 
478  Returns
479  ---------
480  suqare_dist : float
481  square of the distance to the closest intersection
482  of 'inf', if no intersection is closeby.
483 
484  Notes
485  -----
486  """
487  possible_output = [] # min of this list will be output
488  for i in closeby_intersections:
489  intersect_start = c.intersect_endpts[2 * i]
490  intersect_end = c.intersect_endpts[2 * i + 1]
491  projection_on_intersection = dot_product(
492  X - intersect_start, intersect_end - intersect_start)
493  length_of_intersection_sq = distance_sq(intersect_end, intersect_start)
494  if projection_on_intersection <= 0 or projection_on_intersection >= length_of_intersection_sq:
495  # If one of the endpoints is closest to X
496  possible_output = possible_output + \
497  [distance_sq(intersect_start, X), distance_sq(intersect_end, X)]
498  # put distances to either end point on poss.output list
499  else:
500  dist_to_line_sq = norm_sq((X - intersect_start) -
501  (intersect_end - intersect_start) *
502  projection_on_intersection /
503  (length_of_intersection_sq))
504  # square distance from point to infinite line defined by start and
505  # end point
506  possible_output.append(dist_to_line_sq)
507 
508  square_dist = min(possible_output)
509  return square_dist
510 
511 
512 
513 
514 
515 def in_domain(c, X):
516  """ Tests if the node X is within the polyon defined by c.vertices.
517 
518  Parameters
519  -----------
520  c : Poisson Disc Class
521  contains input parameters and widely used variables
522  X : ndarray(float)
523  x,y-coordinates of a node
524 
525  Returns
526  ---------
527  True/False : bool
528  False if X lies within the polygon
529  True otherwise
530  Notes
531  -----
532 
533 
534 
535  """
536  # Checks if below lower bounding function
537  i = c.last_x_min_index # start at index of the vertex of the polygon
538  # with the smalles y-value out of the vertices
539  # with x-value x_min
540  while c.vertices_x[i] < c.x_max:
541  # Go through the x-coordinates of the vertices along the lower
542  # boundary to see between, which two the x-value of X lies
543  # Remember vertices are ordered clockwise
544  if X[0] >= c.vertices_x[i] and X[0] <= c.vertices_x[(i + 1) %
545  c.no_of_vertices]:
546  if X[1] < c.vertices_y[i] + \
547  c.slope_lower_boundary[i] * (X[0] - c.vertices_x[i]):
548  # See if the y-value of X lies below the lower boundary
549  # Since the boundary is piecewise linear, it was enough
550  # to find the two vertices in the previous step
551  return False
552  else:
553  break
554  i = (i + 1) % c.no_of_vertices
555 
556  # Checks if above upper bounding function
557  i = c.first_x_min_index # start at index of vertex of polygon
558  # with highest y-value out of the vertices with
559  # x-value x_min
560  while c.vertices_x[i] < c.x_max:
561  # Go through the x-coordinates of the vertices along the upper
562  # boundary to see between, which two the x-value of X lies
563  # Remember vertices are ordered clockwise
564  if X[0] >= c.vertices_x[i] and X[0] <= c.vertices_x[(i - 1) %
565  c.no_of_vertices]:
566  if X[1] > c.vertices_y[i] + \
567  c.slope_upper_boundary[i] * (X[0] - c.vertices_x[i]):
568  # See if the y-value of X lies above the upper boundary
569  # Since the boundary is piecewise linear, it was enough
570  # to find the two vertices in the previous step
571  return False
572  else:
573  return True
574  i = (i - 1) % c.no_of_vertices
575 
576 
577 
578 
579 
580 def neighboring_cells(c, center_cell, exclusion_radius):
581  """ Returns the coordinate number of all non-empty cells neighboring
582  the input-index
583 
584  Parameters
585  ------------
586  c : Poisson Disc Class
587  contains input parameters and widely used variables
588  center_cell : ndarray(int)
589  neighbor-cell index (x,y) of candidate
590  exclusion_radius : float
591  local exclusion radius of candidate
592 
593  Returns
594  ---------
595  closeby_nodes : list(int)
596  node numbers of all close by nodes
597 
598  Notes
599  -----
600 
601  """
602  max_cell_distance = ceil(exclusion_radius * c.neighbor_cell_size_inv)
603  # furthest number of cells a cell still containing a conflicting node
604  # could be away in x or y-direction
605  subgrid = c.neighbor_grid[max(center_cell[0] - max_cell_distance, 0
606  ):min(center_cell[0] + max_cell_distance +
607  1, c.no_horizontal_neighbor_cells + 1),
608  max(center_cell[1] - max_cell_distance, 0
609  ):min(center_cell[1] + max_cell_distance +
610  1, c.no_vertical_neighbor_cells + 1)]
611  # slice neighboring cells out of grid
612  #sub=subgrid#.toarray()
613  closeby_nodes = subgrid[nonzero(subgrid)]
614 
615  return closeby_nodes
616 
617 
618 
621 def read_vertices(c, path_to_polygon):
622  """ Reads polygon vertices from file, initializes all variables related to
623  geometry of polygon (z_plane, x_min, x_max,y_min, y_max, lower and upper slope of polygon, ...)
624 
625  Parameters
626  ------------
627  c : Poisson Disc Class
628  contains input parameters and widely used variables
629  path_to_polygon : string
630  file containing coordinates of vertices
631 
632  Returns
633  ---------
634  vertices : list(ndarray(float))
635  list of coordinates of the polygon vertices
636 
637  Notes
638  -----
639 
640  """
641  with open(path_to_polygon) as inputfile:
642  lines = inputfile.readlines()
643  c.no_of_vertices = int(lines[0].split()[0])
644  vertices = [
645  array([float(Y) for Y in X.split()[1:3]])
646  for X in lines[1:1 + c.no_of_vertices]
647  ]
648  c.z_plane = float((lines[1]).split()[3])
649  c.vertices_x = [X[0] for X in vertices]
650  c.vertices_y = [X[1] for X in vertices]
651  c.x_min, c.x_max = min(c.vertices_x), max(c.vertices_x)
652  c.y_min, c.y_max = min(c.vertices_y), max(c.vertices_y)
653 
654  # Initialises upper and lower bound-functions for domain check
655  c.first_x_min_index = c.vertices_x.index(c.x_min)
656  # index of the vertex with the highest y-value out of all
657  # vertices with x value x_min
658  c.last_x_min_index = c.first_x_min_index
659  # index of the vertex with lowest y-value out of all
660  # vertices with x value x_min
661  # using clockwise ordering of vertices to find those indices
662  while c.vertices_x[(c.first_x_min_index - 1) %
663  c.no_of_vertices] == c.vertices_x[c.first_x_min_index]:
664  c.first_x_min_index = (c.first_x_min_index - 1) % c.no_of_vertices
665  while c.vertices_x[(c.last_x_min_index + 1) %
666  c.no_of_vertices] == c.vertices_x[c.last_x_min_index]:
667  c.last_x_min_index = (c.last_x_min_index + 1) % c.no_of_vertices
668 
669  c.slope_lower_boundary = zeros(c.no_of_vertices)
670  c.slope_upper_boundary = zeros(c.no_of_vertices)
671  i = c.last_x_min_index
672  while c.vertices_x[i] < c.x_max:
673  c.slope_lower_boundary[i] = (c.vertices_y[
674  (i + 1) % c.no_of_vertices] - c.vertices_y[i]) / (c.vertices_x[
675  (i + 1) % c.no_of_vertices] - c.vertices_x[i])
676  i = (i + 1) % c.no_of_vertices
677  i = c.first_x_min_index
678  while c.vertices_x[i] < c.x_max:
679  c.slope_upper_boundary[i] = (c.vertices_y[
680  (i - 1) % c.no_of_vertices] - c.vertices_y[i]) / (c.vertices_x[
681  (i - 1) % c.no_of_vertices] - c.vertices_x[i])
682  i = (i - 1) % c.no_of_vertices
683  del lines
684  return vertices
685 
686 
687 
688 
689 
690 #@profile
691 def read_intersections(c, path_to_intersections):
692  """ reads Intersection endpoints from file
693 
694  Parameters
695  -----------
696  c : Poisson Disc Class
697  contains input parameters and widely used variables
698  path_to_intersections : string
699  file containing intersection points
700 
701  Returns
702  ---------
703  end_pts : list(ndarray(floats))
704  list of coordinates of start and end points of intersections
705  ordered by intersection: [start_1,end_1,start_2,end_2,...]
706 
707  Notes
708  -----
709  sets neighbor_grid width to max_exclusion_radius/sqrt(2), if there's
710  no intersections.
711 
712 
713  """
714  end_pts = []
715  #short edges require smaller closer nodes to guarantee good triangles
716  for i in range(0, c.no_of_vertices):
717  if distance_sq(c.vertices[i], c.vertices[
718  (i + 1) % c.no_of_vertices]) < c.max_exclusion_radius**2:
719  end_pts.append(c.vertices[i])
720  end_pts.append(c.vertices[(i + 1) % c.no_of_vertices])
721 
722  with open(path_to_intersections) as inputfile:
723  lines = inputfile.readlines()
724  if len(lines) != 0:
725  line_0 = lines[0].split()
726  no_of_pts = int(line_0[0])
727  no_of_lines = int(line_0[1])
728  no_of_intersections = no_of_pts - no_of_lines
729  intersect_labels = [
730  int(X.split()[2]) for X in lines[no_of_pts + no_of_lines + 4:]
731  ]
732  # .inp-file contains labels to which intersection a point belongs
733  # listed after points, line-commands and 4 lines of text.
734  first_line_intersect_j = 1
735  for j in range(0, no_of_intersections):
736  start_j = array(
737  [float(Y) for Y in lines[first_line_intersect_j].split()[1:3]])
738  # find first line that contains current label add it as start
739  end_pts.append(start_j)
740  last_line_intersect_j = no_of_pts - \
741  intersect_labels[::-1].index(intersect_labels[first_line_intersect_j])
742  # look backwards through the file to find last line with current
743  # label
744  end_j = array(
745  [float(Y) for Y in lines[last_line_intersect_j].split()[1:3]])
746  # add it as end
747  end_pts.append(end_j)
748 
749  first_line_intersect_j = last_line_intersect_j + 1
750  # next line has different label
751  if end_pts == []:
752  c.neighbor_cell_size = c.max_exclusion_radius / sqrt(2)
753  c.neighbor_cell_size_inv = 1 / c.neighbor_cell_size
754  # if there's no intersections, the neighbor-cells can be defined
755  # via the max distance, saving time
756  del lines
757  return end_pts
758 
760 
761  pts = []
762 
763  with open("well_points.dat","r") as fwell:
764  fwell.readline() # ignore header (fracture_id, x, y, z)
765  for line in fwell.readlines():
766  if c.fracture_id == int(line.split()[0]):
767  pt = zeros(2)
768  pt[0] = float(line.split()[1])
769  pt[1] = float(line.split()[2])
770  pts.append(pt)
771  pt[0] += c.H
772  pt[1] += c.H
773  pts.append(pt)
774  del pt
775  return pts
776 
777 
780  """ Samples points around the boundary
781 
782  Parameters
783  ------------
784  c : Poisson Disc Class
785  contains input parameters and widely used variables
786 
787  Returns
788  ---------
789  boundary_points : list(ndarray(float))
790  coordinates of a poisson-disk sampling along
791  the boundary of the polygon.
792  Notes
793  -----
794 
795 
796 
797 
798  """
799 
800  # Could be written more efficient, but will only be called a few times
801  # anyway
802 
803  boundary_points = []
804  for i in range(0, c.no_of_vertices):
805  #sampling along all line segments that make up the boundary
806  prev_line = c.vertices[(i - 1) % c.no_of_vertices] - c.vertices[i]
807  current_line = c.vertices[i] - c.vertices[(i + 1) % c.no_of_vertices]
808  next_line = c.vertices[(i + 2) % c.no_of_vertices] - c.vertices[
809  (i + 1) % c.no_of_vertices]
810 
811  cos_start_angle = dot_product(prev_line, current_line) / sqrt(
812  norm_sq(prev_line) * norm_sq(current_line))
813  cos_end_angle = dot_product(current_line, next_line) / sqrt(
814  norm_sq(next_line) * norm_sq(current_line))
815 
816  r_start = max(exclusion_radius(c,
817  c.vertices[i]), (cos_start_angle > .5) *
818  c.H * .25 / sqrt(1 - cos_end_angle**2))
819  r_end = max(
820  exclusion_radius(c, c.vertices[(i + 1) % c.no_of_vertices]),
821  (cos_end_angle > .5) * c.H * .25 / sqrt(1 - cos_end_angle**2))
822  #if angle between sides of the polygon are smaller than 60deg
823  #the distance between points on the boundary needs to be considered
824  #if the angle is biggher than 60deg it is sufficient to guarantee
825  #appropriate distance to the vertices.
826 
827  if sqrt(norm_sq(current_line)) - r_start - r_end > 0:
828  #is there space for at least two samples on the boundary?
829  start = c.vertices[i] - r_start * \
830  current_line / sqrt(norm_sq(current_line))
831  end = c.vertices[(i + 1) % c.no_of_vertices] + \
832  r_end * current_line / sqrt(norm_sq(current_line))
833  if distance(start, end) > min(exclusion_radius(c, start),
834  exclusion_radius(c, end)):
835 
836  boundary_points = boundary_points + \
837  sampling_along_line(c, start, end)
838  else:
839  #If just two samples on the boundary are already too close
840  #pick random point between them instead
841  r = random() * (sqrt(norm_sq(current_line)) - r_start - r_end)
842  start = c.vertices[i] - (r_start + r) * \
843  current_line / sqrt(norm_sq(current_line))
844  boundary_points.append(
845  append(start, exclusion_radius(c, start)))
846  elif sqrt(norm_sq(current_line)) - r_start - r_end == 0:
847  #just enough space for one sample on the boundary?
848  start = c.vertices[i] - r_start * \
849  current_line / sqrt(norm_sq(current_line))
850  boundary_points.append(append(start, exclusion_radius(c, start)))
851  vertices = [append(v, exclusion_radius(c, v)) for v in c.vertices]
852  return vertices + boundary_points
853 
854 
855 
856 
857 
858 def sampling_along_line(c, x, y):
859  """ Samples points on a line with min distance r and max distance 1.3r
860  (Poisson disk sampling on a line)
861 
862  Parameters
863  ------------
864  c : Poisson Disc Class
865  contains input parameters and widely used variables
866  x,y : ndarray(float)
867  coordinates of start and end point of the line to be sampled
868 
869  Returns
870  ---------
871  line_sample : list(ndarray(float))
872  coordinates of a Poisson disk sampling along the line from x to y
873 
874  Notes
875  -----
876  choosing new points in distance between r and 1.3r is rather arbitrary.
877  However greater than r is necessary to maintain Poisson-disk sampling
878  and significantly greater distances of boundary nodes
879  decrease the quality of the triangulation.
880  """
881 
882  previous_sample = x
883  direction = (y - x) / distance(y, x)
884  ex_rad = exclusion_radius(c, previous_sample)
885  line_sample = [append(previous_sample, ex_rad)]
886  while distance(previous_sample, y) > 2.3 * ex_rad:
887  # distance to endpoint still large enough to use full range
888  increment = .3 * random() * ex_rad + ex_rad
889  previous_sample = previous_sample + direction * increment
890  ex_rad = exclusion_radius(c, previous_sample)
891  line_sample.append(append(previous_sample, ex_rad))
892  ex_rad = min(exclusion_radius(c, previous_sample), exclusion_radius(c, y))
893  while distance(previous_sample, y) >= 2 * ex_rad:
894  # place for another sample, but in more restricted area
895  increment = random() * (distance(previous_sample, y) -
896  2 * ex_rad) + ex_rad
897  previous_sample = previous_sample + direction * increment
898  ex_rad = min(exclusion_radius(c, previous_sample),
899  exclusion_radius(c, y))
900  line_sample.append(append(previous_sample, ex_rad))
901  line_sample.append(append(y, exclusion_radius(c, y)))
902  return line_sample
903 
904 
905 
907 
908 
909 def intersect_cell(c, X):
910  """ Returns Intersect-Grid index of the point X
911 
912  Parameters
913  ------------
914  c : Poisson Disc Class
915  contains input parameters and widely used variables
916  X : ndarray(float)
917  x,y-coordinates of the point C
918 
919  Returns
920  ---------
921  x,y : int
922  horizontal and vertical intersection_cell_index
923 
924  Notes
925  -----
926  """
927  x = floor((X[0] - c.x_min) * c.intersect_grid_inv)
928  y = floor((X[1] - c.y_min) * c.intersect_grid_inv)
929  return x, y
930 
931 
932 
933 
934 
935 #@profile
937  """ Initialize Intersect-Grid, looks at all intersections as
938  directed arrows from start to end and marks cell that are crossed by it and neighboring cells
939 
940  Parameters
941  ------------
942  c : Poisson Disc Class
943  contains input parameters and widely used variables
944 
945  Returns
946  ---------
947  None
948 
949  Notes
950  -----
951  The domain is split into square-cells of side length intersect_range.
952  The cells are numbered by integer indexes (i,j). This function add the
953  number (i,j):m to the dictionary c.intersect_cells if the m-th intersection
954  crosses the cell with index (i,j) are a neighboring cell.
955 
956  """
957  c.intersect_cells = {}
958  for intersect_number in range(0, int(len(c.intersect_endpts) / 2)):
959  Start, End = c.intersect_endpts[
960  2 * intersect_number], c.intersect_endpts[2 * intersect_number + 1]
961  current_intersect_cell = intersect_cell(c, Start)
962  final_intersect_cell = intersect_cell(c, End)
963  intersect_mark_start_cells(c, current_intersect_cell, intersect_number)
964  delta_x, delta_y = End[0] - Start[0], End[1] - Start[1]
965  if delta_x != 0:
966  y_intercept = Start[1] - Start[0] * delta_y / delta_x
967  else:
968  y_intercept = Start[0]
969  # if the intersection is parallel to the y-axis, its x-value is
970  # stored
971  directions = intersect_direction(c, delta_x, delta_y)
972  # Does intersection point north, west, northwest,... ?
973  while (current_intersect_cell[0] != final_intersect_cell[0]
974  or current_intersect_cell[1] != final_intersect_cell[1]):
975  for direction in directions:
976  if intersect_crossing_cell_wall(c, direction,
977  current_intersect_cell,
978  delta_x, delta_y, y_intercept):
979  current_intersect_cell = intersect_mark_next_cells(
980  c, direction, current_intersect_cell, intersect_number)
981  # next cell the intersection passes
982  break
983 
984 
985 
986 
987 
988 def intersect_mark_start_cells(c, center_cell, intersect_number):
989  """ Adds the intersection number to the center cell and all its 8 neighbor cells in
990  the dictionary c.intersect_cells. Marks 3x3 cells
991 
992  Parameters
993  ------------
994  c : Poisson Disc Class
995  contains input parameters and widely used variables
996  center_cell : ndarrya(int)
997  index-pair of an intersection-cell
998  intersect_number : int
999  contains the number of an intersection if the are enumerated starting at 1.
1000 
1001  Returns
1002  ---------
1003 
1004  Notes
1005  -----
1006  Adds the intersection number to the center cell and all its 8 neighbor cells in
1007  the dictionary c.intersect_cells
1008  """
1009 
1010  for j in range(0, 9): # loop through 3x3 grid around center cell
1011  try:
1012  c.intersect_cells[center_cell[0] - 1 +
1013  floor(j / 3), center_cell[1] - 1 +
1014  (j % 3)].append(intersect_number)
1015  # if key already contains a list append current intersection number
1016  # else exception is raised and a list is created for that key.
1017  except BaseException:
1018  c.intersect_cells[center_cell[0] - 1 +
1019  floor(j / 3), center_cell[1] - 1 +
1020  (j % 3)] = [intersect_number]
1021 
1022 
1023 
1024 
1025 
1026 def intersect_direction(c, delta_x, delta_y):
1027  """ Determines the direction and intersection is pointing if interpreted as an arrow from start to end.( right, left, up, down, combinations possible)
1028 
1029  Parameters
1030  ---------
1031  c : Poisson Disc Class
1032  contains input parameters and widely used variables
1033  delta_x/delta_y : float
1034  x/y distance between start and end point of an intersection
1035 
1036  Returns
1037  ---------
1038  directions : list(int)
1039  up to 2 numbers between 1 and 4 corresponding to the directions
1040  1-> right, 3->left, 2->up and 4->down.
1041 
1042  Notes
1043  -----
1044  This is used to determine which sides of a intersect-cell an intersection
1045  could cross.
1046 
1047  """
1048 
1049  directions = []
1050  if delta_x > 0:
1051  directions.append(1) # right
1052  elif delta_x < 0:
1053  directions.append(3) # left
1054  if delta_y > 0:
1055  directions.append(2) # up
1056  elif delta_y < 0:
1057  directions.append(4) # down
1058  return directions
1059 
1060 
1061 
1062 
1063 
1064 def intersect_mark_next_cells(c, direction, center_cell, intersect_number):
1065  """ Determines the next intersect-cell the current intersection is crossing
1066  and adds all neighboring cells of that cell to the dictionary
1067  c.intersect_cells.
1068 
1069  Parameters
1070  -----------
1071  c : Poisson Disc Class
1072  contains input parameters and widely used variables
1073  direction : list(int)
1074  integer between 1 and 4 corresponding to directions
1075  right,left,up,down (1,3,2,4)
1076  center_cell : ndarray(int)
1077  index-pair of an intersection-cell
1078  intersect_number : int
1079  number of an intersection if they were enumerated starting at 1
1080 
1081  Returns
1082  ---------
1083  new_center_cell : ndarray(int)
1084  index-pair of the most recent cell known to be crossed by the
1085  current intersection
1086 
1087  Notes
1088  -----
1089  5 of the 8 neighboring cells of new_center_cell are already added to
1090  c.intersect_cells, so it is sufficient to only add the remaining 3.
1091 
1092  """
1093 
1094  x_shift = (direction % 2) * (-1)**(int((direction - 1) / 2))
1095  y_shift = ((direction - 1) % 2) * (-1)**(int(direction / 2) + 1)
1096  # x_shift =+-1 for left/right movement, y_shift =+-1 for up/down movement
1097  if x_shift == 0:
1098  for j in range(0, 3):
1099  # label the 3 cells below/above the 3x3 grid around current cell
1100  try:
1101  c.intersect_cells[center_cell[0] - 1 + j, center_cell[1] +
1102  2 * y_shift].append(intersect_number)
1103  # if the key for any of those cells already contains a list the
1104  # current intersection number is added otherwise an exception is
1105  # raised an a list is created for that key instead
1106  except BaseException:
1107  c.intersect_cells[center_cell[0] - 1 + j, center_cell[1] +
1108  2 * y_shift] = [intersect_number]
1109  else:
1110  for j in range(0, 3):
1111  # label the 3 cells left/right of the the 3x3 grid around current
1112  # cell
1113  try:
1114  c.intersect_cells[center_cell[0] +
1115  2 * x_shift, center_cell[1] - 1 +
1116  j].append(intersect_number)
1117  # if the key for any of those cells already contains a list the
1118  # current intersection number is added otherwise an exception is
1119  # raised an a list is created for that key instead
1120  except BaseException:
1121  c.intersect_cells[center_cell[0] +
1122  2 * x_shift, center_cell[1] - 1 +
1123  j] = [intersect_number]
1124  new_center_cell = center_cell + array([x_shift, y_shift])
1125  return new_center_cell
1126 
1127 
1128 
1129 
1130 
1131 def intersect_crossing_cell_wall(c, direction, current_cell, delta_x, delta_y,
1132  y_intercept):
1133  """ Determines if intersection crosses cell in given direction
1134 
1135  Parameters
1136  -----------
1137  c : Poisson Disc Class
1138  contains input parameters and widely used variables
1139  direction : list(int)
1140  integer between 1 and 4 corresponding to the
1141  directions right,left,up,down (1,3,2,4)
1142  delta_x/delta_y : ndarray(float)
1143  x/y distance between start and end point of current intersection
1144  y_intercept : float
1145  y-value of the y-axis interception of the intersection if extended
1146  to an infinite line.
1147  x-value of the intersection, if it is parallel to the y-axis
1148 
1149  Returns
1150  ---------
1151  True/False : bool
1152  True if the two nodes of the current intersection-cell into the
1153  input direction are on different sides of the intersection
1154  (or if one of those nodes lies on the intersection), i.e. if the
1155  intersection crosses the side of the cell in that direction.
1156  False otherwise.
1157 
1158  Notes
1159  -----
1160 
1161 
1162  """
1163 
1164  cell_nodes_k = c.square_nodes[direction - 1:direction + 1, :]
1165  # c.square contains nodes of a unit square
1166  # we select the two nodes at the edge of the cell, that
1167  # bounds it in the direction the intersection is moving.
1168  square_node_1 = (array(current_cell) + cell_nodes_k[0, :]) * (c.R * c.H +
1169  c.F * c.H)
1170  square_node_2 = (array(current_cell) + cell_nodes_k[1, :]) * (c.R * c.H +
1171  c.F * c.H)
1172  # translates unit-square node into actual nodes of the cell
1173  return intersect_cell_sign(c, square_node_1 + array([c.x_min, c.y_min]),
1174  square_node_2 + array([c.x_min, c.y_min]),
1175  delta_x, delta_y, y_intercept)
1176 
1177 
1178 
1179 
1180 
1181 def intersect_cell_sign(c, X, Y, dx, dy, yshift):
1182  """ Check intersection cell sign. Returns True if X& Y are on the same side of the line determined by dx,dy and yshift
1183 
1184  Parameters
1185  -----------
1186  c : Poisson Disc Class
1187  contains input parameters and widely used variables
1188  X/Y : ndarray(float)
1189  coordinates of two points
1190  dx/dy : float
1191  x/y distance between start and end points of a line segment
1192  yshift : float
1193  y-intercept of an infinite line in 2d or x-value of line
1194  is parallel to y axis
1195  Returns
1196  ---------
1197  True/False : bool
1198  True, if X and Y are on the same side of the line determined by dx,dy,yshift
1199  or if one of the points lies on the line
1200  False otherwise.
1201  Notes
1202  -----
1203 
1204  """
1205  if dx != 0:
1206  sgn_x = dy * (X[0]) - dx * (X[1] - yshift)
1207  # right-hand side ==0 if X lies on intersection
1208  # <0 if above and >0 of below intersection
1209  sgn_y = dy * (Y[0]) - dx * (Y[1] - yshift)
1210  else:
1211  # if intersection is parallel to y-axis just check if
1212  # X, Y are left or right of it
1213  sgn_x = X[0] - yshift
1214  sgn_y = Y[0] - yshift
1215  if sgn_x == 0 or sgn_y == 0:
1216  return True
1217  if (sgn_x < 0 and sgn_y < 0) or (sgn_x > 0 and sgn_y > 0):
1218  return False
1219  else:
1220  return True
1221 
1222 
1223 
1225 
1226 
1227 def occupancy_cell(c, X):
1228  """ Returns Occupancy-Grid index of the point X
1229 
1230  Parameters
1231  ------------
1232  c : Poisson Disc Class
1233  contains input parameters and widely used variables
1234  X : ndarray(float)
1235  x,y-coordinats of a points
1236 
1237  Returns
1238  ---------
1239  x,y : int
1240  index-pair of the occupancy-grid-cell, in which X lies
1241 
1242  Notes
1243  -----
1244 
1245 
1246  """
1247  x = floor((X[0] - c.x_min) * c.occupancy_grid_side_length_inv)
1248  y = floor((X[1] - c.y_min) * c.occupancy_grid_side_length_inv)
1249  return x, y
1250 
1251 
1252 
1253 
1254 
1255 #@profile
1257  """ Determines and fills the occupancy grid and returns the indices of
1258  empty cells. A cell is considered occupied if the disk centered at any
1259  node with a radius of the local exclusion radius of that node overlaps
1260  with said cell.
1261 
1262  Parameters
1263  ------------
1264  c : Poisson Disc Class
1265  contains input parameters and widely used variables
1266 
1267  Returns
1268  ---------
1269  undersampled_cells : list(ndarray(int))
1270  indices of emtpy occupancy-grid-cells
1271  Notes
1272  -----
1273 
1274 
1275 
1276  """
1277  c.no_horizontal_occupancy_cells = ceil(
1278  (c.x_max - c.x_min) * c.occupancy_grid_side_length_inv)
1279  c.no_vertical_occupancy_cells = ceil(
1280  (c.y_max - c.y_min) * c.occupancy_grid_side_length_inv)
1281  c.occupancy_grid = zeros((c.no_horizontal_occupancy_cells + 1,
1282  c.no_vertical_occupancy_cells + 1)).astype(bool)
1283  c.occupancy_grid[:, -1] = True #c.occupancy_grid[:, -1] + 1
1284  c.occupancy_grid[:, 0] = True #c.occupancy_grid[:, 0] + 1
1285  c.occupancy_grid[-1, :] = True #c.occupancy_grid[-1, :] + 1
1286  c.occupancy_grid[0, :] = True #c.occupancy_grid[0, :] + 1
1287  # Boundary cells should are either occupied or outside of the domain
1288  # Takes care of rare cases, where x_max is not in the last boundary cell
1289  # (x_max a multiple of the grid size.)
1290 
1291  xs = arange(
1292  c.x_min, c.x_min +
1293  c.no_horizontal_occupancy_cells * c.occupancy_grid_side_length,
1294  .5 * c.occupancy_grid_side_length)
1295  # create array of x, values such that at least on falls in every column
1296  # of the grid
1297  for points in xs:
1298  try:
1299  boundary_cell = occupancy_cell(c, upper_boundary(c, points))
1300  except BaseException:
1301  print("error", points)
1302  if boundary_cell[0] < c.no_horizontal_occupancy_cells:
1303  # for every x-value find the two boundary-cells and mark
1304  # everything above/below, i.w. outside of the domain as
1305  # occupied, otherwise the algorithm tries to fill in
1306  # holes outside of the domain, which wastes a lot of time
1307  c.occupancy_grid[boundary_cell[0], (boundary_cell[1]):] = True #(
1308  #c.occupancy_grid[boundary_cell[0], (boundary_cell[1]):]) + 1
1309  boundary_cell = occupancy_cell(c, lower_boundary(c, points))
1310  (c.occupancy_grid[boundary_cell[0], :(
1311  boundary_cell[1])]) = True # (
1312  #c.occupancy_grid[boundary_cell[0], :(boundary_cell[1])]) + 1
1313  # marks cells around boundary points
1314  for i in range(0, len(c.coordinates)):
1315  occupancy_mark(c, c.coordinates[i])
1316  undersampled_cells = nonzero(c.occupancy_grid == 0)
1317  del c.occupancy_grid
1318  return undersampled_cells
1319 
1320 
1321 
1322 
1323 
1324 def occupancy_mark(c, node):
1325  """marks circular region around as occupied. Region is chosen such,
1326  that cells overlapping with a circle of radius r(C) around C are
1327  marked, hence Cells that are unmarked are guaranteed to be empty.
1328 
1329  Parameters
1330  ------------
1331  c : Poisson Disc Class
1332  contains input parameters and widely used variables
1333  node : ndarray(float)
1334  x,y coordinates of an accepted node
1335 
1336  Returns
1337  ---------
1338 
1339  Notes
1340  -----
1341 
1342  """
1343  center_cell = occupancy_cell(c, node[0:2])
1344  occupied_radius = ceil(node[2] * c.occupancy_grid_side_length_inv)
1345  # furthest number of cells that could still contain a node conflicting
1346  # with a node in center cell.
1347  X, Y = ogrid[max(center_cell[0] - occupied_radius, 0
1348  ):min(center_cell[0] + occupied_radius +
1349  1, c.no_horizontal_occupancy_cells + 1),
1350  max(center_cell[1] - occupied_radius, 0
1351  ):min(center_cell[1] + occupied_radius +
1352  1, c.no_vertical_occupancy_cells + 1)]
1353  distance_from_center = ((X - center_cell[0])**2 + (Y - center_cell[1])**2)
1354  occupied_by_node = (distance_from_center <= (occupied_radius + 1)**2)
1355  # create a circular mask of ones of cells occupied by center node.
1356  c.occupancy_grid[X, Y] = (c.occupancy_grid[X, Y]) | occupied_by_node
1357  # add circilar mask to grid, to mark cells occupied by center node
1358  # unoccupied cells remain 0.
1359 
1360 
1361 
1362 
1363 
1364 def resample(c, cell_x, cell_y):
1365  """ Uniformly samples a point from an under-sampled cell
1366 
1367  Parameters
1368  -----------
1369  c : Poisson Disc Class
1370  contains input parameters and widely used variables
1371  cell_x/cell_y : int
1372  x,y index of an empty occupancy cell
1373 
1374  Returns
1375  ---------
1376  candidate : ndarray(float)
1377  coordinates of a point within the empty occupancy cell
1378 
1379  Notes
1380  -----
1381  by choice of the empty cells, points sampled by this function
1382  can only conflict with each other.
1383  """
1384  candidate = array([
1385  c.x_min + (cell_x + random()) * c.occupancy_grid_side_length,
1386  c.y_min + (cell_y + random()) * c.occupancy_grid_side_length
1387  ])
1388  return candidate
1389 
1390 
1391 
1392 
1393 
1394 def lower_boundary(c, x):
1395  """ lower bounding function at x
1396 
1397  Parameters
1398  -----------
1399  c : Poisson Disc Class
1400  contains input parameters and widely used variables
1401  x : float
1402  x-value between c.x_min and c.x_max
1403 
1404  Returns
1405  ---------
1406  x,y : ndarray(float)
1407  coordinates of apoint on the upper boundary with x-
1408  coordinate x.
1409 
1410  Notes
1411  -----
1412 
1413  """
1414  i = c.last_x_min_index
1415 
1416  while c.vertices_x[i] < c.x_max:
1417  if x >= c.vertices_x[i] and x <= c.vertices_x[(i + 1) %
1418  c.no_of_vertices]:
1419  y = c.vertices_y[i] + \
1420  c.slope_lower_boundary[i] * (x - c.vertices_x[i])
1421  return array([x, y])
1422  i = (i + 1) % c.no_of_vertices
1423  return array([x, c.y_max])
1424 
1425 
1426 
1427 
1428 
1429 def upper_boundary(c, x):
1430  """ upper bounding function at x
1431 
1432  Parameters
1433  -----------
1434  c : Poisson Disc Class
1435  contains input parameters and widely used variables
1436  x : float
1437  x-value between c.x_min and c.x_max
1438 
1439  Returns
1440  ---------
1441  x,y : ndarray(float)
1442  coordinates of a point on the upper boundary with x-
1443  coordinate x.
1444 
1445  Notes
1446  -----
1447 
1448  """
1449 
1450  i = c.first_x_min_index
1451  while c.vertices_x[i] < c.x_max:
1452  if x >= c.vertices_x[i] and x <= c.vertices_x[(i - 1) %
1453  c.no_of_vertices]:
1454  y = c.vertices_y[i] + \
1455  c.slope_upper_boundary[i] * (x - c.vertices_x[i])
1456  return array([x, y])
1457  i = (i - 1) % c.no_of_vertices
1458  return array([x, c.y_min])
1459 
1460 
1461 
1463 """The following turned out to be faster then their numpy counterpart """
1464 
1465 
1466 def distance(X, Y):
1467  """ Returns the Euclidean distance between X and Y
1468 
1469  Parameters
1470  -----------
1471  X/Y : ndarrya(float)
1472  2d coordinates of points X and Y
1473 
1474  Returns
1475  ---------
1476  distance : float
1477  euclidean distance
1478 
1479  Notes
1480  -----
1481  faster than numpy equivalent for 2d
1482  """
1483  return sqrt((X[0] - Y[0]) * (X[0] - Y[0]) + (X[1] - Y[1]) * (X[1] - Y[1]))
1484 
1485 
1486 def distance_sq(X, Y):
1487  """ Returns the square of the Euclidean distance between X and Y
1488 
1489  Parameters
1490  ------------
1491  X/Y : ndarrya(float)
1492  2d coordinates of points X and Y
1493 
1494  Returns
1495  ---------
1496  distance_sq : float
1497  euclidean distance squared
1498 
1499  Notes
1500  -----
1501  faster than numpy equivalent for 2d
1502 
1503 
1504  """
1505  return (X[0] - Y[0]) * (X[0] - Y[0]) + (X[1] - Y[1]) * (X[1] - Y[1])
1506 
1507 
1508 def norm_sq(X):
1509  """ returns euclidean square norm of X
1510 
1511  Parameters
1512  ------------
1513  X : ndarrya(float)
1514  2d coordinates of points X and Y
1515 
1516  Returns
1517  ---------
1518  norm_sq : float
1519  euclidean norm squared
1520 
1521  Notes
1522  -----
1523  faster than numpy equivalent for 2d
1524 
1525  """
1526  return X[0] * X[0] + X[1] * X[1]
1527 
1528 
1529 def dot_product(X, Y):
1530  """returns dotproduct of X and Y
1531 
1532  Parameters
1533  -----------
1534  X/Y : ndarrya(float)
1535  2d coordinates of points X and Y
1536 
1537  Returns
1538  ---------
1539  dot_product : float
1540  euclidean dotproduct
1541 
1542  Notes
1543  -----
1544  faster than numpy equivalent for 2d
1545 
1546  """
1547  return X[0] * Y[0] + X[1] * Y[1]
1548 
1549 
1550 
1551 def dump_poisson_params(h, coarse_factor, slope, min_dist, max_dist,
1552  concurrent_samples, grid_size, well_flag):
1553  """ Writes the parameters used for Poisson point generation to a pickled python dictionary
1554  named 'poisson_params.p'.
1555 
1556  Checks if parameters are within acceptable ranges. If not, they are returned to default values.
1557 
1558  If coarse_factor is not default (8), then the parameters used in coarsening are
1559 
1560  Parameters
1561  ------------
1562  h : float
1563  Min distance of the system. defined in params.txt
1564  coarse_factor: float
1565  Maximum resolution of the mesh. Given as a factor of h
1566  slope : float
1567  slope of variable coarsening resolution.
1568  min_dist : float
1569  Range of constant min-distance around an intersection (in units of h).
1570  max_dist : float
1571  Range over which the min-distance between nodes increases (in units of h)
1572  concurrent_samples : int
1573  number of new candidates sampled around an accepted node at a time.
1574  grid_size : float
1575  side length of the occupancy grid is given by H/occupancy_factor
1576  well_flag : bool
1577  boolean if wells are included in the meshing.
1578 
1579  Returns
1580  ---------
1581  None
1582 
1583  Notes
1584  -----
1585  Variable names here are not consistent with those used in the function called to construct
1586  the Poisson Samples. Below is a conversion table:
1587 
1588  min_dist = F
1589  max_dist = R
1590  slope = A
1591  grid_size = occupancy_factor
1592 
1593  """
1594  print("--> Writing Poisson Disc Parameters")
1595  # Check parameter ranges
1596  if 0 <= slope < 1:
1597  pass
1598  else:
1599  print(f"--> Slope Parameter is outside correct range [0,1)")
1600  print(f"--> Value provided: {slope}")
1601  print("--> Setting to default: 0.1")
1602  slope = 0.1
1603 
1604  if min_dist < 0:
1605  print("--> Warning: Provided min_dist greater 0")
1606  print(f"--> min_dist provided: {min_dist}")
1607  print("--> Setting to default: 1")
1608  min_dist = 1
1609 
1610  if min_dist > max_dist:
1611  print("--> Warning: Provided min_dist greater than max_dist")
1612  print(f"--> min_dist provided: {min_dist}")
1613  print(f"--> max_dist provided: {max_dist}")
1614  print("Setting to default: min_dist=1, max_dist=40")
1615  min_dist = 1
1616  max_dist = 40
1617 
1618  if coarse_factor != 8:
1619  slope = 0.1
1620  max_dist = (coarse_factor - 1) / (2 * slope)
1621 
1622 
1623  print("--> Poisson Sampling Parameters:")
1624  print(f"--> h: {h}")
1625  print(f"--> coarse_factor: {coarse_factor}")
1626  print(f"--> min_dist: {min_dist}")
1627  print(f"--> max_dist: {max_dist}")
1628  print(f"--> concurrent_samples: {concurrent_samples}")
1629  print(f"--> grid_size: {grid_size}\n")
1630  print(f"--> Lower bound on mesh size: {h/2:0.2e}")
1631  print(f"--> Upper bound on mesh size: {2*slope*h*max_dist + h:0.2e}\n")
1632 
1633  params = {"h":h,"R":max_dist,"A":slope,"F":min_dist,\
1634  "concurrent_samples":concurrent_samples,"grid_size":grid_size,"well_flag": well_flag}
1635  pickle.dump(params, open("poisson_params.p", "wb"))
1636 
1637 
1638 def single_fracture_poisson(fracture_id):
1639  """ Generates a point distribution for meshing fracture {fracture_id} using Poisson Disc
1640  Sampling. Resulting points are written into 'points/points_{fracture_id}.xyz' file with format:
1641 
1642  x_0 y_0 z_0
1643  x_1 y_1 z_1
1644  ...
1645  x_n y_n z_n
1646 
1647  Parameters
1648  -----------
1649  fracture_id : int
1650  fracture index
1651 
1652  Returns
1653  ---------
1654  None
1655 
1656  Notes
1657  -----
1658  Parameters for point generation are in a pickled python dictionary "poisson_params.p"
1659  created by dump_poisson_params.
1660 
1661  """
1662 
1663  print(f"--> Starting Poisson sampling for fracture number {fracture_id}")
1664  params = pickle.load(open("poisson_params.p", "rb"))
1665  c = pc.Poisson_Variables(fracture_id, f"polys/poly_{fracture_id}.inp",\
1666  f"intersections/intersections_{fracture_id}.inp", \
1667  params["h"], params["R"], params["A"],\
1668  params["F"],params["concurrent_samples"],\
1669  params["grid_size"],params["well_flag"])
1670 
1671  start = timeit.default_timer()
1672 
1675 
1676  # Reads geometry from input files, sets all derived parameters and
1677  # creates initial set of nodes on the boundary
1678  main_init(c)
1679 
1680  # samples in majority of domain (1)
1681  main_sample(c)
1682 
1683  # fills in holes in the sampling to guarantee maximality
1685 
1686  # Takes off sampling from where it stopped at (1) to increase density
1687  # in previously under-sampled regions to average.
1688  main_sample(c)
1689 
1690 
1692 
1693  # write coordinates to file
1694  dump_coordinates(c, f'points/points_{fracture_id}.xyz')
1695 
1696  runtime = timeit.default_timer() - start
1697  print(
1698  f"--> Poisson sampling for fracture {fracture_id} took {runtime:0.2f} seconds"
1699  )
def new_candidate(c, X)
___Functions related to primary Sampling___#################
def intersect_mark_next_cells(c, direction, center_cell, intersect_number)
def distance(X, Y)
___2D-Numpy replacements___#######################
def intersect_mark_start_cells(c, center_cell, intersect_number)
def occupancy_cell(c, X)
___Functions related to Occupancy Grid___#################
def neighbor_cell(c, X)
___Functions related to look up grid___###################
def main_init(c)
___Functions called directly by main.py____#################
def read_vertices(c, path_to_polygon)
___Initializing functions___########################## @profile
def intersect_cell(c, X)
___Functions related to Intersection look up___#############
def neighboring_cells(c, center_cell, exclusion_radius)
def intersect_crossing_cell_wall(c, direction, current_cell, delta_x, delta_y, y_intercept)
def dump_poisson_params(h, coarse_factor, slope, min_dist, max_dist, concurrent_samples, grid_size, well_flag)