Skip to content

Commit 07d8852

Browse files
committed
Partially optimize mesh assembly
ACHTUNG! Still not fixed: face merging & duplicate vertices
1 parent 62559e6 commit 07d8852

File tree

21 files changed

+244
-134
lines changed

21 files changed

+244
-134
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ license = { file = "LICENSE" }
77
keywords = ["classy_blocks", "OpenFOAM", "blockMesh"]
88
authors = [{ name = "Nejc Jurkovic", email = "kandelabr@gmail.com" }]
99
requires-python = ">=3.8"
10-
dependencies = ["numpy", "scipy", "nptyping"]
10+
dependencies = ["numpy", "scipy", "nptyping", "numba"]
1111

1212
[project.urls]
1313
"Homepage" = "https://github.com/damogranlabs/classy_blocks"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from typing import Dict, List, Set
2+
3+
from classy_blocks.cbtyping import IndexType
4+
from classy_blocks.util import functions as f
5+
6+
7+
class CellRegistry:
8+
def __init__(self, addressing: List[IndexType]):
9+
self.addressing = addressing
10+
11+
# build a map of cells that belong to each point;
12+
# first, find the maximum point index
13+
max_index = max(f.flatten_2d_list(addressing))
14+
15+
self.near_cells: Dict[int, Set[int]] = {i: set() for i in range(max_index + 1)}
16+
17+
for i_cell, cell_indexes in enumerate(self.addressing):
18+
for i_point in cell_indexes:
19+
self.near_cells[i_point].add(i_cell)
20+
21+
def get_near_cells(self, point_index: int) -> Set[int]:
22+
return self.near_cells[point_index]
Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,49 @@
1-
from typing import List, Tuple
1+
import abc
2+
from typing import ClassVar, Dict, List, Set, Tuple, Type
23

3-
FaceType = Tuple[int, int, int, int]
4+
from classy_blocks.cbtyping import IndexType, OrientType
5+
from classy_blocks.optimize.cell import CellBase, HexCell, QuadCell
46

57

6-
def get_key(indexes: List[int]):
7-
return tuple(sorted(indexes))
8+
class FaceRegistryBase(abc.ABC):
9+
cell_type: ClassVar[Type[CellBase]]
810

11+
def get_key_from_side(self, cell: int, side: OrientType) -> Tuple:
12+
cell_indexes = self.addressing[cell]
13+
face_corners = self.orient_indexes[side]
14+
face_indexes = [cell_indexes[c] for c in face_corners]
915

10-
class Face:
11-
def __init__(self, indexes: FaceType):
12-
self.indexes = indexes
16+
return tuple(sorted(face_indexes))
1317

14-
def __hash__(self):
15-
return hash(self.indexes)
18+
def __init__(self, addressing: List[IndexType]):
19+
self.addressing = addressing
1620

17-
def __repr__(self):
18-
return f"Face {self.indexes}"
21+
# this is almost equal to constants.FACE_MAP except
22+
# faces are oriented so that they point towards cell centers
23+
self.orient_indexes: Dict[OrientType, IndexType] = {
24+
name: self.cell_type.side_indexes[i] for i, name in enumerate(self.cell_type.side_names)
25+
}
26+
27+
# will hold faces, accessible in O(1) time, and their coincident cell(s);
28+
# boundary faces will have a single coincident cell, internal two
29+
self.faces: Dict[Tuple, Set[int]] = {}
30+
31+
for cell in range(len(self.addressing)):
32+
for side in self.orient_indexes.keys():
33+
face_key = self.get_key_from_side(cell, side)
34+
35+
if face_key not in self.faces:
36+
self.faces[face_key] = set()
37+
38+
self.faces[face_key].add(cell)
39+
40+
def get_cells(self, cell: int, side: OrientType) -> Set[int]:
41+
return self.faces[self.get_key_from_side(cell, side)]
42+
43+
44+
class QuadFaceRegistry(FaceRegistryBase):
45+
cell_type = QuadCell
46+
47+
48+
class HexFaceRegistry(FaceRegistryBase):
49+
cell_type = HexCell

src/classy_blocks/assemble/point_registry.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def _compile_unique(self) -> NPPointListType:
4141
handled_indexes: Set[int] = set()
4242

4343
for point in self._repeated_points:
44-
coincident_indexes = self._repeated_point_tree.query_ball_point(point, r=self.merge_tol, workers=-1)
44+
coincident_indexes = self._repeated_point_tree.query_ball_point(point, r=self.merge_tol, workers=1)
4545

4646
if set(coincident_indexes).isdisjoint(handled_indexes):
4747
# this vertex hasn't been handled yet
@@ -52,15 +52,15 @@ def _compile_unique(self) -> NPPointListType:
5252

5353
def _query_unique(self, positions) -> List[int]:
5454
"""A shortcut to KDTree.query_ball_point()"""
55-
result = self._unique_point_tree.query_ball_point(positions, r=self.merge_tol, workers=-1)
55+
result = self._unique_point_tree.query_ball_point(positions, r=self.merge_tol, workers=1)
5656

5757
if len(np.shape(positions)) > 1:
5858
return f.flatten_2d_list(result)
5959

6060
return result
6161

6262
def _query_repeated(self, positions) -> List[int]:
63-
result = self._repeated_point_tree.query_ball_point(positions, r=self.merge_tol, workers=-1)
63+
result = self._repeated_point_tree.query_ball_point(positions, r=self.merge_tol, workers=1)
6464

6565
if len(np.shape(positions)) > 1:
6666
return f.flatten_2d_list(result)

src/classy_blocks/construct/assemblies/joints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(
3636
for loft in self.shell:
3737
point_1 = loft.top_face.points[1].position
3838
point_2 = loft.top_face.points[2].position
39-
edge_points = f.divide_arc(axis, axis_point_2, point_1, point_2, 5)
39+
edge_points = f.divide_arc(axis_point_2, point_1, point_2, 5)
4040
loft.top_face.add_edge(1, Spline(edge_points))
4141

4242
for loft in self.operations:

src/classy_blocks/construct/curves/curve.py

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,7 @@ def length(self) -> float:
6060

6161
@abc.abstractmethod
6262
def get_closest_param(self, point: PointType) -> float:
63-
"""Finds the parameter on curve where point is the closest to given point;
64-
To improve search speed and reliability, an optional starting
65-
estimation can be supplied."""
66-
# because curves can have all sorts of shapes, find
67-
# initial guess by checking distance to discretized points
68-
point = np.array(point)
69-
all_points = self.discretize()
70-
71-
distances = np.array([f.norm(p - point) for p in all_points])
72-
params = np.linspace(self.bounds[0], self.bounds[1], num=len(distances))
73-
74-
i_distance = np.argmin(distances)
75-
return params[i_distance]
63+
"""Finds the parameter on curve where point is the closest to given point."""
7664

7765
def get_param_at_length(self, length: float) -> float:
7866
"""Returns parameter at specified length along the curve"""
@@ -135,25 +123,28 @@ def discretize(
135123
return np.array([self.function(t) for t in params])
136124

137125
def get_closest_param(self, point: PointType) -> float:
138-
"""Finds the param on curve where point is the closest to given point;
139-
To improve search speed and reliability, an optional starting
140-
estimation can be supplied."""
141-
closest_param = super().get_closest_param(point)
126+
"""Finds the param on curve where point is the closest to given point"""
127+
# because curves can have all sorts of shapes, find
128+
# initial guess by checking distance to discretized points
142129
point = np.array(point)
130+
all_points = self.discretize()
143131

144-
result = scipy.optimize.minimize(
145-
lambda t: f.norm(self.get_point(t[0]) - point), (closest_param,), bounds=(self.bounds,)
146-
)
132+
distances = np.linalg.norm(all_points.T - point[:, None], axis=0)
133+
params = np.linspace(self.bounds[0], self.bounds[1], num=len(distances))
147134

148-
return result.x[0]
135+
i_smallest = int(np.argmin(distances))
136+
i_prev = max(i_smallest - 1, 0)
137+
i_next = min(i_smallest + 1, len(distances) - 1)
138+
139+
param_start = params[i_prev]
140+
param_end = params[i_next]
141+
142+
result = scipy.optimize.minimize_scalar(
143+
lambda t: f.norm(self.get_point(t) - point),
144+
bounds=(param_start, param_end),
145+
)
149146

150-
# TODO: optimize by using a simpler function (?)
151-
# param_start = super().get_closest_param(point)
152-
# param_end = (1 + closest_param) / 2
153-
# result = scipy.optimize.minimize_scalar(
154-
# lambda t: f.norm(self.get_point(t) - point), bounds=(param_start, param_end), method="bounded"
155-
# )
156-
# return result.x
147+
return result.x
157148

158149
def get_point(self, param: float) -> NPPointType:
159150
self._check_param(param)

src/classy_blocks/construct/curves/discrete.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,15 @@ def get_length(self, param_from: Optional[float] = None, param_to: Optional[floa
4646

4747
def get_closest_param(self, point: PointType) -> float:
4848
"""Returns the index of point on this curve where distance to supplied
49-
point is the smallest.
49+
point is the smallest."""
50+
point = np.array(point)
51+
all_points = self.discretize()
5052

51-
For DiscreteCurve, argument param_start is ignored since all points are checked."""
52-
return super().get_closest_param(point)
53+
distances = np.linalg.norm(all_points.T - point[:, None], axis=0)
54+
params = np.linspace(self.bounds[0], self.bounds[1], num=len(distances))
55+
56+
i_distance = np.argmin(distances)
57+
return params[i_distance]
5358

5459
@property
5560
def center(self):

src/classy_blocks/grading/autograding/grader.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def __init__(self, mesh: Mesh, rules: ChopRules):
1515
self.rules = rules
1616

1717
self.mesh.assemble()
18+
self.mesh.block_list.update_lengths() # TODO: move to a better place
1819
self.probe = Probe(self.mesh)
1920

2021
def _chop_wire(self, wire: Wire, chops: List[Chop]) -> None:

src/classy_blocks/items/edges/arcs/angle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def arc_from_theta(edge_point_1: PointType, edge_point_2: PointType, angle: floa
3737

3838
center = pm - length * axis / 2 - rm * mag_chord / 2 / np.tan(angle / 2)
3939

40-
return f.arc_mid(axis, center, edge_point_1, edge_point_2)
40+
return f.arc_mid(center, edge_point_1, edge_point_2)
4141

4242

4343
@dataclasses.dataclass

src/classy_blocks/items/edges/arcs/origin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def arc_from_origin(
7373
return arc_from_origin(p1, p3, new_center, False)
7474

7575
# done, return the calculated point
76-
return f.arc_mid(axis, center, edge_point_1, edge_point_2)
76+
return f.arc_mid(center, edge_point_1, edge_point_2)
7777

7878

7979
@dataclasses.dataclass
@@ -82,6 +82,7 @@ class OriginEdge(ArcEdgeBase):
8282

8383
data: edges.Origin
8484

85+
# TODO: make this accessible to the user
8586
adjust_center: ClassVar[bool] = True
8687

8788
@property

0 commit comments

Comments
 (0)