Skip to content

Commit 92b1d4d

Browse files
committed
Add edge grading with Chop.preserve keyword
Currently all block gradings are specified as `edgeGrading` (TODO: switch only when necessary). - Add an example to demonstrate the 'preserve' keyword - Remove 'take' keyword - always calculate cell count from average block size in specified axis (TODO: add an option to grade a single edge) - Add keyword hints to Operation.chop() method
1 parent dcc2de2 commit 92b1d4d

File tree

24 files changed

+436
-218
lines changed

24 files changed

+436
-218
lines changed

CHANGELOG.md

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7-
# [1.6.4]
7+
## Unreleased
8+
### Added
9+
- **Edge grading**
10+
11+
## [1.6.4]
812
### Added
913
- MappedSketch.merge() method
1014

1115
### Changed
1216
- Improved blocking in cyclone example
1317

14-
# [1.6.3]
18+
## [1.6.3]
1519
### Changed
1620
- Bugfix: sorting of cells by sensitivity
1721
- Bugfix: sensitivity calculation moved clamps around
@@ -21,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2125
### Removed
2226
- Quality caching (produced invalid results and did not speed up optimization)
2327

24-
# [1.6.2]
28+
## [1.6.2]
2529
### Added
2630
- QuarterSplineRound and QuarterSplineRoundRing; cylinders and rings with an arbitrary, parametrized spline cross-section
2731
- Lofts and Shapes, created with spline side-edges (when multiple sketches for mid-sections are provided)
@@ -30,15 +34,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3034
### Changed
3135
- Bugfixes
3236

33-
# [1.6.1]
37+
## [1.6.1]
3438
### Added
3539
- Assemblies and *Joints
3640
- Examples thereof
3741

3842
### Changed
3943
- Some bugfixes
4044

41-
# [1.6.0]
45+
## [1.6.0]
4246
### Added
4347
- Array element; handling multiple points at once (makes transforms faster)
4448
- Complete overhaul of Optimization:
@@ -55,14 +59,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5559
### Removed
5660
- QuadMap is no longer needed (handled by Grid classes)
5761

58-
# [1.5.3]
62+
## [1.5.3]
5963
### Added
6064
- RoundSolidShape.remove_inner_edges() can now remove edges from a specific face (start, end or both)
6165

6266
### Changed
6367
- Bugfix: invalid cells in round shapes (due to wrong spline point calculation)
6468

65-
# [1.5.2]
69+
## [1.5.2]
6670
### Added
6771
- Spline edges on QuarterDisk, HalfDisk and Disk (FourCoreDisk) to improve mesh quality
6872
- Face.remove_edges() to reset all edges to simple lines
@@ -71,7 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7175
### Changed
7276
- Updated affected tutorials (diffusers, cyclone)
7377

74-
# [1.5.1]
78+
## [1.5.1]
7579
### Added
7680
- Optimization:
7781
- Choice of solver (different problems require - work best - with different solvers)
@@ -86,7 +90,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8690
### Changed
8791
- Improved optimization speed
8892

89-
# [1.5.0]
93+
## [1.5.0]
9094
### Added
9195
- Curves:
9296
- get_tangent()
@@ -114,15 +118,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
114118
- Calling .transform() with a Mirror transformation will warn about creating an inverted block
115119

116120

117-
# [1.4.1]
121+
## [1.4.1]
118122
### Changed
119123
- Improved optimization output
120124

121125
### Removed
122126
- Relaxation within optimization
123127
- Numerical integration of analytic curve lengths; use discretization instead
124128

125-
# [1.4.0]
129+
## [1.4.0]
126130
### Added
127131
- Channel example
128132
- Cyclone example
@@ -157,7 +161,7 @@ that parameter is of a similar magnitudes than points' coordinates.
157161
### Removed
158162
- Junction.delta() is now handled by optimization automatically
159163

160-
# [1.3.3]
164+
## [1.3.3]
161165
### Added
162166
- Airfoil example
163167
- Translation and Rotational Link: move together with vertices being optimized (see the airfoil example)
@@ -172,19 +176,19 @@ that parameter is of a similar magnitudes than points' coordinates.
172176
### Removed
173177
- Curve.get_closest_param() now finds initial_param automatically
174178

175-
# [1.3.2] Curves
179+
## [1.3.2] Curves
176180
### Added
177181
- *Curve objects for dealing with edge specification and optimization
178182

179-
# [1.3.1] Optimization/Backport
183+
## [1.3.1] Optimization/Backport
180184
### Added
181185
- mesh.clear() removes lists of all items that were populated during mesh.assemble()
182186
- mesh.backport() updates user supplied operations' points with results of optimization/modification
183187

184188
### Changed
185189
- Optimizer: under-relaxation for the first optimization iterations
186190

187-
# [1.3.0] Blocking Optimization
191+
## [1.3.0] Blocking Optimization
188192
### Added
189193
- **Blocking Optimization**
190194
- Finders for easier fetching vertices, generated by mesh.assemble()
@@ -208,7 +212,7 @@ that parameter is of a similar magnitudes than points' coordinates.
208212
- Calling .project_side() on an Operation will add the new geometry to edges instead of replacing them (but will replace existing label for the side)
209213

210214

211-
# [1.2.0] Shell Shape
215+
## [1.2.0] Shell Shape
212216
### Added
213217
- A Shell Shape, created from arbitrary set of faces
214218
### Changed

examples/advanced/edge_grading.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import os
2+
3+
import numpy as np
4+
5+
import classy_blocks as cb
6+
7+
mesh = cb.Mesh()
8+
9+
start = cb.Box([0, 0, 0], [1, 1, 0.1])
10+
11+
start.chop(0, start_size=0.1)
12+
start.chop(1, length_ratio=0.5, start_size=0.01, c2c_expansion=1.2, preserve="start_size")
13+
# start.chop(1, length_ratio=0.5, end_size=0.01, c2c_expansion=1 / 1.2, preserve="end_size")
14+
start.chop(2, count=1)
15+
mesh.add(start)
16+
17+
expand_start = start.get_face("right")
18+
expand = cb.Loft(expand_start, expand_start.copy().translate([1, 0, 0]).scale(2))
19+
expand.chop(2, start_size=0.1)
20+
mesh.add(expand)
21+
22+
contract_start = expand.get_face("top")
23+
contract = cb.Loft(contract_start, contract_start.copy().translate([1, 0, 0]).scale(0.25))
24+
contract.chop(2, start_size=0.1)
25+
mesh.add(contract)
26+
27+
end = cb.Extrude(contract.get_face("top"), 1)
28+
end.rotate(np.pi, [0, 0, 1])
29+
end.chop(2, start_size=0.1)
30+
31+
mesh.add(end)
32+
33+
mesh.set_default_patch("walls", "wall")
34+
35+
mesh.write(os.path.join("..", "case", "system", "blockMeshDict"))

examples/complex/airfoil/airfoil.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,12 @@ def get_loft(indexes):
135135
# Keep in mind that not all blocks need exact specification as
136136
# chopping will propagate automatically through blocking
137137
lofts[0].chop(2, count=1) # 1 cell in the 3rd dimension
138-
lofts[1].chop(1, start_size=BL_THICKNESS, c2c_expansion=C2C_EXPANSION, take="max")
139-
lofts[8].chop(1, start_size=CELL_SIZE, take="max")
138+
# keep consistent first cell thickness by using edge grading
139+
lofts[1].chop(1, start_size=BL_THICKNESS, c2c_expansion=C2C_EXPANSION, preserve="start_size")
140+
lofts[8].chop(1, start_size=CELL_SIZE)
140141

141142
for i in (0, 1, 2, 3, 4, 5, 6):
142-
lofts[i].chop(0, start_size=CELL_SIZE, take="max")
143+
lofts[i].chop(0, start_size=CELL_SIZE)
143144

144145
for loft in lofts:
145146
mesh.add(loft)
@@ -195,7 +196,9 @@ def optimize_along_line(point_index, line_index_1, line_index_2):
195196
optimize_along_line(5, 3, 6)
196197

197198
if OPTIMIZE:
198-
optimizer.optimize(tolerance=1e-7, method="SLSQP")
199+
optimizer.optimize(tolerance=1e-3, method="SLSQP")
200+
mesh.backport()
201+
mesh.clear()
199202

200203
### Write the mesh
201204
mesh.modify_patch("topAndBottom", "empty")

examples/complex/heater/heater.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def set_cell_zones(shape: cb.Shape):
4343

4444
# The curved part of heater (and fluid around it); constructed from 4 revolves
4545
heater_arch = cb.RevolvedStack(straight_1.sketch_2, np.pi, [0, 0, 1], [0, 0, 0], 4)
46-
heater_arch.chop(start_size=p.solid_cell_size, take="min")
46+
heater_arch.chop(start_size=p.solid_cell_size)
4747
for shape in heater_arch.shapes:
4848
set_cell_zones(shape)
4949
mesh.add(heater_arch)

showcase/classy_classic_grading.png

283 KB
Loading

showcase/classy_edges_grading.png

295 KB
Loading

src/classy_blocks/base/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ class UndefinedGradingsError(Exception):
1818
define all blocks in the mesh"""
1919

2020

21+
class InconsistentGradingsError(Exception):
22+
"""Raised when cell counts for edges on the same axis is not consistent"""
23+
24+
2125
class ShapeCreationError(Exception):
2226
"""Base class for shape creation errors (invalid parameters/types to
2327
shape constructors)"""

src/classy_blocks/construct/operations/operation.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import warnings
2-
from typing import Dict, List, Optional, TypeVar, Union, get_args
2+
from typing import Dict, List, Optional, Union, get_args
33

44
import numpy as np
5+
from typing_extensions import Unpack
56

67
from classy_blocks.base.element import ElementBase
78
from classy_blocks.base.exceptions import EdgeCreationError
@@ -10,15 +11,13 @@
1011
from classy_blocks.construct.flat.face import Face
1112
from classy_blocks.construct.point import Point
1213
from classy_blocks.grading.chop import Chop
13-
from classy_blocks.types import AxisType, NPPointType, OrientType, PointType, ProjectToType, VectorType
14+
from classy_blocks.types import AxisType, ChopArgs, NPPointType, OrientType, PointType, ProjectToType, VectorType
1415
from classy_blocks.util import constants
1516
from classy_blocks.util import functions as f
1617
from classy_blocks.util.constants import SIDES_MAP
1718
from classy_blocks.util.frame import Frame
1819
from classy_blocks.util.tools import edge_map
1920

20-
OperationT = TypeVar("OperationT", bound="Operation")
21-
2221

2322
class Operation(ElementBase):
2423
"""A base class for all single-block operations
@@ -59,7 +58,7 @@ def add_side_edge(self, corner_idx: int, edge_data: EdgeData) -> None:
5958

6059
self.side_edges[corner_idx] = edge_data
6160

62-
def chop(self, axis: AxisType, **kwargs) -> None:
61+
def chop(self, axis: AxisType, **kwargs: Unpack[ChopArgs]) -> None:
6362
"""Chop the operation (count/grading) in given axis:
6463
0: along first edge of a face
6564
1: along second edge of a face

src/classy_blocks/grading/chop.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import dataclasses
22
from functools import lru_cache
3-
from typing import Callable, List, Optional, Set, Tuple, Union
3+
from typing import Callable, Dict, List, Optional, Set, Tuple, Union
44

55
from classy_blocks.grading import relations as rel
6-
from classy_blocks.types import ChopTakeType
6+
from classy_blocks.types import ChopPreserveType
77

88

99
@dataclasses.dataclass
@@ -58,9 +58,9 @@ class Chop:
5858
end_size: Optional[float] = None
5959
total_expansion: Optional[float] = None
6060
invert: bool = False
61-
take: ChopTakeType = "avg"
61+
preserve: ChopPreserveType = "c2c_expansion"
6262

63-
def __post_init__(self):
63+
def __post_init__(self) -> None:
6464
# default: take c2c_expansion=1 if there's less than 2 parameters given
6565
grading_params = [self.start_size, self.end_size, self.count, self.total_expansion, self.c2c_expansion]
6666
if len(grading_params) - grading_params.count(None) < 2:
@@ -71,25 +71,31 @@ def __post_init__(self):
7171
if self.count is not None:
7272
self.count = max(int(self.count), 1)
7373

74+
# stored results from self.calculate() method;
75+
# this will be used for creating new chops taking self.preserve
76+
# into account. User input (chop arguments) should not be overridden because
77+
# if mesh is modified (optimization etc.) all numbers will be wrong.
78+
self.results: Dict[Union[ChopPreserveType, str], Union[float, int]] = dict()
79+
7480
def calculate(self, length: float) -> Tuple[int, float]:
7581
"""Calculates cell count and total expansion ratio for this chop
7682
by calling functions that take known variables and return new values"""
7783
data = dataclasses.asdict(self)
84+
self.results = data
7885
calculated: Set[str] = set()
7986

80-
for key in data.keys():
87+
for key in self.results.keys():
8188
if data[key] is not None:
8289
calculated.add(key)
8390

84-
for _ in range(20):
85-
if {"count", "total_expansion"}.issubset(calculated):
86-
count = int(data["count"])
87-
total_expansion = data["total_expansion"]
91+
for _ in range(12):
92+
if {"count", "total_expansion", "c2c_expansion", "start_size", "end_size"}.issubset(calculated):
93+
self.results["count"] = int(self.results["count"])
8894

8995
if self.invert:
90-
return count, 1 / total_expansion
96+
data["total_expansion"] = 1 / data["total_expansion"]
9197

92-
return count, total_expansion
98+
return data["count"], data["total_expansion"]
9399

94100
for chop_rel in ChopRelation.get_possible_combinations():
95101
output = chop_rel.output
@@ -106,3 +112,32 @@ def calculate(self, length: float) -> Tuple[int, float]:
106112
calculated.add(output)
107113

108114
raise ValueError(f"Could not calculate count and grading for given parameters: {data}")
115+
116+
def copy_preserving(self, inverted: bool = False) -> "Chop":
117+
"""Creates a copy of this Chop with equal count but
118+
sets other parameters from current data so that
119+
the correct start/end size or c2c is maintained"""
120+
args = dataclasses.asdict(self)
121+
args["count"] = self.results["count"]
122+
123+
for arg in ["total_expansion", "c2c_expansion", "start_size", "end_size"]:
124+
args[arg] = None
125+
126+
# TODO: get rid of if-ing
127+
if self.preserve == "start_size":
128+
if inverted:
129+
args["end_size"] = self.results["start_size"]
130+
else:
131+
args["start_size"] = self.results["start_size"]
132+
elif self.preserve == "end_size":
133+
if inverted:
134+
args["start_size"] = self.results["end_size"]
135+
else:
136+
args["end_size"] = self.results["end_size"]
137+
else:
138+
if inverted:
139+
args["c2c_expansion"] = 1 / self.results["c2c_expansion"]
140+
else:
141+
args["c2c_expansion"] = self.results["c2c_expansion"]
142+
143+
return Chop(**args)

src/classy_blocks/grading/grading.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ class Grading:
4949
def __init__(self, length: float):
5050
# "multi-grading" specification according to:
5151
# https://cfd.direct/openfoam/user-guide/v9-blockMesh/#multi-grading
52-
self.specification: List[List] = [] # a list of lists [length ratio, count ratio, total expansion]
53-
5452
self.length = length
5553

54+
self.specification: List[List] = [] # a list of lists [length ratio, count ratio, total expansion]
55+
5656
def add_chop(self, chop: Chop) -> None:
5757
"""Add a grading division to block specification.
5858
Use length_ratio for multigrading (see documentation).
@@ -101,10 +101,15 @@ def inverted(self) -> "Grading":
101101

102102
return g_inv
103103

104+
@property
105+
def counts(self) -> List[int]:
106+
"""Counts per chop"""
107+
return [d[1] for d in self.specification]
108+
104109
@property
105110
def count(self) -> int:
106111
"""Return number of cells, summed over all sub-divisions"""
107-
return sum(d[1] for d in self.specification)
112+
return sum(self.counts)
108113

109114
@property
110115
def is_defined(self) -> bool:

0 commit comments

Comments
 (0)