RDG-Networks 0.3.7__py3-none-any.whl → 0.3.9__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: RDG-Networks
3
- Version: 0.3.7
3
+ Version: 0.3.9
4
4
  Summary: Most of the code from the RDG Networks project
5
5
  Home-page: https://github.com/NiekMooij/RDG_networks
6
6
  Author: Niek Mooij
@@ -14,6 +14,14 @@ Requires-Dist: numpy
14
14
  Requires-Dist: scipy
15
15
  Requires-Dist: shapely
16
16
  Requires-Dist: typing
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: classifier
20
+ Dynamic: description
21
+ Dynamic: home-page
22
+ Dynamic: license
23
+ Dynamic: requires-dist
24
+ Dynamic: summary
17
25
 
18
26
  ## Overview
19
27
 
@@ -1,5 +1,5 @@
1
1
  RDG_networks/Classes.py,sha256=_9X3JPHFAYYlaC8IZ_H9__sfz99G5l9UfPl65lL60_4,7977
2
- RDG_networks/__init__.py,sha256=tKoHFugrHAkce3wyw62MvOzcSfoBr2tpSp6da7Y54LQ,1764
2
+ RDG_networks/__init__.py,sha256=8cc-h5ifS7RP1-N4b1Ow-4bA6MxgFETaS6SD0of54Fk,1575
3
3
  RDG_networks/draw_segments.py,sha256=U53N5GXmQHWKdM1Q1faP_EGKjc6enOu2mcsunzSFpP0,984
4
4
  RDG_networks/generate_line_network.py,sha256=lJ4rhObim3WcEQoebomewRQKWNJC5phFyFYRW7qjXIg,1127
5
5
  RDG_networks/generate_line_segments.py,sha256=QV8_k7q6TD5c7Hcb2Ms_apEdWYw4XdLr7rdJgh49v4Q,9004
@@ -8,15 +8,16 @@ RDG_networks/generate_line_segments_static.py,sha256=7KvHZi3krv-tAGydJR_gbMMmHKZ
8
8
  RDG_networks/get_intersection_segments.py,sha256=mXB5qCy1oOps4Vu1mX6flW6v_4Xxc71YK41yOWjJX8o,2797
9
9
  RDG_networks/sample_in_polygon.py,sha256=qpPpW-Da1vK8ZkVWMJ0zBsE8IgyMB619gCdybSkzKSQ,1605
10
10
  RDG_networks/save_to_stl.py,sha256=St8kGw6wl8uOGx8KhrZhBfe89-mOfp5JKhz0dEDBvc0,3894
11
- RDG_networks/thickness/Classes.py,sha256=gVe_q5Rh_1DBiJoZ8H0FyJ4xG-IAcespjUpUirxFfAA,8125
12
- RDG_networks/thickness/__init__.py,sha256=jyyA7Bp519TkOGNSYDVxPKxCgO9vTYpQvpFytnIuqQs,892
13
- RDG_networks/thickness/generate_line_segments_thickness.py,sha256=meLFptrXWt_povCTlEA0n_TYVfi_-HF9KUvW_tBpt4w,29194
14
- RDG_networks/thickness/generate_line_segments_thickness_orientation.py,sha256=oTEwQkXRBuoHvEdIGU30p21e2QHW1UlmApzRO1s5c64,16821
15
- RDG_networks/thickness/generate_line_segments_thickness_static.py,sha256=zvYkLZpmyl711Kr7LCEFbXeVUgxQuA1n9Z5jD8W2iXc,9021
11
+ RDG_networks/thickness/Classes.py,sha256=zRhi2TFDwK1oARvAd1HlxnMWeHSj7KKO-u79_r23tXc,8176
12
+ RDG_networks/thickness/__init__.py,sha256=DzH-mmdrk5e1LL7oq5kg0xaDjtWR7DiCeKKchArHSIs,703
13
+ RDG_networks/thickness/generate_line_segments_thickness.py,sha256=q3MVKEC8w1MUpiRGF6o4MKh6VHBIs1nuIibsqsewDcQ,29037
14
+ RDG_networks/thickness/generate_line_segments_thickness_correct.py,sha256=rvvqEMGYX7imosKedyXHyTkBV4C2_24K8UxthXDTe7Q,28627
15
+ RDG_networks/thickness/generate_line_segments_thickness_static.py,sha256=6-2p4GOQFU-dP5Q9nYuaVqeb7wvxk2Fqld3A7cV2pY8,8964
16
+ RDG_networks/thickness/orientate_network.py,sha256=4jii3y89Jlw1tPmTWoUyrKE7MlHq21JuoiuwnBa1Mkk,15484
16
17
  RDG_networks/thickness/sample_in_polygon.py,sha256=nJ-yqfoCCGfC6_EpGL3L1t1LOYdqWZd-7v5bxy6th34,1849
17
- RDG_Networks-0.3.7.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
18
- RDG_Networks-0.3.7.dist-info/METADATA,sha256=A_gS2IRz_hQxrek9dkl2BawsJ6SKYuBxIUBCdhkjb58,2422
19
- RDG_Networks-0.3.7.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
20
- RDG_Networks-0.3.7.dist-info/entry_points.txt,sha256=DRd5hzsY9jAz5e_gkd3gNoIwqj6RAZEtISlV1qpsIE8,1038
21
- RDG_Networks-0.3.7.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
22
- RDG_Networks-0.3.7.dist-info/RECORD,,
18
+ RDG_Networks-0.3.9.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
19
+ RDG_Networks-0.3.9.dist-info/METADATA,sha256=hlDd-R21n_eTohn-xz3OU7-ZvLragLhxSHwNWTq0u4M,2577
20
+ RDG_Networks-0.3.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
21
+ RDG_Networks-0.3.9.dist-info/entry_points.txt,sha256=coqOWe9rYYuz9VQvJGFomTzvEP7JY5T9V9gauMhSb_0,986
22
+ RDG_Networks-0.3.9.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
23
+ RDG_Networks-0.3.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -6,10 +6,10 @@ generate_line_segments = RDG_networks.generate_line_segments:main
6
6
  generate_line_segments_dynamic = RDG_networks.generate_line_segments_dynamic:main
7
7
  generate_line_segments_static = RDG_networks.generate_line_segments_static:main
8
8
  generate_line_segments_thickness = RDG_networks.thickness.generate_line_segments_thickness:main
9
- generate_line_segments_thickness_orientation = RDG_networks.thickness.generate_line_segments_dynamic_orientation:main
10
9
  generate_line_segments_thickness_static = RDG_networks.generate_line_segments_thickness_static:main
11
10
  get_alignment_mean = RDG_networks.thickness.get_alignment_mean:main
12
11
  get_intersection_segments = RDG_networks.get_intersection_segments:main
12
+ orientate_network = RDG_networks.thickness.orientate_network:main
13
13
  rotate_network = RDG_networks.thickness.rotate_network:main
14
14
  save_to_stl = RDG_networks.save_to_stl:main
15
15
  translate_network = RDG_networks.thickness.translate_network:main
RDG_networks/__init__.py CHANGED
@@ -8,17 +8,17 @@ from .generate_line_segments_dynamic import generate_line_segments_dynamic
8
8
  from .generate_line_segments_static import generate_line_segments_static
9
9
  from .draw_segments import draw_segments
10
10
  from .thickness.generate_line_segments_thickness import generate_line_segments_thickness
11
- from .thickness.generate_line_segments_thickness_orientation import generate_line_segments_thickness_orientation
11
+ from .thickness.orientate_network import orientate_network
12
12
  from .thickness.generate_line_segments_thickness_static import generate_line_segments_thickness_static
13
- from .thickness.generate_line_segments_thickness_orientation import translate_network
14
- from .thickness.generate_line_segments_thickness_orientation import clip_network
15
- from .thickness.generate_line_segments_thickness_orientation import rotate_network
16
- from .thickness.generate_line_segments_thickness_orientation import get_alignment_mean
13
+ from .thickness.orientate_network import translate_network
14
+ from .thickness.orientate_network import clip_network
15
+ from .thickness.orientate_network import rotate_network
16
+ from .thickness.orientate_network import get_alignment_mean
17
17
  from .save_to_stl import save_to_stl
18
18
 
19
19
  __all__ = ['generate_line_segments',
20
20
  'generate_line_segments_thickness',
21
- 'generate_line_segments_thickness_orientation',
21
+ 'orientate_network',
22
22
  'translate_network',
23
23
  'clip_network',
24
24
  'rotate_network',
@@ -95,7 +95,7 @@ class Polygon:
95
95
  vertices (List[Tuple[float, float]]): A list of (x, y) coordinates representing the vertices of the polygon.
96
96
  """
97
97
 
98
- def __init__(self, vertices: List[tuple], middle_segment=None):
98
+ def __init__(self, vertices: List[tuple], middle_segment=None, neighbors=None):
99
99
  """
100
100
  Initializes a Polygon instance with the provided vertices.
101
101
 
@@ -104,6 +104,7 @@ class Polygon:
104
104
  """
105
105
  self.vertices = vertices
106
106
  self.middle_segment = middle_segment
107
+ self.neighbors = neighbors
107
108
 
108
109
  def area(self) -> float:
109
110
  """
@@ -1,16 +1,16 @@
1
1
  # __init__.py
2
2
 
3
3
  from .generate_line_segments_thickness import generate_line_segments_thickness
4
- from .generate_line_segments_thickness_orientation import generate_line_segments_thickness_orientation
4
+ from .orientate_network import orientate_network
5
5
  from .generate_line_segments_thickness_static import generate_line_segments_thickness_static
6
- from .generate_line_segments_thickness_orientation import translate_network
7
- from .generate_line_segments_thickness_orientation import clip_network
8
- from .generate_line_segments_thickness_orientation import rotate_network
9
- from .generate_line_segments_thickness_orientation import get_alignment_mean
6
+ from .orientate_network import translate_network
7
+ from .orientate_network import clip_network
8
+ from .orientate_network import rotate_network
9
+ from .orientate_network import get_alignment_mean
10
10
 
11
11
  __all__ = [
12
12
  'generate_line_segments_thickness',
13
- 'generate_line_segments_thickness_orientation',
13
+ 'orientate_network',
14
14
  'generate_line_segments_thickness_static',
15
15
  'translate_network',
16
16
  'clip_network',
@@ -142,10 +142,10 @@ def pick_item_with_probability(
142
142
  def get_location_and_direction(
143
143
  polygon_arr: Dict[str, Dict[str, object]],
144
144
  thickness: float,
145
+ angle: float,
145
146
  nucleation_point: Tuple[float, float] = None,
146
147
  min_distance: float = 0,
147
- max_attempts: int = 1000,
148
- angles: Union[str, List[float]] = 'uniform'
148
+ max_attempts: int = 1000
149
149
  ) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
150
150
  """
151
151
  Attempts to find a valid location and direction within a polygon for placing a new segment. The direction can either be randomly
@@ -159,8 +159,8 @@ def get_location_and_direction(
159
159
  The thickness of the segment that needs to fit inside the polygon.
160
160
  max_attempts (int, optional):
161
161
  The maximum number of attempts to find a valid location and direction. Defaults to 1000.
162
- angles (Union[str, List[float]], optional):
163
- A string ('uniform' for random directions) or a list of angles (in radians) to choose the direction from. Defaults to 'uniform'.
162
+ angle (float):
163
+ A float indicating the angle of the new segment.
164
164
  nucleation_point (Tuple[float, float], optional):
165
165
  predified nucleation point for the segment. Defaults to None.
166
166
  min_distance (float, optional):
@@ -178,16 +178,8 @@ def get_location_and_direction(
178
178
  - The angle of the segment.
179
179
  - Returns `False` if no valid location and direction are found after the maximum attempts.
180
180
  """
181
-
182
- # Generate a new direction based on the angles parameter
183
- if angles == 'uniform':
184
- angle_new = random.uniform(-np.pi, np.pi)
185
- direction = (np.cos(angle_new), np.sin(angle_new))
186
- direction = direction / np.linalg.norm(direction) # Normalize the direction vector
187
- else:
188
- angle_new = random.choice(angles)
189
- direction = (np.cos(angle_new), np.sin(angle_new))
190
- direction = np.array(direction) / np.linalg.norm(direction)
181
+ direction = (np.cos(angle), np.sin(angle))
182
+ direction = np.array(direction) / np.linalg.norm(direction)
191
183
 
192
184
  # Try to find a valid location and direction up to max_attempts
193
185
  attempt = 0
@@ -215,7 +207,7 @@ def get_location_and_direction(
215
207
 
216
208
  # Check if both endpoints of the segment are inside the polygon
217
209
  if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
218
- return polygon_id, polygon, location_new, direction, perpendicular, angle_new
210
+ return polygon_id, polygon, location_new, direction, perpendicular, angle
219
211
 
220
212
  attempt += 1
221
213
 
@@ -425,7 +417,7 @@ def update_data(
425
417
  segments_dict[segment_new_1.id] = segment_new_1
426
418
  segments_dict[neighbor1_1].neighbors[id_1] = vertex_begin_1
427
419
  segments_dict[neighbor1_2].neighbors[id_1] = vertex_end_1
428
-
420
+
429
421
  # Update the segments_dict for the second segment
430
422
  neighbors_initial_2 = {
431
423
  neighbor2_1: vertex_begin_2,
@@ -443,7 +435,20 @@ def update_data(
443
435
  segments_dict[neighbor2_2].neighbors[id_2] = vertex_end_2
444
436
 
445
437
  # Update the segment_thickness_dict with the base polygon
446
- segment_thickness_dict[len(segment_thickness_dict) + 1] = Polygon(vertices=vertices0)
438
+ neighbors = cycle0.copy()
439
+ neighbors.remove(f'{len(segment_thickness_dict)+1}_1')
440
+ neighbors.remove(f'{len(segment_thickness_dict)+1}_2')
441
+ neighbors = [ i[:-2] if i not in ['b1', 'b2', 'b3', 'b4'] else i for i in neighbors ]
442
+ neighbors = list(set(neighbors))
443
+
444
+ segment_thickness_dict[len(segment_thickness_dict) + 1] = Polygon(vertices=vertices0, neighbors=neighbors)
445
+
446
+ for n in neighbors:
447
+ if n in ['b1', 'b2', 'b3', 'b4']:
448
+ continue
449
+
450
+ else:
451
+ segment_thickness_dict[int(n)].neighbors.append(len(segment_thickness_dict) + 1)
447
452
 
448
453
  return segments_dict, polygon_arr, segment_thickness_dict
449
454
 
@@ -451,7 +456,7 @@ def add_line_segment(
451
456
  segments_dict: Dict[int, LineSegment],
452
457
  polygon_arr: Dict[str, Dict[str, object]],
453
458
  segment_thickness_dict: Dict[int, Polygon],
454
- angles: str = 'uniform',
459
+ angle: float,
455
460
  thickness: float = 0,
456
461
  nucleation_point: Tuple[float, float] = None,
457
462
  min_distance: float = 0,
@@ -480,7 +485,7 @@ def add_line_segment(
480
485
  """
481
486
 
482
487
  # Get a valid location and direction, or return False if none is found
483
- loc = get_location_and_direction(polygon_arr, thickness, nucleation_point, min_distance, max_attempts=max_attempts, angles=angles)
488
+ loc = get_location_and_direction(polygon_arr, angle=angle, thickness=thickness, nucleation_point=nucleation_point, min_distance=min_distance, max_attempts=max_attempts)
484
489
  if loc:
485
490
  polygon_id, polygon, location_new, direction_new, perpendicular, angle_new = loc
486
491
  else:
@@ -560,8 +565,7 @@ def generate_line_segments_thickness(
560
565
  Args:
561
566
  size (int): The number of line segments to generate.
562
567
  thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
563
- angles (str): The angle distribution method for generating segments. Defaults to 'uniform'.
564
- List[float]: list of angles in radians.
568
+ angles (str): Angle used in the generation of the segments.
565
569
  config (List[List[float]]): A list of configurations for the nucleation points and angles.
566
570
  epsilon (float): the minimum distance between two line.
567
571
  box_size (float): the size of the box.
@@ -606,8 +610,6 @@ def generate_line_segments_thickness(
606
610
  angles = [config[i]['angle']]
607
611
  else:
608
612
  nucleation_point = None
609
- # if angles != 'uniform':
610
- # angles=[angles[i]]
611
613
 
612
614
  output = add_line_segment(segments_dict,
613
615
  polygon_arr,
@@ -615,7 +617,7 @@ def generate_line_segments_thickness(
615
617
  thickness=thickness_arr[i],
616
618
  min_distance = epsilon,
617
619
  nucleation_point = nucleation_point,
618
- angles=angles,
620
+ angle=angles[i],
619
621
  box_size=box_size)
620
622
  if output:
621
623
  segments_dict, polygon_arr, segment_thickness_dict, location, angle = output
@@ -624,7 +626,7 @@ def generate_line_segments_thickness(
624
626
  else:
625
627
  if config:
626
628
  print('Configuration not possible. Point is skipped.')
627
- else:
629
+ else:
628
630
  print(f"Stopped at iteration {len(segment_thickness_dict)}, could not find a valid segment position.")
629
631
  jammed = True
630
632
  break
@@ -0,0 +1,633 @@
1
+ import math
2
+ import numpy as np
3
+ import random
4
+ from typing import List, Dict, Tuple, Union, Optional
5
+ from shapely.geometry import Polygon as Polygon_Shapely, LineString
6
+
7
+ from .Classes import Line, LineSegment, Polygon
8
+ from .sample_in_polygon import sample_in_polygon, is_inside_polygon
9
+
10
+ def doLinesIntersect(line1: Line, line2: Line, box_size = 1) -> Tuple[bool, Union[Tuple[float, float], None]]:
11
+ """
12
+ Check if two lines intersect and return the intersection point.
13
+
14
+ Args:
15
+ - line1 (Line): The first line segment.
16
+ - line2 (Line): The second line segment.
17
+ - box_size (float): The size of the bounding box. Defaults to 1.
18
+
19
+ Returns:
20
+ - intersect (bool): True if the lines intersect, False otherwise.
21
+ - intersection_point (tuple or None): The intersection point (x, y) if lines intersect, None otherwise.
22
+ """
23
+ x1, y1 = line1.location
24
+ v1, w1 = line1.direction
25
+
26
+ x2, y2 = line2.location
27
+ v2, w2 = line2.direction
28
+
29
+ determinant = v1 * w2 - v2 * w1
30
+
31
+ if determinant == 0:
32
+ return False, (None, None)
33
+
34
+ t1 = ((x2 - x1) * w2 - (y2 - y1) * v2) / determinant
35
+ t2 = ((x2 - x1) * w1 - (y2 - y1) * v1) / determinant
36
+
37
+ intersect_x = x1 + v1 * t1
38
+ intersect_y = y2 + w2 * t2
39
+
40
+
41
+ if -1e-6 < intersect_x < box_size + 1e-6 and -1e-6 < intersect_y < box_size + 1e-6:
42
+ return True, (intersect_x, intersect_y)
43
+ else:
44
+ return False, (None, None)
45
+
46
+ def doSegmentsIntersect(
47
+ segment1: LineSegment,
48
+ segment2: LineSegment,
49
+ box_size = 1
50
+ ) -> Tuple[bool, Tuple[Optional[float], Optional[float]]]:
51
+ """
52
+ Determines if two line segments intersect and returns the intersection point if they do.
53
+
54
+ Args:
55
+ segment1 (LineSegment): The first line segment.
56
+ segment2 (LineSegment): The second line segment.
57
+
58
+ Returns:
59
+ Tuple[bool, Tuple[Optional[float], Optional[float]]]:
60
+ - A boolean indicating whether the segments intersect.
61
+ - A tuple of the x and y coordinates of the intersection point if they intersect,
62
+ otherwise (None, None).
63
+ """
64
+
65
+ # Create line equations based on the segments' start and end points
66
+ line1 = Line(location=segment1.start, direction=np.array(segment1.end) - np.array(segment1.start))
67
+ line2 = Line(location=segment2.start, direction=np.array(segment2.end) - np.array(segment2.start))
68
+
69
+ # Check if the infinite extensions of the two lines intersect
70
+ intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2, box_size)
71
+
72
+ # If no intersection, return False
73
+ if not intersect:
74
+ return False, (None, None)
75
+
76
+ # Check if the intersection point is within the bounds of both segments in the x-direction
77
+ xcheck = (
78
+ (segment1.end[0] <= intersect_x <= segment1.start[0]
79
+ or segment1.start[0] <= intersect_x <= segment1.end[0]
80
+ or abs(intersect_x - segment1.end[0]) < 1e-6
81
+ or abs(intersect_x - segment1.start[0]) < 1e-6)
82
+ and
83
+ (segment2.end[0] <= intersect_x <= segment2.start[0]
84
+ or segment2.start[0] <= intersect_x <= segment2.end[0]
85
+ or abs(intersect_x - segment2.end[0]) < 1e-6
86
+ or abs(intersect_x - segment2.start[0]) < 1e-6)
87
+ )
88
+
89
+ # Check if the intersection point is within the bounds of both segments in the y-direction
90
+ ycheck = (
91
+ (segment1.end[1] <= intersect_y <= segment1.start[1]
92
+ or segment1.start[1] <= intersect_y <= segment1.end[1]
93
+ or abs(intersect_y - segment1.end[1]) < 1e-6
94
+ or abs(intersect_y - segment1.start[1]) < 1e-6)
95
+ and
96
+ (segment2.end[1] <= intersect_y <= segment2.start[1]
97
+ or segment2.start[1] <= intersect_y <= segment2.end[1]
98
+ or abs(intersect_y - segment2.end[1]) < 1e-6
99
+ or abs(intersect_y - segment2.start[1]) < 1e-6)
100
+ )
101
+
102
+ # If the intersection point lies within the bounds of both segments, return True with the intersection point
103
+ if xcheck and ycheck:
104
+ return True, (intersect_x, intersect_y)
105
+
106
+ # Otherwise, return False and no intersection point
107
+ return False, (None, None)
108
+
109
+ def pick_item_with_probability(
110
+ polygon_arr: Dict[str, Dict[str, object]]
111
+ ) -> Tuple[str, Dict[str, object]]:
112
+ """
113
+ Randomly selects an item from the polygon array with a probability proportional to the area of the polygons.
114
+
115
+ Args:
116
+ polygon_arr (Dict[str, Dict[str, object]]):
117
+ A dictionary where keys are polygon identifiers (e.g., 'p1', 'p2') and values are dictionaries containing polygon properties,
118
+ including an 'area' key that stores the area of the polygon.
119
+
120
+ Returns:
121
+ Tuple[str, Dict[str, object]]:
122
+ - The identifier of the selected polygon.
123
+ - The corresponding polygon data (dictionary) containing its properties.
124
+ """
125
+
126
+ # Calculate the total weight (sum of areas of all polygons)
127
+ max_weight = sum(pol['area'] for pol in polygon_arr.values())
128
+
129
+ # Generate a random threshold between 0 and the total weight
130
+ threshold = random.uniform(0, max_weight)
131
+ cumulative_weight = 0
132
+
133
+ # Iterate through the polygons, accumulating weights
134
+ for item, pol in polygon_arr.items():
135
+ weight = pol['area']
136
+ cumulative_weight += weight
137
+
138
+ # Return the polygon when the cumulative weight surpasses the threshold
139
+ if cumulative_weight >= threshold:
140
+ return item, pol
141
+
142
+ def get_location_and_direction(
143
+ polygon_arr: Dict[str, Dict[str, object]],
144
+ thickness: float,
145
+ angle: float,
146
+ nucleation_point: Tuple[float, float] = None,
147
+ min_distance: float = 0,
148
+ max_attempts: int = 1000
149
+ ) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
150
+ """
151
+ Attempts to find a valid location and direction within a polygon for placing a new segment. The direction can either be randomly
152
+ chosen (uniformly) or from a specified list of angles. It ensures that the segment lies within the polygon's bounds given the
153
+ specified thickness.
154
+
155
+ Args:
156
+ polygon_arr (Dict[str, Dict[str, object]]):
157
+ A dictionary where the keys are polygon identifiers and the values are dictionaries containing polygon properties, including 'vertices'.
158
+ thickness (float):
159
+ The thickness of the segment that needs to fit inside the polygon.
160
+ max_attempts (int, optional):
161
+ The maximum number of attempts to find a valid location and direction. Defaults to 1000.
162
+ angle (float):
163
+ A float indicating the angle of the new segment.
164
+ nucleation_point (Tuple[float, float], optional):
165
+ predified nucleation point for the segment. Defaults to None.
166
+ min_distance (float, optional):
167
+ the minimum distance between two lines. Defaults to 0.
168
+
169
+ Returns:
170
+ Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
171
+ - If a valid location and direction are found, returns a tuple containing:
172
+ - The polygon ID (`str`).
173
+ - The polygon data (`Dict[str, object]`).
174
+ - The new location as a tuple of floats (`Tuple[float, float]`).
175
+ - The direction vector as a numpy array (`np.ndarray`).
176
+ - The perpendicular vector to the direction as a numpy array (`np.ndarray`).
177
+ - The nucleation point [x,y] of the segment
178
+ - The angle of the segment.
179
+ - Returns `False` if no valid location and direction are found after the maximum attempts.
180
+ """
181
+ direction = (np.cos(angle), np.sin(angle))
182
+ direction = np.array(direction) / np.linalg.norm(direction)
183
+
184
+ # Try to find a valid location and direction up to max_attempts
185
+ attempt = 0
186
+ while attempt < max_attempts:
187
+ polygon_id, polygon = pick_item_with_probability(polygon_arr)
188
+
189
+ # Sample a location within the polygon
190
+ #check if nucleation point is given
191
+ if nucleation_point is not None:
192
+ location_new = nucleation_point
193
+ else:
194
+ location_new = sample_in_polygon(polygon['vertices'])
195
+
196
+ # Compute the perpendicular vector to the direction
197
+ perpendicular = np.array([direction[1], -direction[0]])
198
+ perpendicular = perpendicular / np.linalg.norm(perpendicular)
199
+
200
+ # Ensure the perpendicular vector is oriented consistently (y-component is non-negative)
201
+ if perpendicular[1] < 0:
202
+ perpendicular = -perpendicular
203
+
204
+ # Compute the positions for the segment with thickness, shifted by half-thickness along the perpendicular direction
205
+ p1 = np.array(location_new) + (thickness/2 + min_distance) * perpendicular
206
+ p2 = np.array(location_new) - (thickness/2 + min_distance) * perpendicular
207
+
208
+ # Check if both endpoints of the segment are inside the polygon
209
+ if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
210
+ return polygon_id, polygon, location_new, direction, perpendicular, angle
211
+
212
+ attempt += 1
213
+
214
+ # If no valid location and direction is found, return False
215
+ return False
216
+
217
+ def get_polygons(polygon_id, polygon_arr, neighbor1_1, neighbor1_2, vertex_begin_1, vertex_end_1, neighbor2_1, neighbor2_2, vertex_begin_2, vertex_end_2, segment_new_id_1, segment_new_id_2):
218
+ # Extract vertices and cycle (faces) of the original polygon
219
+ vertices = polygon_arr[polygon_id]['vertices']
220
+ cycle = polygon_arr[polygon_id]['faces']
221
+
222
+ # Get first cycle and vertices
223
+ index_start_1, index_end_1 = (cycle.index(neighbor1_1), cycle.index(neighbor1_2))
224
+ if index_start_1 < index_end_1:
225
+ cycle1 = [segment_new_id_1] + cycle[index_start_1:index_end_1+1]
226
+ vertices1 = [vertex_begin_1] + vertices[index_start_1:index_end_1] + [vertex_end_1]
227
+ else:
228
+ cycle1 = [segment_new_id_1] + cycle[index_start_1:] + cycle[:index_end_1+1]
229
+ vertices1 = [vertex_begin_1] + vertices[index_start_1:] + vertices[:index_end_1] + [vertex_end_1]
230
+
231
+ # Get second cycle and vertices
232
+ index_start_2, index_end_2 = (cycle.index(neighbor2_2), cycle.index(neighbor2_1))
233
+ if index_start_2 < index_end_2:
234
+ cycle2 = [segment_new_id_2] + cycle[index_start_2:index_end_2+1]
235
+ vertices2 = [vertex_end_2] + vertices[index_start_2:index_end_2] + [vertex_begin_2]
236
+ else:
237
+ cycle2 = [segment_new_id_2] + cycle[index_start_2:] + cycle[:index_end_2+1]
238
+ vertices2 = [vertex_end_2] + vertices[index_start_2:] + vertices[:index_end_2] + [vertex_begin_2]
239
+
240
+ # Get middle cycle and vertices
241
+ cycle0 = [neighbor1_1, segment_new_id_1, neighbor1_2]
242
+ vertices0 = [vertex_begin_1, vertex_end_1]
243
+
244
+ index_start_0, index_end_0 = (cycle.index(neighbor1_2), cycle.index(neighbor2_2))
245
+ if index_start_0 < index_end_0:
246
+ cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
247
+ vertices0 = vertices0 + vertices[index_start_0:index_end_0]
248
+
249
+ elif index_start_0 > index_end_0:
250
+ cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
251
+ vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
252
+
253
+ cycle0 = cycle0 + [segment_new_id_2]
254
+ vertices0 = vertices0 + [vertex_end_2] + [vertex_begin_2]
255
+
256
+ index_start_0, index_end_0 = (cycle.index(neighbor2_1), cycle.index(neighbor1_1))
257
+ if index_start_0 < index_end_0:
258
+ cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
259
+ vertices0 = vertices0 + vertices[index_start_0:index_end_0]
260
+
261
+ elif index_start_0 > index_end_0:
262
+ cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
263
+ vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
264
+
265
+ return cycle0, vertices0, cycle1, vertices1, cycle2, vertices2
266
+
267
+ def get_new_segment(
268
+ line_segments_to_check: List[LineSegment],
269
+ location: Tuple[float, float],
270
+ direction: Tuple[float, float],
271
+ id: Optional[int] = None,
272
+ box_size: float = 1
273
+ ) -> LineSegment:
274
+ """
275
+ Creates a new line segment by extending a given location in a specified direction and
276
+ determines its neighbors by checking intersections with other line segments.
277
+
278
+ Args:
279
+ line_segments_to_check (List[LineSegment]): List of existing line segments to check for intersections.
280
+ location (Tuple[float, float]): The starting point (x, y) for the new line segment.
281
+ direction (Tuple[float, float]): The direction vector in which to extend the line segment.
282
+ id (Optional[int]): Optional ID for the new line segment. If not provided, defaults to None.
283
+ box_size(optional[Int]]): The size of the box. Defaults to 1.
284
+
285
+ Returns:
286
+ LineSegment: A new line segment object with its neighbors based on intersections.
287
+ """
288
+
289
+ # Create a temporary line segment extending from the location in both directions
290
+ s_temp = LineSegment(start=np.array(location) - 10*np.sqrt(2)*box_size * np.array(direction), end=np.array(location) + 10*np.sqrt(2)*box_size * np.array(direction))
291
+
292
+ intersection_points = []
293
+
294
+ # Check for intersections with existing line segments
295
+ for segment in line_segments_to_check:
296
+ intersect, (intersect_x, intersect_y) = doSegmentsIntersect(s_temp, segment, box_size)
297
+
298
+ if intersect:
299
+ segment_length = math.sqrt(
300
+ (location[0] - intersect_x) ** 2
301
+ + (location[1] - intersect_y) ** 2
302
+ )
303
+ intersection_points.append(
304
+ {"id": segment.id, "point": (intersect_x, intersect_y), "segment_length": segment_length}
305
+ )
306
+
307
+ # Divide intersections into ones behind and in front of the new line
308
+ intersections_b = [intersection for intersection in intersection_points if intersection["point"][0] < location[0]]
309
+ intersections_f = [intersection for intersection in intersection_points if intersection["point"][0] > location[0]]
310
+
311
+ if not intersections_b or not intersections_f:
312
+ intersections_b = [intersection for intersection in intersection_points if intersection["point"][1] < location[1]]
313
+ intersections_f = [intersection for intersection in intersection_points if intersection["point"][1] > location[1]]
314
+
315
+ # Determine the closest intersections for segment start and end
316
+ s_start = min(intersections_b, key=lambda x: x["segment_length"])
317
+ s_end = min(intersections_f, key=lambda x: x["segment_length"])
318
+ start, end = s_start['point'], s_end['point']
319
+ start_id, end_id = s_start['id'], s_end['id']
320
+
321
+ # Ensure the start comes before the end
322
+ if start[0] > end[0]:
323
+ start, end = end, start
324
+ start_id, end_id = end_id, start_id
325
+
326
+ # Create a new line segment and assign neighbors
327
+ neighbors_initial = {start_id: start, end_id: end}
328
+ segment_new = LineSegment(start=start, end=end, id=id, neighbors_initial=neighbors_initial, neighbors=neighbors_initial)
329
+
330
+ return segment_new
331
+
332
+ def update_data(
333
+ segments_dict: Dict[int, LineSegment],
334
+ polygon_arr: Dict[str, Dict[str, object]],
335
+ polygon_id: str,
336
+ segment_thickness_dict: Dict[int, Polygon],
337
+ vertices0: List[Tuple[float, float]],
338
+ vertices1: List[Tuple[float, float]],
339
+ vertices2: List[Tuple[float, float]],
340
+ cycle0: List[int],
341
+ cycle1: List[int],
342
+ cycle2: List[int],
343
+ neighbor1_1: int,
344
+ neighbor1_2: int,
345
+ neighbor2_1: int,
346
+ neighbor2_2: int,
347
+ vertex_begin_1: Tuple[float, float],
348
+ vertex_end_1: Tuple[float, float],
349
+ vertex_begin_2: Tuple[float, float],
350
+ vertex_end_2: Tuple[float, float],
351
+ id_1: int,
352
+ id_2: int
353
+ ) -> Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
354
+ """
355
+ Updates the segments, polygons, and segment thickness dictionaries by adding new data derived
356
+ from provided vertices and neighbor information.
357
+
358
+ Args:
359
+ segments_dict (Dict[int, LineSegment]): A dictionary of segments with segment ID as the key.
360
+ polygon_arr (Dict[str, Dict[str, object]]): A dictionary of polygons with polygon ID as the key.
361
+ polygon_id (str): The ID of the polygon being updated.
362
+ segment_thickness_dict (Dict[int, Polygon]): A dictionary mapping thickness information to polygon objects.
363
+ vertices0 (List[Tuple[float, float]]): Vertices of the base polygon.
364
+ vertices1 (List[Tuple[float, float]]): Vertices of the first new polygon.
365
+ vertices2 (List[Tuple[float, float]]): Vertices of the second new polygon.
366
+ cycle0 (List[int]): List of face indices for the base polygon.
367
+ cycle1 (List[int]): List of face indices for the first new polygon.
368
+ cycle2 (List[int]): List of face indices for the second new polygon.
369
+ neighbor1_1 (int): ID of the first neighbor of the first segment.
370
+ neighbor1_2 (int): ID of the second neighbor of the first segment.
371
+ neighbor2_1 (int): ID of the first neighbor of the second segment.
372
+ neighbor2_2 (int): ID of the second neighbor of the second segment.
373
+ vertex_begin_1 (Tuple[float, float]): Starting vertex of the first segment.
374
+ vertex_end_1 (Tuple[float, float]): Ending vertex of the first segment.
375
+ vertex_begin_2 (Tuple[float, float]): Starting vertex of the second segment.
376
+ vertex_end_2 (Tuple[float, float]): Ending vertex of the second segment.
377
+ id_1 (int): ID of the first new segment.
378
+ id_2 (int): ID of the second new segment.
379
+
380
+ Returns:
381
+ Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
382
+ - Updated dictionary of line segments.
383
+ - Updated dictionary of polygons.
384
+ - Updated dictionary of segment thickness.
385
+ """
386
+
387
+ # Update polygon_arr (a dictionary of polygons)
388
+ polygon_new_1 = {
389
+ f'p{len(polygon_arr) + 1}': {
390
+ 'vertices': vertices1,
391
+ 'area': Polygon(vertices=vertices1).area(),
392
+ 'faces': cycle1
393
+ }
394
+ }
395
+ polygon_new_2 = {
396
+ polygon_id: {
397
+ 'vertices': vertices2,
398
+ 'area': Polygon(vertices=vertices2).area(),
399
+ 'faces': cycle2
400
+ }
401
+ }
402
+ polygon_arr.update(polygon_new_1)
403
+ polygon_arr.update(polygon_new_2)
404
+
405
+ # Update the segments_dict for the first segment
406
+ neighbors_initial_1 = {
407
+ neighbor1_1: vertex_begin_1,
408
+ neighbor1_2: vertex_end_1
409
+ }
410
+ segment_new_1 = LineSegment(
411
+ start=vertex_begin_1,
412
+ end=vertex_end_1,
413
+ id=id_1,
414
+ neighbors_initial=neighbors_initial_1,
415
+ neighbors=neighbors_initial_1
416
+ )
417
+ segments_dict[segment_new_1.id] = segment_new_1
418
+ segments_dict[neighbor1_1].neighbors[id_1] = vertex_begin_1
419
+ segments_dict[neighbor1_2].neighbors[id_1] = vertex_end_1
420
+
421
+ # Update the segments_dict for the second segment
422
+ neighbors_initial_2 = {
423
+ neighbor2_1: vertex_begin_2,
424
+ neighbor2_2: vertex_end_2
425
+ }
426
+ segment_new_2 = LineSegment(
427
+ start=vertex_begin_2,
428
+ end=vertex_end_2,
429
+ id=id_2,
430
+ neighbors_initial=neighbors_initial_2,
431
+ neighbors=neighbors_initial_2
432
+ )
433
+ segments_dict[segment_new_2.id] = segment_new_2
434
+ segments_dict[neighbor2_1].neighbors[id_2] = vertex_begin_2
435
+ segments_dict[neighbor2_2].neighbors[id_2] = vertex_end_2
436
+
437
+ # Update the segment_thickness_dict with the base polygon
438
+ segment_thickness_dict[len(segment_thickness_dict) + 1] = Polygon(vertices=vertices0)
439
+
440
+ return segments_dict, polygon_arr, segment_thickness_dict
441
+
442
+ def add_line_segment(
443
+ segments_dict: Dict[int, LineSegment],
444
+ polygon_arr: Dict[str, Dict[str, object]],
445
+ segment_thickness_dict: Dict[int, Polygon],
446
+ angle: float,
447
+ thickness: float = 0,
448
+ nucleation_point: Tuple[float, float] = None,
449
+ min_distance: float = 0,
450
+ box_size: float = 1,
451
+ max_attempts: int = 1000
452
+ ) -> Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], List[float], float], bool]:
453
+ """
454
+ Adds a new line segment to the segments and polygon data structures, with a given thickness and angle distribution.
455
+
456
+ Args:
457
+ segments_dict (Dict[int, LineSegment]): A dictionary containing the current line segments.
458
+ polygon_arr (Dict[str, Dict[str, object]]): A dictionary containing the current polygons and their properties.
459
+ segment_thickness_dict (Dict[int, Polygon]): A dictionary storing the thickness information mapped to polygons.
460
+ thickness (float): The thickness of the new segment to be added. Defaults to 0.
461
+ angles (str): The angle distribution method. Defaults to 'uniform'.
462
+ nucleation_point (Tuple[float, float]): A predefined nucleation point for the new segment. Defaults to None.
463
+ min_distance (float): The minimum distance between two lines. Defaults to 0.
464
+ box_size (float): The size of the box. Defaults to 1.
465
+ max_attempts (int): The maximum number of attempts to find a valid location and direction. Defaults to 1000.
466
+
467
+ Returns:
468
+ Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]], List[float], float, bool]:
469
+ - A tuple containing the updated segments dictionary, polygon dictionary, and thickness dictionary,
470
+ or False if no valid location for the new segment is found.
471
+ -nucleation point in a list [x,y] and the angle of the segment in radians.
472
+ """
473
+
474
+ # Get a valid location and direction, or return False if none is found
475
+ loc = get_location_and_direction(polygon_arr, angle=angle, thickness=thickness, nucleation_point=nucleation_point, min_distance=min_distance, max_attempts=max_attempts)
476
+ if loc:
477
+ polygon_id, polygon, location_new, direction_new, perpendicular, angle_new = loc
478
+ else:
479
+ print('No valid location found')
480
+ return False
481
+
482
+ # Get the borders of the new segment with the given thickness
483
+ line_segments_to_check = [segments_dict[segment] for segment in polygon['faces']]
484
+ middle_segment = get_new_segment(line_segments_to_check, location=location_new, direction=direction_new, box_size=box_size)
485
+ s1 = get_new_segment(line_segments_to_check, location=np.array(location_new) + thickness * perpendicular / 2, direction=direction_new,box_size=box_size)
486
+ s2 = get_new_segment(line_segments_to_check, location=np.array(location_new) - thickness * perpendicular / 2, direction=direction_new, box_size=box_size)
487
+
488
+ # Extract neighbor information and segment vertices
489
+ neighbor1_1, neighbor1_2 = list(s1.neighbors.keys())
490
+ vertex_begin_1, vertex_end_1 = list(s1.neighbors.values())
491
+ neighbor2_1, neighbor2_2 = list(s2.neighbors.keys())
492
+ vertex_begin_2, vertex_end_2 = list(s2.neighbors.values())
493
+ id_1 = str(int((len(segments_dict.keys()) - 2) / 2)) + '_1'
494
+ id_2 = str(int((len(segments_dict.keys()) - 2) / 2)) + '_2'
495
+
496
+ # Get the resulting polygons after splitting
497
+ cycle0, vertices0, cycle1, vertices1, cycle2, vertices2 = get_polygons(
498
+ polygon_id,
499
+ polygon_arr,
500
+ neighbor1_1,
501
+ neighbor1_2,
502
+ vertex_begin_1,
503
+ vertex_end_1,
504
+ neighbor2_1,
505
+ neighbor2_2,
506
+ vertex_begin_2=vertex_begin_2,
507
+ vertex_end_2=vertex_end_2,
508
+ segment_new_id_1=id_1,
509
+ segment_new_id_2=id_2
510
+ )
511
+
512
+ # Update all relevant data structures
513
+ segments_dict, polygon_arr, segment_thickness_dict = update_data(
514
+ segments_dict,
515
+ polygon_arr,
516
+ polygon_id,
517
+ segment_thickness_dict,
518
+ vertices0,
519
+ vertices1,
520
+ vertices2,
521
+ cycle0,
522
+ cycle1,
523
+ cycle2,
524
+ neighbor1_1,
525
+ neighbor1_2,
526
+ neighbor2_1,
527
+ neighbor2_2,
528
+ vertex_begin_1,
529
+ vertex_end_1,
530
+ vertex_begin_2,
531
+ vertex_end_2,
532
+ id_1,
533
+ id_2
534
+ )
535
+
536
+ # Associate the middle segment with the newly created thickness entry
537
+ segment_thickness_dict[list(segment_thickness_dict.keys())[-1]].middle_segment = middle_segment
538
+
539
+ return segments_dict, polygon_arr, segment_thickness_dict, location_new, angle_new
540
+
541
+ def generate_line_segments_thickness(
542
+ size: int,
543
+ thickness_arr: List[float],
544
+ angles: str = 'uniform',
545
+ config: List[List[float]] = None,
546
+ epsilon: float = 0,
547
+ box_size: float = 1
548
+ ) -> Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], np.ndarray]:
549
+ """
550
+ Generates a specified number of line segments and updates the polygon and segment thickness dictionaries.
551
+
552
+ Args:
553
+ size (int): The number of line segments to generate.
554
+ thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
555
+ angles (str): Angle used in the generation of the segments.
556
+ config (List[List[float]]): A list of configurations for the nucleation points and angles.
557
+ epsilon (float): the minimum distance between two line.
558
+ box_size (float): the size of the box.
559
+
560
+ Returns:
561
+ Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
562
+ - Updated dictionary of line segments.
563
+ - Updated dictionary of polygons.
564
+ - Updated dictionary of segment thicknesses.
565
+ - Array of the nucleation points and angles [x,y,theta].
566
+ """
567
+
568
+ # Initialize border segments for a square and its polygon representation
569
+ borders = [
570
+ LineSegment((box_size, 0), (0, 0), id='b1', neighbors_initial={'b2': (0, 0), 'b4': (box_size, 0)}, neighbors={'b2': (0, 0), 'b4': (box_size, 0)}),
571
+ LineSegment((0, box_size), (0, 0), id='b2', neighbors_initial={'b1': (0, 0), 'b3': (0, box_size)}, neighbors={'b1': (0, 0), 'b3': (0, box_size)}),
572
+ LineSegment((0, box_size), (box_size, box_size), id='b3', neighbors_initial={'b2': (0, box_size), 'b4': (box_size, box_size)}, neighbors={'b2': (0, box_size), 'b4': (box_size, box_size)}),
573
+ LineSegment((box_size, box_size), (box_size, 0), id='b4', neighbors_initial={'b1': (box_size, 0), 'b3': (box_size, box_size)}, neighbors={'b1': (box_size, 0), 'b3': (box_size, box_size)})
574
+ ]
575
+
576
+ polygon_arr = {
577
+ 'p1': {
578
+ 'vertices': [(0, 0), (0, box_size), (box_size, box_size), (box_size, 0)],
579
+ 'area': box_size**2,
580
+ 'faces': ['b1', 'b2', 'b3', 'b4']
581
+ }
582
+ }
583
+
584
+ segments = borders
585
+ segments_dict = {segment.id: segment for segment in segments}
586
+ segment_thickness_dict = {}
587
+ generated_config = []
588
+
589
+ if config is not None and size > len(config):
590
+ print("The size of the configuration is smaller than the size of the segments. Generated a network of the same size as the configuration.")
591
+ size = len(config)
592
+
593
+ jammed = False
594
+ for i in range(size):
595
+ if config:
596
+ nucleation_point = config[i]['location']
597
+ angles = [config[i]['angle']]
598
+ else:
599
+ nucleation_point = None
600
+ # if angles != 'uniform':
601
+ # angles=[angles[i]]
602
+
603
+ output = add_line_segment(segments_dict,
604
+ polygon_arr,
605
+ segment_thickness_dict,
606
+ thickness=thickness_arr[i],
607
+ min_distance = epsilon,
608
+ nucleation_point = nucleation_point,
609
+ angle=angles[i],
610
+ box_size=box_size)
611
+ if output:
612
+ segments_dict, polygon_arr, segment_thickness_dict, location, angle = output
613
+ generated_config.append({ 'location': location, 'angle': angle, 'thickness': thickness_arr[i] })
614
+
615
+ else:
616
+ if config:
617
+ print('Configuration not possible. Point is skipped.')
618
+ else:
619
+ print(f"Stopped at iteration {len(segment_thickness_dict)}, could not find a valid segment position.")
620
+ jammed = True
621
+ break
622
+
623
+ # Uncomment the following line if you want progress feedback
624
+ percentage = np.round(i / size * 100, 3)
625
+ print(f'generate_segments: {percentage}% done', end='\r')
626
+
627
+ data_dict = {'segments_dict': segments_dict,
628
+ 'polygon_arr': polygon_arr,
629
+ 'segment_thickness_dict': segment_thickness_dict,
630
+ 'jammed': jammed,
631
+ 'generated_config': generated_config}
632
+
633
+ return data_dict
@@ -105,7 +105,7 @@ def seeds(number_of_lines, radius = 0.015, number_of_trials = 10000):
105
105
  if trial <= number_of_trials:
106
106
  nucleation_points += [new_points]
107
107
  angles = [0, np.pi/4, np.pi/2, 3*np.pi/4]
108
- angle_new = random.uniform(0, 2*np.pi) #random.choice(angles)#np.pi #random.uniform(0, 2*np.pi)
108
+ angle_new = random.uniform(0, 2*np.pi)
109
109
  angle += [angle_new]
110
110
  Line[line_id] = [ new_points ,angle_new]
111
111
  line_id += 1
@@ -1,8 +1,8 @@
1
1
  import numpy as np
2
+ import math
2
3
  from typing import List, Dict, Tuple
3
4
  from shapely.geometry import Polygon as Polygon_Shapely
4
5
  from shapely.geometry import LineString, box
5
- from concurrent.futures import ProcessPoolExecutor
6
6
  from .Classes import LineSegment, Polygon
7
7
 
8
8
  def rotate(point, center, rotation_matrix):
@@ -13,7 +13,9 @@ def rotate(point, center, rotation_matrix):
13
13
  rotation_matrix: 2x2 numpy array representing the rotation matrix
14
14
  """
15
15
  translated_point = point - center
16
- rotated_point = np.dot(rotation_matrix, translated_point)
16
+
17
+ # rotated_point = np.dot(rotation_matrix, translated_point)
18
+ rotated_point = rotation_matrix@translated_point
17
19
  final_point = rotated_point + center
18
20
 
19
21
  return final_point
@@ -30,16 +32,22 @@ def angle_between(v1, v2):
30
32
 
31
33
  def get_alignment_mean(line_vector_arr, director):
32
34
  """Get the mean alignment."""
33
- S_all = []
35
+ S_all = 0
36
+ total_mass = 0
34
37
  for item in line_vector_arr:
35
38
  line_vector = item['line_vector']
39
+ vector_diff = np.array(line_vector[1]) - np.array(line_vector[0])
40
+
36
41
  area = item['area']
37
- P2 = 0.5*(3*(np.cos(angle_between(line_vector, director)))**2-1)
38
- S_all.append(P2*area)
42
+ align = math.cos(angle_between(vector_diff, director))**2
43
+ S_all += align*area
44
+ total_mass += area
39
45
 
40
- return float(np.mean(S_all))
46
+ output = S_all / total_mass
41
47
 
42
- def compute_alignment_for_angle(
48
+ return output
49
+
50
+ def compute_alignment(
43
51
  angle: float,
44
52
  segment_thickness_dict: dict[str, Polygon],
45
53
  director: np.ndarray,
@@ -69,7 +77,7 @@ def compute_alignment_for_angle(
69
77
  tuple[float, float]
70
78
  A tuple where the first element is the input angle and the second element is the computed alignment value.
71
79
  """
72
- box_center = (np.array(box_measurements[0]) + np.array(box_measurements[2])) / 2
80
+ box_center = np.array((box_measurements[0]) + np.array(box_measurements[2])) / 2
73
81
 
74
82
  # Rotate network
75
83
  segment_thickness_dict_new = rotate_network(segment_thickness_dict, rotate_angle=angle, box_center=box_center)
@@ -79,13 +87,38 @@ def compute_alignment_for_angle(
79
87
 
80
88
  line_vectors = [
81
89
  {'line_vector': [seg.middle_segment.start, seg.middle_segment.end], 'area': seg.area()}
82
- for seg in segment_thickness_dict_new.values()
90
+ for seg in segment_thickness_dict_new.values() if seg.middle_segment is not None
83
91
  ]
84
92
 
85
93
  alignment = get_alignment_mean(line_vectors, director)
86
-
94
+
87
95
  return angle, alignment
88
96
 
97
+ def get_max_alignment(
98
+ segment_thickness_dict: dict,
99
+ director: np.ndarray,
100
+ box_measurements: list[float],
101
+ grid_points: int = 360
102
+ ) -> float:
103
+ """Find the angle with the maximum alignment using parallel processing."""
104
+ # Create a list of angles to evaluate
105
+ angles = np.linspace(0, np.pi, grid_points)
106
+
107
+ results = []
108
+ for a in angles:
109
+ result = compute_alignment(a, segment_thickness_dict, director, box_measurements)
110
+ results.append(result)
111
+
112
+ # Find the angle with the maximum alignment
113
+ max_alignment = 0
114
+ max_angle = None
115
+ for angle, alignment in results:
116
+ if alignment > max_alignment:
117
+ max_alignment = alignment
118
+ max_angle = angle
119
+
120
+ return max_angle
121
+
89
122
  def rotate_network(
90
123
  segment_thickness_dict: dict[str, Polygon],
91
124
  rotate_angle: float,
@@ -137,7 +170,7 @@ def rotate_network(
137
170
  middle_segment_new = LineSegment(start=start, end=end)
138
171
 
139
172
  # Store the rotated segment in the new dictionary
140
- segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
173
+ segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
141
174
 
142
175
  return segment_thickness_dict_new
143
176
 
@@ -204,7 +237,7 @@ def clip_network(
204
237
  middle_segment_new = LineSegment(start=start, end=end)
205
238
 
206
239
  # Create a new clipped polygon with updated vertices and middle segment
207
- pol_new = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
240
+ pol_new = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
208
241
  pol_new.sort_vertices() # Ensure vertices are sorted
209
242
  segment_thickness_dict_new[id] = pol_new
210
243
 
@@ -252,78 +285,16 @@ def translate_network(
252
285
  middle_segment_new = LineSegment(start=start, end=end)
253
286
 
254
287
  # Store the translated segment in the new dictionary
255
- segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
288
+ segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
256
289
 
257
290
  return segment_thickness_dict_new
258
291
 
259
- def get_alignment_mean(line_vector_arr, director):
260
- """Get the mean alignment."""
261
- S_all = []
262
- for item in line_vector_arr:
263
- line_vector = item['line_vector']
264
- area = item['area']
265
- P2 = 0.5*(3*(np.cos(angle_between(line_vector, director)))**2-1)
266
- S_all.append(P2*area)
267
-
268
- return float(np.mean(S_all))
269
-
270
- def compute_alignment_for_angle(
271
- segment_thickness_dict: dict,
272
- angle: float,
273
- box_center,
274
- director: np.ndarray,
275
- ) -> tuple[float, float]:
276
- """Compute the alignment for a given angle."""
277
-
278
- # Rotate the segment network for the given angle
279
- segment_thickness_dict_rotated = rotate_network(segment_thickness_dict, rotate_angle=angle, box_center=box_center)
280
-
281
- # Create line vectors from the rotated segments
282
- line_vectors = []
283
- for s in segment_thickness_dict_rotated.values():
284
- line_vectors.append({'line_vector': np.array([s.middle_segment.start, s.middle_segment.end]), 'area': s.area()})
285
-
286
- # Compute the alignment for the current angle
287
- alignment = get_alignment_mean(line_vectors, director)
288
- return angle, alignment
289
-
290
- def get_max_alignment_angle(
291
- segment_thickness_dict: dict,
292
- director: np.ndarray,
293
- box_measurements: list[float],
294
- grid_points: int = 360
295
- ) -> float:
296
- """Find the angle with the maximum alignment using parallel processing."""
297
-
298
- # Create a list of angles to evaluate
299
- angles = np.linspace(0, 2 * np.pi, grid_points)
300
-
301
- # Use ProcessPoolExecutor for parallel computation of alignment
302
- with ProcessPoolExecutor() as executor:
303
- # Submit tasks to the pool for each angle
304
- results = list(executor.map(
305
- compute_alignment_for_angle,
306
- [segment_thickness_dict] * len(angles), # Same segment dictionary for all angles
307
- angles, # Different angles
308
- [box_measurements] * len(angles), # Same box measurements for all angles
309
- [director] * len(angles) # Same director for all angles
310
- ))
311
-
312
- # Find the angle with the maximum alignment
313
- max_alignment = 0
314
- max_angle = 0
315
- for angle, alignment in results:
316
- if alignment > max_alignment:
317
- max_alignment = alignment
318
- max_angle = angle
319
-
320
- return max_angle
321
-
322
- def generate_line_segments_thickness_orientation(
292
+ def orientate_network(
323
293
  data_dict: Dict[str, dict],
324
294
  orientation: List[int],
325
295
  grid_points: int = 360,
326
- box_measurements: List[Tuple[float, float]] = [(0, 0), (0, 1), (1, 1), (1, 0)]
296
+ box_measurements: List[Tuple[float, float]] = [(0, 0), (0, 1), (1, 1), (1, 0)],
297
+ director: np.ndarray = np.array([0, 1])
327
298
  ) -> List[Dict[str, dict]]:
328
299
  """
329
300
  Generates a set of networks of line segments with different thicknesses and orientations, and clips them to fit
@@ -354,36 +325,33 @@ def generate_line_segments_thickness_orientation(
354
325
 
355
326
  # Extract the segment thickness dictionary from the input data
356
327
  segment_thickness_dict = data_dict['segment_thickness_dict']
357
-
358
- # Define the director vector along the y-axis
359
- director = np.array([0, 1])
360
328
 
361
329
  # Find the angle that aligns the network most with the y-axis
362
- max_angle = get_max_alignment_angle(segment_thickness_dict, director, box_measurements, grid_points)
363
-
330
+ max_angle = get_max_alignment(segment_thickness_dict, director, box_measurements, grid_points)
331
+
364
332
  # Store the initial unmodified configuration
365
333
  output = [{'orientation': 'original', 'data_dict': data_dict}]
366
334
 
367
335
  # Loop through each given orientation, rotate, clip, and translate the network
368
336
  for o in orientation:
369
337
  # Compute the rotation angle for the current orientation relative to max alignment
370
- rotate_angle = o - max_angle
338
+ rotate_angle = -max_angle + o
371
339
 
372
340
  # Rotate the network by the computed angle
373
- segment_thickness_dict_new = rotate_network(segment_thickness_dict, rotate_angle=rotate_angle, box_center=box_center)
341
+ segment_thickness_dict_rotated = rotate_network(segment_thickness_dict, rotate_angle=rotate_angle, box_center=box_center)
374
342
 
375
343
  # Clip the rotated network to fit within the bounding box
376
- segment_thickness_dict_new = clip_network(segment_thickness_dict_new, box_measurements=box_measurements)
344
+ segment_thickness_dict_clipped = clip_network(segment_thickness_dict_rotated, box_measurements=box_measurements)
377
345
 
378
346
  # Translate the clipped network to start at the origin (0,0)
379
347
  translation_vector = -np.array(box_measurements[0])
380
- segment_thickness_dict_new = translate_network(segment_thickness_dict_new, translation_vector)
348
+ segment_thickness_dict_translated = translate_network(segment_thickness_dict_clipped, translation_vector)
381
349
 
382
350
  # Prepare a new data dictionary with the transformed segment information
383
351
  data_dict_new = {
384
352
  'segments_dict': None,
385
353
  'polygon_arr': None,
386
- 'segment_thickness_dict': segment_thickness_dict_new,
354
+ 'segment_thickness_dict': segment_thickness_dict_translated,
387
355
  'jammed': None,
388
356
  'generated_config': None
389
357
  }