Skip to content

Commit 62559e6

Browse files
committed
Speed up assembly with registry (and other goodies)
- Improve grading error messages - Change the way deleted operations work (just comment out block output in blockMesh but still take all the data from deleted operations*) - Cache wire length and store wires and edges in a dict for faster addressing - Utilize scipy's KDTree to get rid of plain linear searches for block neighbours (and in many other parts in the code**) * TODO: might result in an error if the block carries an edge that is not shared by any other block ** TODO: get rid of sketch mapper that still uses that old linear search TODO! Face merging is broken! Put the functionality back and write tests that confirm that.
1 parent 05404f5 commit 62559e6

File tree

14 files changed

+231
-74
lines changed

14 files changed

+231
-74
lines changed

examples/advanced/smooth_grader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
vertex.translate([0, 0.8, 0])
2929

3030
mesh.set_default_patch("walls", "wall")
31-
mesh.block_list.update_lengths()
31+
3232
grader = cb.SmoothGrader(mesh, 0.05)
3333
grader.grade()
3434

examples/shape/cylinder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
mesh = cb.Mesh()
66

7-
axis_point_1 = [0.0, 0.0, 0.0]
8-
axis_point_2 = [5.0, 5.0, 0.0]
9-
radius_point_1 = [0.0, 0.0, 2.0]
7+
axis_point_1 = [0, 0, 0]
8+
axis_point_2 = [0, 0, 1]
9+
radius_point_1 = [1, 0, 0]
1010

1111
cylinder = cb.Cylinder(axis_point_1, axis_point_2, radius_point_1)
1212

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from typing import ClassVar, Dict, List, Set, Tuple
2+
3+
from classy_blocks.cbtyping import IndexType, NPPointListType
4+
from classy_blocks.util.constants import EDGE_PAIRS
5+
6+
ConnectionType = Tuple[int, int]
7+
8+
9+
def get_key(index_1: int, index_2: int):
10+
return (min(index_1, index_2), max(index_1, index_2))
11+
12+
13+
class Connection:
14+
"""Connects two points by their index in unique point list"""
15+
16+
def __init__(self, index_1: int, index_2: int):
17+
self.indexes = get_key(index_1, index_2)
18+
19+
def get_other_index(self, index):
20+
if self.indexes[0] == index:
21+
return self.indexes[1]
22+
23+
return self.indexes[0]
24+
25+
def __repr__(self):
26+
return f"Connection {self.indexes[0]}-{self.indexes[1]}"
27+
28+
def __hash__(self):
29+
return hash(self.indexes)
30+
31+
32+
class ConnectionRegistryBase:
33+
edge_pairs: ClassVar[List[ConnectionType]]
34+
35+
def __init__(self, points: NPPointListType, addressing: List[IndexType]):
36+
self.points = points
37+
self.addressing = addressing
38+
39+
self.connections: Dict[ConnectionType, Connection] = {}
40+
self.nodes: Dict[int, Set[Connection]] = {i: set() for i in range(len(self.points))}
41+
42+
for i in range(len(self.addressing)):
43+
cell_indexes = self.addressing[i]
44+
for pair in self.edge_pairs:
45+
index_1 = cell_indexes[pair[0]]
46+
index_2 = cell_indexes[pair[1]]
47+
48+
edge_key = get_key(index_1, index_2)
49+
50+
if edge_key not in self.connections:
51+
connection = Connection(index_1, index_2)
52+
self.connections[edge_key] = connection
53+
self.nodes[index_1].add(connection)
54+
self.nodes[index_2].add(connection)
55+
56+
def get_connected_indexes(self, index: int) -> Set[int]:
57+
return {c.get_other_index(index) for c in self.nodes[index]}
58+
59+
60+
class QuadConnectionRegistry(ConnectionRegistryBase):
61+
edge_pairs: ClassVar = [(0, 1), (1, 2), (2, 3), (3, 0)]
62+
63+
64+
class HexConnectionRegistry(ConnectionRegistryBase):
65+
edge_pairs: ClassVar = EDGE_PAIRS
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from typing import List, Tuple
2+
3+
FaceType = Tuple[int, int, int, int]
4+
5+
6+
def get_key(indexes: List[int]):
7+
return tuple(sorted(indexes))
8+
9+
10+
class Face:
11+
def __init__(self, indexes: FaceType):
12+
self.indexes = indexes
13+
14+
def __hash__(self):
15+
return hash(self.indexes)
16+
17+
def __repr__(self):
18+
return f"Face {self.indexes}"

src/classy_blocks/assemble/navigator.py renamed to src/classy_blocks/assemble/point_registry.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
from typing import List, Set, Tuple, Type, TypeVar
1+
from typing import List, Set, Type, TypeVar
22

33
import numpy as np
44
import scipy.spatial
55

66
from classy_blocks.base.exceptions import VertexNotFoundError
77
from classy_blocks.cbtyping import IndexType, NPPointListType, PointType
8+
from classy_blocks.construct.flat.sketch import Sketch
89
from classy_blocks.construct.operations.operation import Operation
910
from classy_blocks.util import functions as f
1011
from classy_blocks.util.constants import TOL, vector_format
1112

12-
NavigatorType = TypeVar("NavigatorType", bound="NavigatorBase")
13+
PointRegistryType = TypeVar("PointRegistryType", bound="PointRegistryBase")
1314

1415

15-
class NavigatorBase:
16-
"""Searches for, connects and creates unique indexes points, taken from lists of operations"""
16+
class PointRegistryBase:
17+
"""Searches for, connects and creates unique points, taken from lists of elements (quads, hexas, ...)"""
1718

1819
cell_size: int # 4 for quads, 8 for hexas
1920

2021
def __init__(self, flattened_points: NPPointListType, merge_tol: float) -> None:
22+
if len(flattened_points) % self.cell_size != 0:
23+
raise ValueError(f"Number of points not divisible by cell_size: {len(flattened_points)} % {self.cell_size}")
24+
2125
self.merge_tol = merge_tol
2226

2327
# a flattened list of all points, possibly multiple at the same spot;
@@ -29,6 +33,8 @@ def __init__(self, flattened_points: NPPointListType, merge_tol: float) -> None:
2933
self.unique_points = self._compile_unique()
3034
self._unique_point_tree = scipy.spatial.KDTree(self.unique_points)
3135

36+
self.cell_addressing = [self._query_unique(self.find_cell_points(i)) for i in range(self.cell_count)]
37+
3238
def _compile_unique(self) -> NPPointListType:
3339
# create a list of unique vertices, taken from the list of operations
3440
unique_points = []
@@ -83,10 +89,11 @@ def find_cell_points(self, cell: int) -> NPPointListType:
8389

8490
def find_cell_indexes(self, cell: int) -> List[int]:
8591
"""Returns indexes of points that define this cell"""
86-
return self._query_unique(self.find_cell_points(cell))
92+
return self.cell_addressing[cell]
8793

8894
def find_cell_neighbours(self, cell: int) -> List[int]:
8995
"""Returns indexes of this and every touching cell"""
96+
# TODO: remove
9097
cell_points = self.find_cell_points(cell)
9198

9299
indexes = []
@@ -96,38 +103,50 @@ def find_cell_neighbours(self, cell: int) -> List[int]:
96103

97104
return list(set(indexes))
98105

99-
def get_map(self) -> Tuple[NPPointListType, List[IndexType]]:
100-
cell_indexes: List[IndexType] = []
101-
102-
for i in range(len(self._repeated_points) // self.cell_size):
103-
cell_indexes.append(self.find_cell_indexes(i))
104-
105-
return self.unique_points, cell_indexes
106-
107106
@staticmethod
108107
def flatten(points, length) -> NPPointListType:
109108
return np.reshape(points, (length, 3))
110109

111110
@classmethod
112111
def from_addresses(
113-
cls: Type[NavigatorType], points: NPPointListType, addressing: List[IndexType], merge_tol: float = TOL
114-
) -> NavigatorType:
112+
cls: Type[PointRegistryType], points: NPPointListType, addressing: List[IndexType], merge_tol: float = TOL
113+
) -> PointRegistryType:
115114
all_points = cls.flatten(
116115
[np.take(points, addr, axis=0) for addr in addressing], len(addressing) * cls.cell_size
117116
)
118117

119118
return cls(all_points, merge_tol)
120119

120+
@property
121+
def cell_count(self) -> int:
122+
return len(self._repeated_points) // self.cell_size
123+
124+
@property
125+
def point_count(self) -> int:
126+
return len(self.unique_points)
127+
128+
129+
class QuadPointRegistry(PointRegistryBase):
130+
"""A registry of points, taken from a list of quads"""
121131

122-
class QuadNavigator(NavigatorBase):
123132
cell_size = 4
124133

134+
@classmethod
135+
def from_sketch(cls: Type[PointRegistryType], sketch: Sketch, merge_tol: float = TOL) -> PointRegistryType:
136+
return cls(
137+
cls.flatten([face.point_array for face in sketch.faces], len(sketch.faces) * cls.cell_size), merge_tol
138+
)
139+
140+
141+
class HexPointRegistry(PointRegistryBase):
142+
"""A registry of points, taken from a list of hexas"""
125143

126-
class HexNavigator(NavigatorBase):
127144
cell_size = 8
128145

129146
@classmethod
130-
def from_operations(cls: Type[NavigatorType], operations: List[Operation], merge_tol: float = TOL) -> NavigatorType:
147+
def from_operations(
148+
cls: Type[PointRegistryType], operations: List[Operation], merge_tol: float = TOL
149+
) -> PointRegistryType:
131150
all_points = cls.flatten([op.point_array for op in operations], len(operations) * cls.cell_size)
132151

133152
return cls(all_points, merge_tol)

src/classy_blocks/construct/curves/curve.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,23 @@ def get_closest_param(self, point: PointType) -> float:
138138
"""Finds the param on curve where point is the closest to given point;
139139
To improve search speed and reliability, an optional starting
140140
estimation can be supplied."""
141-
param_start = super().get_closest_param(point)
141+
closest_param = super().get_closest_param(point)
142142
point = np.array(point)
143143

144144
result = scipy.optimize.minimize(
145-
lambda t: f.norm(self.get_point(t[0]) - point), (param_start,), bounds=(self.bounds,)
145+
lambda t: f.norm(self.get_point(t[0]) - point), (closest_param,), bounds=(self.bounds,)
146146
)
147147

148148
return result.x[0]
149149

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
157+
150158
def get_point(self, param: float) -> NPPointType:
151159
self._check_param(param)
152160
return self.function(param)

src/classy_blocks/items/block.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def __init__(self, index: int, vertices: List[Vertex]):
3838
# (visible in blockMeshDict, useful for debugging)
3939
self.comment: str = ""
4040

41+
# 'hidden' blocks carry all of the data from the mesh
42+
# but is not inserted into blockMeshDict;
43+
self.visible = True
44+
4145
def add_edge(self, corner_1: int, corner_2: int, edge: Edge):
4246
"""Adds an edge between vertices at specified indexes."""
4347
if not (0 <= corner_1 < 8 and 0 <= corner_2 < 8):
@@ -146,13 +150,14 @@ def format_grading(self) -> str:
146150
@property
147151
def description(self) -> str:
148152
"""hex definition for blockMesh"""
153+
fmt_hidden = "" if self.visible else "// "
149154
fmt_vertices = "( " + " ".join(str(v.index) for v in self.vertices) + " )"
150155
fmt_count = "( " + " ".join([str(axis.count) for axis in self.axes]) + " )"
151156

152157
fmt_grading = self.format_grading()
153158
fmt_comments = f"// {self.index} {self.comment}\n"
154159

155-
return f"\thex {fmt_vertices} {self.cell_zone} {fmt_count} {fmt_grading} {fmt_comments}"
160+
return f"\t{fmt_hidden}hex {fmt_vertices} {self.cell_zone} {fmt_count} {fmt_grading} {fmt_comments}"
156161

157162
def __hash__(self) -> int:
158163
return self.index

src/classy_blocks/lists/block_list.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List, Set
22

3-
from classy_blocks.assemble.navigator import HexNavigator
3+
from classy_blocks.assemble.point_registry import HexPointRegistry
44
from classy_blocks.base.exceptions import UndefinedGradingsError
55
from classy_blocks.items.block import Block
66

@@ -16,7 +16,7 @@ def add(self, block: Block) -> None:
1616
"""Add blocks"""
1717
self.blocks.append(block)
1818

19-
def update_neighbours(self, navigator: HexNavigator) -> None:
19+
def update_neighbours(self, navigator: HexPointRegistry) -> None:
2020
"""Find and assign neighbours of a given block entry"""
2121
for block in self.blocks:
2222
neighbour_indexes = navigator.find_cell_neighbours(block.index)

src/classy_blocks/mesh.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import List, Optional, Set, Union, get_args
44

5-
from classy_blocks.assemble.navigator import HexNavigator
5+
from classy_blocks.assemble.point_registry import HexPointRegistry
66
from classy_blocks.base.exceptions import EdgeNotFoundError
77
from classy_blocks.cbtyping import DirectionType
88
from classy_blocks.construct.assemblies.assembly import Assembly
@@ -99,22 +99,6 @@ def delete(self, operation: Operation) -> None:
9999
the data remains but it will not contribute to the mesh"""
100100
self.deleted.add(operation)
101101

102-
@property
103-
def _operations(self) -> List[Operation]:
104-
operations: List[Operation] = []
105-
106-
for entity in self.depot:
107-
if isinstance(entity, Operation):
108-
ops_to_add = [entity]
109-
else:
110-
ops_to_add = entity.operations
111-
112-
for op in ops_to_add:
113-
if op not in self.deleted:
114-
operations.append(op)
115-
116-
return operations
117-
118102
def _add_geometry(self) -> None:
119103
for entity in self.depot:
120104
if entity.geometry is not None:
@@ -130,8 +114,8 @@ def assemble(self) -> None:
130114
self.block_list.update_lengths()
131115
return
132116

133-
operations = self._operations
134-
navigator = HexNavigator.from_operations(operations)
117+
operations = self.operations
118+
navigator = HexPointRegistry.from_operations(operations)
135119

136120
self.vertex_list.vertices = [Vertex(pos, i) for i, pos in enumerate(navigator.unique_points)]
137121

@@ -164,6 +148,7 @@ def assemble(self) -> None:
164148
block.add_chops(direction, operation.chops[direction])
165149

166150
block.cell_zone = operation.cell_zone
151+
block.visible = operation not in self.deleted
167152

168153
self.block_list.add(block)
169154

0 commit comments

Comments
 (0)