Skip to content

Commit 3c711e6

Browse files
committed
Add tests, resolve issues
1 parent e41539e commit 3c711e6

File tree

17 files changed

+231
-60
lines changed

17 files changed

+231
-60
lines changed

examples/advanced/edge_grading.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import os
22

3-
import numpy as np
4-
53
import classy_blocks as cb
64

75
mesh = cb.Mesh()
@@ -24,11 +22,8 @@
2422
contract.chop(2, start_size=0.1)
2523
mesh.add(contract)
2624

27-
# rotate the end block to demonstrate grading propagation on non-aligned blocks
2825
end = cb.Extrude(contract.get_face("top"), 1)
29-
end.rotate(np.pi, [0, 0, 1])
3026
end.chop(2, start_size=0.1)
31-
3227
mesh.add(end)
3328

3429
mesh.set_default_patch("walls", "wall")

src/classy_blocks/construct/operations/operation.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,21 @@ def chop(self, axis: AxisType, **kwargs: Unpack[ChopArgs]) -> None:
6464
1: along second edge of a face
6565
2: between faces / along operation path
6666
67-
Kwargs: see arguments for Chop object"""
67+
kwargs:
68+
Available grading parameters are:
69+
- start_size: width of start cell
70+
- end_size: width of end cell
71+
- count: cell count in given direction
72+
- c2c_expansion: cell-to-cell expansion ratio (default=1)
73+
- total_expansion: ratio between first and last cell size
74+
75+
You must specify start_size and/or count.
76+
c2c_expansion is optional - will be used to create graded cells
77+
and will default to 1 if not provided.
78+
79+
Use length_ratio for multigrading (see documentation):
80+
https://cfd.direct/openfoam/user-guide/v9-blockMesh/#multi-grading"""
81+
6882
self.chops[axis].append(Chop(**kwargs))
6983

7084
def unchop(self, axis: AxisType) -> None:

src/classy_blocks/grading/chop.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ def copy_preserving(self, inverted: bool = False) -> "Chop":
131131
args[arg] = None
132132

133133
args[self.preserve] = self.results[self.preserve]
134-
chop = Chop(**args)
135134

135+
chop = Chop(**args)
136136
if inverted:
137137
chop.invert()
138138

src/classy_blocks/grading/grading.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,6 @@ def __init__(self, length: float):
5454
self.specification: List[List] = [] # a list of lists [length ratio, count ratio, total expansion]
5555

5656
def add_chop(self, chop: Chop) -> None:
57-
"""Add a grading division to block specification.
58-
Use length_ratio for multigrading (see documentation).
59-
Available grading parameters are:
60-
- start_size: width of start cell
61-
- end_size: width of end cell
62-
- count: cell count in given direction
63-
- c2c_expansion: cell-to-cell expansion ratio (default=1)
64-
- total_expansion: ratio between first and last cell size
65-
66-
You must specify start_size and/or count.
67-
c2c_expansion is optional - will be used to create graded cells
68-
and will default to 1 if not provided.
69-
70-
To reverse grading, use invert=True.
71-
72-
Documentation:
73-
https://cfd.direct/openfoam/user-guide/v9-blockMesh/#multi-grading"""
7457
if not (0 < chop.length_ratio <= 1):
7558
raise ValueError(f"Length ratio must be between 0 and (including) 1, got {chop.length_ratio}")
7659

@@ -160,3 +143,6 @@ def __eq__(self, other):
160143
return False
161144

162145
return True
146+
147+
def __repr__(self) -> str:
148+
return str(self.specification)

src/classy_blocks/items/block.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ def is_defined(self) -> bool:
121121
"""Returns True if counts and gradings are defined for all axes"""
122122
return all(axis.is_defined for axis in self.axes)
123123

124+
def grade(self):
125+
for axis in self.axes:
126+
axis.grade()
127+
124128
def copy_grading(self) -> bool:
125129
"""Attempts to copy grading from a neighbouring block;
126130
returns True if the grading was copied and False in all other cases"""
@@ -132,13 +136,17 @@ def copy_grading(self) -> bool:
132136

133137
return updated
134138

139+
def check_consistency(self) -> None:
140+
for axis in self.axes:
141+
axis.check_consistency()
142+
135143
@property
136144
def indexes(self) -> IndexType:
137145
return [vertex.index for vertex in self.vertices]
138146

139147
def format_grading(self) -> str:
140148
"""Returns the simple/edgeGrading string"""
141-
if all([axis.wires.is_simple for axis in self.axes]): # is_simple
149+
if all(axis.is_simple for axis in self.axes): # is_simple
142150
return (
143151
"simpleGrading ( "
144152
+ self.axes[0].wires.format_single()

src/classy_blocks/items/wires/axis.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,21 @@ def copy_grading(self) -> bool:
9090
for chop in reversed(neighbour.wires.chops):
9191
self.wires.add_chop(chop.copy_preserving(inverted=True))
9292

93-
self.wires.grade()
93+
self.grade()
9494
return True
9595

9696
return False
9797

98+
def grade(self) -> None:
99+
self.wires.grade()
100+
98101
@property
99102
def count(self) -> int:
100103
return self.wires.count
101104

102105
@property
103106
def is_simple(self) -> bool:
104107
return self.wires.is_simple
108+
109+
def check_consistency(self) -> None:
110+
self.wires.check_consistency()

src/classy_blocks/items/wires/manager.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,20 @@ def __iter__(self):
2121
def add_chop(self, chop: Chop) -> None:
2222
self.chops.append(chop)
2323

24+
@abc.abstractmethod
25+
def update(self) -> None:
26+
"""Re-set grading and wires' lengths"""
27+
for wire in self.wires:
28+
wire.grading.length = wire.length
29+
2430
@abc.abstractmethod
2531
def grade(self) -> None:
2632
"""Convert data from user or neighbour to Grading objects on wires"""
2733

2834
@property
29-
@abc.abstractmethod
3035
def is_defined(self) -> bool:
31-
"""Returns True if there's enough data to define a grading on this axis"""
36+
"""Returns True if all gradings are defined on this axis"""
37+
return all(wire.grading.is_defined for wire in self.wires)
3238

3339
@property
3440
@abc.abstractmethod
@@ -61,6 +67,13 @@ def is_simple(self) -> bool:
6167

6268
return True
6369

70+
def check_consistency(self) -> None:
71+
"""Raises an error if not all wires have the same count"""
72+
counts = [wire.grading.count for wire in self.wires]
73+
if len(set(counts)) != 1:
74+
wire_descriptions = [str(wire) for wire in self.wires]
75+
raise InconsistentGradingsError(f"Inconsistent counts on wires {wire_descriptions} ({counts})")
76+
6477

6578
class WireChopManager(WireManagerBase):
6679
"""Responsible for conversion of user-specified Chops
@@ -71,21 +84,16 @@ def __init__(self, wires: List[Wire]):
7184

7285
# Chops and Grading that is done on the whole axis;
7386
# Cell count is defined here. Other values are a matter of each individual Wire.
74-
self.grading = Grading(self.length)
75-
76-
@property
77-
def is_defined(self) -> bool:
78-
"""True if chops have been delegated to each wire and gradings calculated"""
79-
if len(self.chops) == 0:
80-
return False
87+
self.grading = Grading(0)
8188

82-
# either all wires are defined or none is
83-
if not self.wires[0].grading.is_defined:
84-
self.grade()
89+
def update(self):
90+
self.grading.length = self.length
8591

86-
return True
92+
super().update()
8793

8894
def grade(self) -> None:
95+
self.update()
96+
8997
# Create a proper Grading from chops
9098
for chop in self.chops:
9199
self.grading.add_chop(chop)
@@ -108,17 +116,15 @@ class WirePropagateManager(WireManagerBase):
108116
def __init__(self, wires: List[Wire]):
109117
super().__init__(wires)
110118

111-
@property
112-
def is_defined(self):
113-
return self.wires[0].grading.is_defined
119+
def update(self):
120+
super().update()
114121

115122
def grade(self):
116123
"""Checks each wire whether their coincidents (wires from other blocks)
117124
have grading defined already; if so, copy it and return True.
118125
Returns False otherwise"""
119126
self.copy_neighbours()
120127
self.propagate_grading()
121-
self.check_consistency()
122128

123129
def copy_neighbours(self) -> None:
124130
"""Checks for defined neighbours and copies grading from them"""
@@ -148,13 +154,6 @@ def propagate_grading(self) -> None:
148154
for chop in self.chops:
149155
wire.grading.add_chop(chop)
150156

151-
def check_consistency(self):
152-
"""Raises an error if not all wires have the same count"""
153-
counts = [wire.grading.count for wire in self.wires]
154-
if len(set(counts)) != 1:
155-
wire_descriptions = [str(wire) for wire in self.wires]
156-
raise InconsistentGradingsError(f"Inconsistent counts on wires {wire_descriptions} ({counts})")
157-
158157
@property
159158
def count(self):
160159
return sum(chop.results["count"] for chop in self.chops)

src/classy_blocks/lists/block_list.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ def add(self, block: Block) -> None:
1818
self.blocks.append(block)
1919
self.update_neighbours(block)
2020

21+
def grade_blocks(self) -> None:
22+
for block in self.blocks:
23+
block.grade()
24+
2125
def update_neighbours(self, new_block: Block) -> None:
2226
"""Find and assign neighbours of a given block entry"""
2327
for block in self.blocks:
@@ -62,6 +66,10 @@ def propagate_gradings(self):
6266

6367
raise UndefinedGradingsError(message)
6468

69+
def check_consistency(self):
70+
for block in self.blocks:
71+
block.check_consistency()
72+
6573
def clear(self) -> None:
6674
"""Removes created blocks"""
6775
self.blocks.clear()

src/classy_blocks/mesh.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ def assemble(self, skip_edges: bool = False) -> None:
132132
if entity.geometry is not None:
133133
self.add_geometry(entity.geometry)
134134

135+
def grade(self) -> None:
136+
if not self.is_assembled:
137+
raise RuntimeError("Cannot grade a mesh before it is assembled")
138+
139+
self.block_list.grade_blocks()
140+
self.block_list.propagate_gradings()
141+
self.block_list.check_consistency()
142+
135143
def clear(self) -> None:
136144
"""Undoes the assemble() method; clears created blocks and other lists
137145
but leaves added depot items intact"""
@@ -189,7 +197,7 @@ def write(self, output_path: str, debug_path: Optional[str] = None) -> None:
189197

190198
# gradings: define after writing VTK;
191199
# if it is not specified correctly, this will raise an exception
192-
self.block_list.propagate_gradings()
200+
self.grade()
193201

194202
with open(output_path, "w", encoding="utf-8") as output:
195203
output.write(constants.MESH_HEADER)

tests/test_construct/test_assembly.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def test_chop(self):
7575

7676
mesh.add(joint)
7777
mesh.assemble()
78+
mesh.block_list.grade_blocks()
7879
mesh.block_list.propagate_gradings()
7980

8081
def test_set_patches(self):

0 commit comments

Comments
 (0)