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
16 This file contains all functions called by main.py
17 The contained functions in order are:
19 *called directly by main:
23 - search_undersampled_cells()
28 *called by other functions:
30 - neighbor_grid_init()
34 - intersect_distance()
38 - read_intersections()
40 - sampling_along_line()
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()
49 - occupancy_undersampled()
50 - occupancy_grid_update()
68 """ Reads inputs and initializes variables in c, i.e. initialized the polygon,
69 the intersections, samples along the boundary and initializes
74 c : Poisson Disc Class
75 contains input parameters and widely used variables
92 for i
in range(len(well_pts)):
93 c.intersect_endpts.append(well_pts[i])
99 c.no_of_nodes = len(c.coordinates)
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.
116 c : Poisson Disc Class
117 contains input parameters and widely used variables
125 Proceeds from, where it terminated the previous time, if called
128 while c.current_node < c.no_of_nodes:
132 for j
in range(0, c.k):
135 c.no_of_nodes = c.no_of_nodes + 1
137 c.current_node = c.current_node + 1
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.
151 c : Poisson Disc Class
152 contains input parameters and widely used variables
164 no_of_undersampled = len(undersampled_x)
165 random_permutation = [m
for m
in range(0, no_of_undersampled)]
166 shuffle(random_permutation)
171 while random_permutation:
172 random_integer = random_permutation.pop()
173 candidate =
resample(c, undersampled_x[random_integer],
174 undersampled_y[random_integer])
176 c.no_of_nodes = c.no_of_nodes + 1
181 """ Prints accepted coordinates to file
185 c : Poisson Disc Class
186 contains input parameters and widely used variables
188 coordinates will be printed to a file with this name
200 col_format =
"{:<30}" * 3 +
"\n"
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]))
211 """ Plots accepted nodes to screen or file
215 c : Poisson Disc Class
216 contains input parameters and widely used variables
218 name of the file in which c.coordinated-plot will be saved.
219 c.coordinates are plotted to screen, if empty.
227 creates the file "output_file", if this string is not empty.
231 xcoord = [x[0]
for x
in c.coordinates]
232 ycoord = [x[1]
for x
in c.coordinates]
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
237 plt.scatter(xcoord, ycoord, s=.5)
238 if len(output_file) == 0:
241 plt.savefig(output_file +
'png', dpi=150)
250 """ Returns look up-Grid index of the point X
254 c : Poisson Disc Class
255 contains input parameters and widely used variables
257 2D coordinates of a point inside the neighbor-grid
261 (x,y) : tuple(int,int)
262 horizontal and vertical neighbor-cell number
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)
277 """ Initializes background grid
281 c : Poisson Disc Class
282 contains input parameters and widely used variables
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.
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)):
307 c, c.coordinates[node_number])] = node_number + 1
318 """ Returns a random point in a annular neighborhood of X
322 c : Poisson Disc Class
323 contains input parameters and widely used variables
325 first two entries: x,y-coordinates of a already accepted node.
326 last entry: local exclusion_radius of that node.
330 candidate : ndarray(float)
331 x,y- coordinates of a potential new node.
337 radius = random() * c.max_exclusion_radius + X[2]
342 angle = random() * pi * 2
343 candidate = X[0:2] + [radius * cos(angle), radius * sin(angle)]
351 """ accepts a candidate p, if no conflicts with domain or already accepted nodes arise
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
363 True, if candidate is accepted as new node
368 If the candidate is accepted, it is added to c.coordinates
369 (including its local_exclusion_radius) and the neighbor grid
375 if candidate[0] < c.x_min:
377 if candidate[0] > c.x_max:
379 if candidate[1] < c.y_min:
381 if candidate[1] > c.y_max:
388 if c.neighbor_grid[candidates_neighbor_cell] != 0:
397 candidates_ex_rad_sq = candidates_ex_rad**2
401 closeby_node = c.coordinates[node_number - 1]
404 closeby_ex_rad_sq = closeby_node[2]**2
405 if distance_sq(candidate, closeby_node) < min(candidates_ex_rad_sq,
411 c.coordinates.append(append(candidate, candidates_ex_rad))
412 c.neighbor_grid[candidates_neighbor_cell] = c.no_of_nodes + 1
420 """ returns the local min-distance of particle X
424 c : Poisson Disc Class
425 contains input parameters and widely used variables
427 first two entries: x,y-coordinates of a node
431 local_exclusion_radius : float
432 exclusion radius at point X
435 X can have more than 2 entries. Anything, but the first
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
452 D = sqrt(closest_intersect_distance_sq)
455 local_exclusion_radius = max(c.A * (D - c.F * c.H) + .5 * c.H,
457 return local_exclusion_radius
459 local_exclusion_radius = c.max_exclusion_radius
460 return local_exclusion_radius
467 """ returns square distance to closest intersection
471 c : Poisson Disc Class
472 contains input parameters and widely used variables
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
481 square of the distance to the closest intersection
482 of 'inf', if no intersection is closeby.
488 for i
in closeby_intersections:
489 intersect_start = c.intersect_endpts[2 * i]
490 intersect_end = c.intersect_endpts[2 * i + 1]
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:
496 possible_output = possible_output + \
500 dist_to_line_sq =
norm_sq((X - intersect_start) -
501 (intersect_end - intersect_start) *
502 projection_on_intersection /
503 (length_of_intersection_sq))
506 possible_output.append(dist_to_line_sq)
508 square_dist = min(possible_output)
516 """ Tests if the node X is within the polyon defined by c.vertices.
520 c : Poisson Disc Class
521 contains input parameters and widely used variables
523 x,y-coordinates of a node
528 False if X lies within the polygon
537 i = c.last_x_min_index
540 while c.vertices_x[i] < c.x_max:
544 if X[0] >= c.vertices_x[i]
and X[0] <= c.vertices_x[(i + 1) %
546 if X[1] < c.vertices_y[i] + \
547 c.slope_lower_boundary[i] * (X[0] - c.vertices_x[i]):
554 i = (i + 1) % c.no_of_vertices
557 i = c.first_x_min_index
560 while c.vertices_x[i] < c.x_max:
564 if X[0] >= c.vertices_x[i]
and X[0] <= c.vertices_x[(i - 1) %
566 if X[1] > c.vertices_y[i] + \
567 c.slope_upper_boundary[i] * (X[0] - c.vertices_x[i]):
574 i = (i - 1) % c.no_of_vertices
581 """ Returns the coordinate number of all non-empty cells neighboring
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
595 closeby_nodes : list(int)
596 node numbers of all close by nodes
602 max_cell_distance = ceil(exclusion_radius * c.neighbor_cell_size_inv)
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)]
613 closeby_nodes = subgrid[nonzero(subgrid)]
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, ...)
627 c : Poisson Disc Class
628 contains input parameters and widely used variables
629 path_to_polygon : string
630 file containing coordinates of vertices
634 vertices : list(ndarray(float))
635 list of coordinates of the polygon vertices
641 with open(path_to_polygon)
as inputfile:
642 lines = inputfile.readlines()
643 c.no_of_vertices = int(lines[0].split()[0])
645 array([float(Y)
for Y
in X.split()[1:3]])
646 for X
in lines[1:1 + c.no_of_vertices]
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)
655 c.first_x_min_index = c.vertices_x.index(c.x_min)
658 c.last_x_min_index = c.first_x_min_index
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
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
692 """ reads Intersection endpoints from file
696 c : Poisson Disc Class
697 contains input parameters and widely used variables
698 path_to_intersections : string
699 file containing intersection points
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,...]
709 sets neighbor_grid width to max_exclusion_radius/sqrt(2), if there's
716 for i
in range(0, c.no_of_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])
722 with open(path_to_intersections)
as inputfile:
723 lines = inputfile.readlines()
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
730 int(X.split()[2])
for X
in lines[no_of_pts + no_of_lines + 4:]
734 first_line_intersect_j = 1
735 for j
in range(0, no_of_intersections):
737 [float(Y)
for Y
in lines[first_line_intersect_j].split()[1:3]])
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])
745 [float(Y)
for Y
in lines[last_line_intersect_j].split()[1:3]])
747 end_pts.append(end_j)
749 first_line_intersect_j = last_line_intersect_j + 1
752 c.neighbor_cell_size = c.max_exclusion_radius / sqrt(2)
753 c.neighbor_cell_size_inv = 1 / c.neighbor_cell_size
763 with open(
"well_points.dat",
"r")
as fwell:
765 for line
in fwell.readlines():
766 if c.fracture_id == int(line.split()[0]):
768 pt[0] = float(line.split()[1])
769 pt[1] = float(line.split()[2])
780 """ Samples points around the boundary
784 c : Poisson Disc Class
785 contains input parameters and widely used variables
789 boundary_points : list(ndarray(float))
790 coordinates of a poisson-disk sampling along
791 the boundary of the polygon.
804 for i
in range(0, c.no_of_vertices):
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]
811 cos_start_angle =
dot_product(prev_line, current_line) / sqrt(
813 cos_end_angle =
dot_product(current_line, next_line) / sqrt(
817 c.vertices[i]), (cos_start_angle > .5) *
818 c.H * .25 / sqrt(1 - cos_end_angle**2))
821 (cos_end_angle > .5) * c.H * .25 / sqrt(1 - cos_end_angle**2))
827 if sqrt(
norm_sq(current_line)) - r_start - r_end > 0:
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))
836 boundary_points = boundary_points + \
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(
846 elif sqrt(
norm_sq(current_line)) - r_start - r_end == 0:
848 start = c.vertices[i] - r_start * \
849 current_line / sqrt(
norm_sq(current_line))
852 return vertices + boundary_points
859 """ Samples points on a line with min distance r and max distance 1.3r
860 (Poisson disk sampling on a line)
864 c : Poisson Disc Class
865 contains input parameters and widely used variables
867 coordinates of start and end point of the line to be sampled
871 line_sample : list(ndarray(float))
872 coordinates of a Poisson disk sampling along the line from x to y
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.
883 direction = (y - x) /
distance(y, x)
885 line_sample = [append(previous_sample, ex_rad)]
886 while distance(previous_sample, y) > 2.3 * ex_rad:
888 increment = .3 * random() * ex_rad + ex_rad
889 previous_sample = previous_sample + direction * increment
891 line_sample.append(append(previous_sample, ex_rad))
893 while distance(previous_sample, y) >= 2 * ex_rad:
895 increment = random() * (
distance(previous_sample, y) -
897 previous_sample = previous_sample + direction * increment
900 line_sample.append(append(previous_sample, ex_rad))
910 """ Returns Intersect-Grid index of the point X
914 c : Poisson Disc Class
915 contains input parameters and widely used variables
917 x,y-coordinates of the point C
922 horizontal and vertical intersection_cell_index
927 x = floor((X[0] - c.x_min) * c.intersect_grid_inv)
928 y = floor((X[1] - c.y_min) * c.intersect_grid_inv)
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
942 c : Poisson Disc Class
943 contains input parameters and widely used variables
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.
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]
964 delta_x, delta_y = End[0] - Start[0], End[1] - Start[1]
966 y_intercept = Start[1] - Start[0] * delta_y / delta_x
968 y_intercept = Start[0]
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:
977 current_intersect_cell,
978 delta_x, delta_y, y_intercept):
980 c, direction, current_intersect_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
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.
1006 Adds the intersection number to the center cell and all its 8 neighbor cells in
1007 the dictionary c.intersect_cells
1010 for j
in range(0, 9):
1012 c.intersect_cells[center_cell[0] - 1 +
1013 floor(j / 3), center_cell[1] - 1 +
1014 (j % 3)].append(intersect_number)
1017 except BaseException:
1018 c.intersect_cells[center_cell[0] - 1 +
1019 floor(j / 3), center_cell[1] - 1 +
1020 (j % 3)] = [intersect_number]
1027 """ Determines the direction and intersection is pointing if interpreted as an arrow from start to end.( right, left, up, down, combinations possible)
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
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.
1044 This is used to determine which sides of a intersect-cell an intersection
1051 directions.append(1)
1053 directions.append(3)
1055 directions.append(2)
1057 directions.append(4)
1065 """ Determines the next intersect-cell the current intersection is crossing
1066 and adds all neighboring cells of that cell to the dictionary
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
1083 new_center_cell : ndarray(int)
1084 index-pair of the most recent cell known to be crossed by the
1085 current intersection
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.
1094 x_shift = (direction % 2) * (-1)**(int((direction - 1) / 2))
1095 y_shift = ((direction - 1) % 2) * (-1)**(int(direction / 2) + 1)
1098 for j
in range(0, 3):
1101 c.intersect_cells[center_cell[0] - 1 + j, center_cell[1] +
1102 2 * y_shift].append(intersect_number)
1106 except BaseException:
1107 c.intersect_cells[center_cell[0] - 1 + j, center_cell[1] +
1108 2 * y_shift] = [intersect_number]
1110 for j
in range(0, 3):
1114 c.intersect_cells[center_cell[0] +
1115 2 * x_shift, center_cell[1] - 1 +
1116 j].append(intersect_number)
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
1133 """ Determines if intersection crosses cell in given direction
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
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
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.
1164 cell_nodes_k = c.square_nodes[direction - 1:direction + 1, :]
1168 square_node_1 = (array(current_cell) + cell_nodes_k[0, :]) * (c.R * c.H +
1170 square_node_2 = (array(current_cell) + cell_nodes_k[1, :]) * (c.R * c.H +
1174 square_node_2 + array([c.x_min, c.y_min]),
1175 delta_x, delta_y, y_intercept)
1182 """ Check intersection cell sign. Returns True if X& Y are on the same side of the line determined by dx,dy and yshift
1186 c : Poisson Disc Class
1187 contains input parameters and widely used variables
1188 X/Y : ndarray(float)
1189 coordinates of two points
1191 x/y distance between start and end points of a line segment
1193 y-intercept of an infinite line in 2d or x-value of line
1194 is parallel to y axis
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
1206 sgn_x = dy * (X[0]) - dx * (X[1] - yshift)
1209 sgn_y = dy * (Y[0]) - dx * (Y[1] - yshift)
1213 sgn_x = X[0] - yshift
1214 sgn_y = Y[0] - yshift
1215 if sgn_x == 0
or sgn_y == 0:
1217 if (sgn_x < 0
and sgn_y < 0)
or (sgn_x > 0
and sgn_y > 0):
1228 """ Returns Occupancy-Grid index of the point X
1232 c : Poisson Disc Class
1233 contains input parameters and widely used variables
1235 x,y-coordinats of a points
1240 index-pair of the occupancy-grid-cell, in which X lies
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)
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
1264 c : Poisson Disc Class
1265 contains input parameters and widely used variables
1269 undersampled_cells : list(ndarray(int))
1270 indices of emtpy occupancy-grid-cells
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
1284 c.occupancy_grid[:, 0] =
True
1285 c.occupancy_grid[-1, :] =
True
1286 c.occupancy_grid[0, :] =
True
1293 c.no_horizontal_occupancy_cells * c.occupancy_grid_side_length,
1294 .5 * c.occupancy_grid_side_length)
1300 except BaseException:
1301 print(
"error", points)
1302 if boundary_cell[0] < c.no_horizontal_occupancy_cells:
1307 c.occupancy_grid[boundary_cell[0], (boundary_cell[1]):] =
True
1310 (c.occupancy_grid[boundary_cell[0], :(
1311 boundary_cell[1])]) =
True
1314 for i
in range(0, len(c.coordinates)):
1316 undersampled_cells = nonzero(c.occupancy_grid == 0)
1317 del c.occupancy_grid
1318 return undersampled_cells
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.
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
1344 occupied_radius = ceil(node[2] * c.occupancy_grid_side_length_inv)
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)
1356 c.occupancy_grid[X, Y] = (c.occupancy_grid[X, Y]) | occupied_by_node
1365 """ Uniformly samples a point from an under-sampled cell
1369 c : Poisson Disc Class
1370 contains input parameters and widely used variables
1372 x,y index of an empty occupancy cell
1376 candidate : ndarray(float)
1377 coordinates of a point within the empty occupancy cell
1381 by choice of the empty cells, points sampled by this function
1382 can only conflict with each other.
1385 c.x_min + (cell_x + random()) * c.occupancy_grid_side_length,
1386 c.y_min + (cell_y + random()) * c.occupancy_grid_side_length
1395 """ lower bounding function at x
1399 c : Poisson Disc Class
1400 contains input parameters and widely used variables
1402 x-value between c.x_min and c.x_max
1406 x,y : ndarray(float)
1407 coordinates of apoint on the upper boundary with x-
1414 i = c.last_x_min_index
1416 while c.vertices_x[i] < c.x_max:
1417 if x >= c.vertices_x[i]
and x <= c.vertices_x[(i + 1) %
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])
1430 """ upper bounding function at x
1434 c : Poisson Disc Class
1435 contains input parameters and widely used variables
1437 x-value between c.x_min and c.x_max
1441 x,y : ndarray(float)
1442 coordinates of a point on the upper boundary with x-
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) %
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])
1463 """The following turned out to be faster then their numpy counterpart """
1467 """ Returns the Euclidean distance between X and Y
1471 X/Y : ndarrya(float)
1472 2d coordinates of points X and Y
1481 faster than numpy equivalent for 2d
1483 return sqrt((X[0] - Y[0]) * (X[0] - Y[0]) + (X[1] - Y[1]) * (X[1] - Y[1]))
1487 """ Returns the square of the Euclidean distance between X and Y
1491 X/Y : ndarrya(float)
1492 2d coordinates of points X and Y
1497 euclidean distance squared
1501 faster than numpy equivalent for 2d
1505 return (X[0] - Y[0]) * (X[0] - Y[0]) + (X[1] - Y[1]) * (X[1] - Y[1])
1509 """ returns euclidean square norm of X
1514 2d coordinates of points X and Y
1519 euclidean norm squared
1523 faster than numpy equivalent for 2d
1526 return X[0] * X[0] + X[1] * X[1]
1530 """returns dotproduct of X and Y
1534 X/Y : ndarrya(float)
1535 2d coordinates of points X and Y
1540 euclidean dotproduct
1544 faster than numpy equivalent for 2d
1547 return X[0] * Y[0] + X[1] * Y[1]
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'.
1556 Checks if parameters are within acceptable ranges. If not, they are returned to default values.
1558 If coarse_factor is not default (8), then the parameters used in coarsening are
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
1567 slope of variable coarsening resolution.
1569 Range of constant min-distance around an intersection (in units of h).
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.
1575 side length of the occupancy grid is given by H/occupancy_factor
1577 boolean if wells are included in the meshing.
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:
1591 grid_size = occupancy_factor
1594 print(
"--> Writing Poisson Disc Parameters")
1599 print(f
"--> Slope Parameter is outside correct range [0,1)")
1600 print(f
"--> Value provided: {slope}")
1601 print(
"--> Setting to default: 0.1")
1605 print(
"--> Warning: Provided min_dist greater 0")
1606 print(f
"--> min_dist provided: {min_dist}")
1607 print(
"--> Setting to default: 1")
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")
1618 if coarse_factor != 8:
1620 max_dist = (coarse_factor - 1) / (2 * slope)
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")
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"))
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:
1658 Parameters for point generation are in a pickled python dictionary "poisson_params.p"
1659 created by dump_poisson_params.
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"])
1671 start = timeit.default_timer()
1696 runtime = timeit.default_timer() - start
1698 f
"--> Poisson sampling for fracture {fracture_id} took {runtime:0.2f} seconds"
def neighbor_grid_init(c)
def occupancy_mark(c, node)
def sampling_along_line(c, x, y)
def intersect_cell_sign(c, X, Y, dx, dy, yshift)
def new_candidate(c, X)
___Functions related to primary Sampling___#################
def occupancy_undersampled(c)
def exclusion_radius(c, X)
def intersect_mark_next_cells(c, direction, center_cell, intersect_number)
def distance(X, Y)
___2D-Numpy replacements___#######################
def accept_candidate(c, candidate)
def intersect_mark_start_cells(c, center_cell, intersect_number)
def occupancy_cell(c, X)
___Functions related to Occupancy Grid___#################
def single_fracture_poisson(fracture_id)
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_distance_sq(c, X, closeby_intersections)
def intersect_cell(c, X)
___Functions related to Intersection look up___#############
def neighboring_cells(c, center_cell, exclusion_radius)
def search_undersampled_cells(c)
def resample(c, cell_x, cell_y)
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)
def boundary_sampling(c)
@profile
def intersect_grid_init(c)
def read_intersections(c, path_to_intersections)
def dump_coordinates(c, output_file="points.xyz")
def plot_coordinates(c, output_file="")
def intersect_direction(c, delta_x, delta_y)