Skip to content

Commit cfbe200

Browse files
committed
Fix GradingManager (missing axes for propagation), add tests
1 parent aeb6fb9 commit cfbe200

File tree

11 files changed

+145
-34
lines changed

11 files changed

+145
-34
lines changed

examples/complex/cyclone/cyclone.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def add_regions(regions: list[Region]) -> None:
8080
for clamp in region.get_clamps(mesh):
8181
optimizer.add_clamp(clamp)
8282

83-
optimizer.optimize(tolerance=1e-3, max_iterations=20)
83+
# optimizer.optimize(tolerance=1e-3, max_iterations=20)
8484

8585
# Now Block objects contain optimization results but those are not reflected in
8686
# user-created Operations. Mesh.backport() will copy the data back.

src/classy_blocks/grading/define/chop.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,26 +85,28 @@ def defined_params(self) -> int:
8585
grading_params = [self.start_size, self.end_size, self.count, self.total_expansion, self.c2c_expansion]
8686
return len(grading_params) - grading_params.count(None)
8787

88+
def __post_init__(self):
89+
self.check()
90+
8891
def check(self):
8992
# check that the user did not overdefine the chop;
9093
# if more than 2 parameters are given, the results might change between runs because
9194
# of the iterative calculation
9295
if self.defined_params > 2:
93-
# TODO: test
9496
raise ValueError("Over-defined Chop; speficy at most 2 grading parameters!")
9597

96-
# default: take c2c_expansion=1 if there's less than 2 parameters given
97-
if self.defined_params < 2:
98-
if self.c2c_expansion is None:
99-
self.c2c_expansion = 1
100-
10198
# also: count can only be an integer
10299
if self.count is not None:
103100
self.count = max(int(self.count), 1)
104101

105102
def calculate(self, length: float) -> ChopData:
106103
self.check()
107104

105+
# default: take c2c_expansion=1 if there's less than 2 parameters given
106+
if self.defined_params < 2:
107+
if self.c2c_expansion is None:
108+
self.c2c_expansion = 1
109+
108110
"""Calculates cell count and total expansion ratio for this chop
109111
by calling functions that take known variables and return new values"""
110112
data = dataclasses.asdict(self)

src/classy_blocks/grading/define/collector.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,5 @@ def clear(self) -> None:
3333
def is_edge_chopped(self) -> bool:
3434
return self._edge_chops_count > 0
3535

36-
def __len__(self):
37-
return len(self.axis_chops)
38-
3936
def __getitem__(self, i: DirectionType):
4037
return self.axis_chops[i]

src/classy_blocks/grading/graders/manager.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def __init__(self, wire: Wire):
2222
def copy(from_wire: Wire, to_wire: Wire) -> None:
2323
"""Copies grading from another wire but takes care to orient it properly"""
2424
if to_wire.grading.is_defined:
25-
raise InconsistentGradingsError(f"Copying grading to a defined wire! (From {from_wire} to {to_wire})")
25+
if to_wire.grading != from_wire.grading:
26+
raise InconsistentGradingsError(f"Copying grading to a defined wire! (From {from_wire} to {to_wire})")
2627

2728
to_wire.grading = from_wire.grading.copy(to_wire.length, not to_wire.is_aligned(from_wire))
2829

@@ -140,7 +141,6 @@ def grade(self) -> int:
140141
grader = WireGrader(wire)
141142
# TODO: assign priority to Chop so it's possible
142143
# to tell whether to copy the grading to coincidents or take an existing one from them
143-
# TODO: put this grading-copy-logic into the WireGrader, duh?
144144
grader.assign(grading.copy(wire.length, False))
145145

146146
return grading.count
@@ -152,11 +152,19 @@ def __init__(self, row: Row):
152152
self.axes = set(row.get_axes())
153153

154154
# each wire belongs to an axis; create a lookup dict
155-
self.wire_to_axis: dict[Wire, Axis] = {}
155+
self.wire_to_axis: dict[Wire, set[Axis]] = {}
156156

157157
for axis in self.axes:
158158
for wire in axis.wires:
159-
self.wire_to_axis[wire] = axis
159+
if wire not in self.wire_to_axis:
160+
self.wire_to_axis[wire] = set()
161+
162+
self.wire_to_axis[wire].add(axis)
163+
for coincident in wire.coincidents:
164+
if coincident not in self.wire_to_axis:
165+
self.wire_to_axis[coincident] = set()
166+
167+
self.wire_to_axis[coincident].add(axis)
160168

161169
self.defined: set[Axis] = set(axis for axis in self.axes if axis.is_graded)
162170

@@ -206,7 +214,7 @@ def _propagate_wires(self, axis: Axis) -> set[Axis]:
206214
wire_grader = WireGrader(wire)
207215
wire_grader.copy_to_coincidents()
208216
for coincident in wire.coincidents:
209-
fresh_neighbours.add(self.wire_to_axis[coincident])
217+
fresh_neighbours.update(self.wire_to_axis[coincident])
210218

211219
return fresh_neighbours
212220

src/classy_blocks/items/wires/wire.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,5 @@ def is_graded(self) -> bool:
103103
def __repr__(self):
104104
return f"Wire {self.corners[0]}-{self.corners[1]} ({self.vertices[0].index}-{self.vertices[1].index})"
105105

106-
def __hash__(self):
107-
return self.key
106+
# def __hash__(self):
107+
# return self.key

src/classy_blocks/lists/block_list.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44

55
class BlockList:
6-
"""Handling of the 'blocks' part of blockMeshDict, along with
7-
count/grading propagation and whatnot"""
6+
"""Handling of the 'blocks' part of blockMeshDict"""
87

98
def __init__(self) -> None:
109
self.blocks: list[Block] = []
@@ -13,22 +12,13 @@ def add(self, block: Block) -> None:
1312
"""Add blocks"""
1413
self.blocks.append(block)
1514

16-
def update_neighbours(self, navigator: HexPointRegistry) -> None:
15+
def update_neighbours(self, registry: HexPointRegistry) -> None:
1716
"""Find and assign neighbours of a given block entry"""
1817
for block in self.blocks:
19-
neighbour_indexes = navigator.find_cell_neighbours(block.index)
18+
neighbour_indexes = registry.find_cell_neighbours(block.index)
2019

2120
for i in neighbour_indexes:
2221
block.add_neighbour(self.blocks[i])
2322

24-
def update_lengths(self) -> None:
25-
"""Update lengths on grading objects"""
26-
# Grading on each wire was specified with length 0;
27-
# after edges have been added to blocks, they now have a proper
28-
# value to work with
29-
for block in self.blocks:
30-
for wire in block.wire_list:
31-
wire.update()
32-
3323
def __hash__(self):
3424
return id(self)

src/classy_blocks/mesh.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def grade(self) -> None:
114114
Will fail if the mesh has not been assembled yet and will also raise an
115115
exception if chops are over- or under-defined.
116116
Is called automatically when writing the mesh."""
117+
self.assemble()
118+
117119
assert isinstance(self.dump, AssembledDump) # to pacify type checker
118120

119121
manager = GradingManager(self.dump, self.settings)

src/classy_blocks/write/writer.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ def format_settings(settings: Settings):
5858

5959
for key, value in asdict(settings).items():
6060
if key in ("patch_settings", "merged_patches", "default_patch", "geometry"):
61-
# FIXME: this is a temporary fix, Writer will handle that anyway
6261
continue
6362

6463
key_words = key.split("_")
@@ -86,7 +85,7 @@ def write(self, output_path: str):
8685
output.write(format_list("vertices", self.dump.vertices, format_vertex))
8786
output.write(format_list("blocks", self.dump.blocks, format_block))
8887
output.write(format_list("edges", self.dump.edges, format_edge))
89-
output.write(format_list("boundary", list(self.dump.patch_list.patches.values()), format_patch))
88+
output.write(format_list("boundary", list(self.dump.patches), format_patch))
9089
output.write(format_list("faces", self.dump.face_list.faces, format_face))
9190

9291
output.write(format_settings(self.settings))
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import unittest
2+
3+
from classy_blocks.grading.define.collector import Chop, ChopCollector
4+
5+
6+
class CollectorTests(unittest.TestCase):
7+
def test_chop_edge_single(self):
8+
c = ChopCollector()
9+
10+
c.chop_edge(0, 1, Chop(count=10))
11+
12+
edge_chops = 0
13+
for beam in c.edge_chops.get_all_beams():
14+
if beam:
15+
edge_chops += 1
16+
self.assertEqual(edge_chops, 1)
17+
18+
def test_chop_edge_multiple(self):
19+
c = ChopCollector()
20+
21+
c.chop_edge(0, 1, Chop(count=10))
22+
c.chop_edge(3, 2, Chop(count=10))
23+
24+
edge_chops = 0
25+
for beam in c.edge_chops.get_all_beams():
26+
if beam:
27+
edge_chops += 1
28+
self.assertEqual(edge_chops, 2)
29+
30+
def test_is_not_edge_chopped(self):
31+
c = ChopCollector()
32+
33+
c.chop_axis(0, Chop(count=10))
34+
35+
self.assertFalse(c.is_edge_chopped)
36+
37+
def test_is_edge_chopped(self):
38+
c = ChopCollector()
39+
40+
c.chop_edge(0, 1, Chop(count=10))
41+
42+
self.assertTrue(c.is_edge_chopped)
Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
import numpy as np
55

6-
from classy_blocks.base.exceptions import InconsistentGradingsError
7-
from classy_blocks.cbtyping import DirectionType
6+
from classy_blocks.base.exceptions import InconsistentGradingsError, UndefinedGradingsError
7+
from classy_blocks.cbtyping import DirectionType, GradingSpecType
88
from classy_blocks.construct.edges import Arc
99
from classy_blocks.construct.operations.box import Box
1010
from classy_blocks.construct.operations.connector import Connector
@@ -150,3 +150,70 @@ def test_propagated_count(self):
150150

151151
for block in self.mesh.blocks:
152152
self.assertTrue(" ( 10 10 10 ) simpleGrading" in formats.format_block(block))
153+
154+
155+
class BoxEdgeChopTests(unittest.TestCase):
156+
"""Edge chopping on a single operation"""
157+
158+
def setUp(self):
159+
self.box = Box([0, 0, 0], [1, 1, 1])
160+
self.mesh = Mesh()
161+
162+
self.mesh.add(self.box)
163+
164+
def finalize(self):
165+
self.mesh.assemble()
166+
self.mesh.grade()
167+
168+
def get_grading(self, direction: DirectionType, i_wire: int) -> list[GradingSpecType]:
169+
self.finalize()
170+
171+
return self.mesh.blocks[0].axes[direction].wires[i_wire].grading.specification
172+
173+
def test_edge_chop_solo_fail(self):
174+
"""Cannot use edge chopping if the mesh is not fully defined"""
175+
self.box.chop_edge(0, 1, count=30)
176+
177+
with self.assertRaises(UndefinedGradingsError):
178+
self.finalize()
179+
180+
def test_edge_chop_solo_success(self):
181+
for i in get_args(DirectionType):
182+
self.box.chop(i, count=(i + 1) * 5)
183+
184+
self.box.chop_edge(0, 1, start_size=0.05)
185+
186+
self.finalize()
187+
188+
# one edge is graded, the rest are simple
189+
self.assertAlmostEqual(self.get_grading(0, 0)[0][2], 9.043586, places=5)
190+
self.assertAlmostEqual(self.get_grading(0, 1)[0][2], 1)
191+
192+
def test_edge_chop_multiple(self):
193+
"""Do multiple chops and let the last chop define the remaining count automatically"""
194+
195+
self.box.chop(0, count=20)
196+
self.box.chop(1, count=10)
197+
self.box.chop(2, count=10)
198+
199+
self.box.chop_edge(0, 1, length_ratio=0.49, start_size=0.1) # 5 cells
200+
self.box.chop_edge(0, 1, length_ratio=0.51, end_size=0.05) # the rest
201+
202+
self.finalize()
203+
204+
self.assertEqual(self.get_grading(0, 0)[0][1], 5)
205+
self.assertEqual(self.get_grading(0, 0)[1][1], 15)
206+
207+
def test_clear(self):
208+
for i in get_args(DirectionType):
209+
self.box.chop(i, count=(i + 1) * 5)
210+
self.box.chop_edge(0, 1, start_size=0.5)
211+
self.box.chop_edge(1, 2, count=30)
212+
213+
self.box.chops.clear()
214+
215+
self.assertListEqual(self.box.chops.axis_chops[0], [])
216+
self.assertListEqual(self.box.chops.axis_chops[1], [])
217+
self.assertListEqual(self.box.chops.axis_chops[2], [])
218+
219+
self.assertFalse(self.box.chops.is_edge_chopped)

0 commit comments

Comments
 (0)