Skip to content

Commit

Permalink
Rework all of chopping and propagation
Browse files Browse the repository at this point in the history
Edge chop is thrown out because it burdens the user with
micromanagement. Instead, the 'take' parameter is introduced back into
Chop.

Grading and Chops are now friends and grading specification got smarter.

Propagation in block_list is now much simpler.

The Loft example with edge grading produced bad quality cells so it was
changed.
  • Loading branch information
FranzBangar committed Sep 13, 2024
1 parent cae95df commit 0197ffc
Show file tree
Hide file tree
Showing 26 changed files with 456 additions and 429 deletions.
28 changes: 0 additions & 28 deletions examples/advanced/edge_chop.py

This file was deleted.

2 changes: 1 addition & 1 deletion examples/advanced/edge_grading.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@

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

mesh.write(os.path.join("..", "case", "system", "blockMeshDict"))
mesh.write(os.path.join("..", "case", "system", "blockMeshDict"), debug_path="debug.vtk")
8 changes: 4 additions & 4 deletions examples/complex/airfoil/airfoil.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
FILE_NAME = "naca2414.dat"
ANGLE_OF_ATTACK = 20 # in degrees
CHORD = 0.5 # desired chord (provided the one from points is 1)
OPTIMIZE = True # Set to False to skip optimization
OPTIMIZE = False # Set to False to skip optimization

CELL_SIZE = 0.025
BL_THICKNESS = 0.001 # thickness of boundary layer cells
Expand Down Expand Up @@ -136,11 +136,11 @@ def get_loft(indexes):
# chopping will propagate automatically through blocking
lofts[0].chop(2, count=1) # 1 cell in the 3rd dimension
# keep consistent first cell thickness by using edge grading
lofts[1].chop(1, start_size=BL_THICKNESS, c2c_expansion=C2C_EXPANSION, preserve="start_size")
lofts[8].chop(1, start_size=CELL_SIZE)
lofts[1].chop(1, start_size=BL_THICKNESS, c2c_expansion=C2C_EXPANSION, take="max", preserve="start_size")
lofts[8].chop(1, start_size=CELL_SIZE, take="max")

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

for loft in lofts:
mesh.add(loft)
Expand Down
2 changes: 1 addition & 1 deletion examples/complex/heater/heater.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def set_cell_zones(shape: cb.Shape):

# The curved part of heater (and fluid around it); constructed from 4 revolves
heater_arch = cb.RevolvedStack(straight_1.sketch_2, np.pi, [0, 0, 1], [0, 0, 0], 4)
heater_arch.chop(start_size=p.solid_cell_size)
heater_arch.chop(start_size=p.solid_cell_size, take="min")
for shape in heater_arch.shapes:
set_cell_zones(shape)
mesh.add(heater_arch)
Expand Down
6 changes: 3 additions & 3 deletions examples/operation/loft.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
# 4 points for face corners
[[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]],
# edges: arc between 0-1, line between 1-2, arc between 2-3, line between 3-0
[cb.Arc([0.5, -0.25, 0]), None, cb.Arc([0.5, 1.25, 0]), None],
[cb.Arc([0.5, -0.2, 0]), None, cb.Arc([0.5, 1.2, 0]), None],
)

top_face = cb.Face(
[[0, 0, 2], [1, 0, 2], [1, 1, 2], [0, 1, 2]], [None, cb.Arc([1.25, 0.5, 2]), None, cb.Arc([-0.25, 0.5, 2])]
[[0, 0, 2], [1, 0, 2], [1, 1, 2], [0, 1, 2]], [None, cb.Arc([1.2, 0.5, 2]), None, cb.Arc([-0.2, 0.5, 2])]
)

loft = cb.Loft(bottom_face, top_face)
loft.add_side_edge(0, cb.PolyLine([[0.15, 0.15, 0.5], [0.2, 0.2, 1.0], [0.15, 0.15, 1.5]])) # corners 0 - 4
loft.add_side_edge(0, cb.PolyLine([[0.1, 0.1, 0.5], [0.15, 0.15, 1.0], [0.1, 0.1, 1.5]])) # corners 0 - 4
loft.add_side_edge(1, cb.Arc([0.9, 0.1, 1])) # 1 - 5
loft.add_side_edge(2, cb.Arc([0.9, 0.9, 1])) # 2 - 6
loft.add_side_edge(3, cb.Arc([0.1, 0.9, 1])) # 3 - 7
Expand Down
30 changes: 10 additions & 20 deletions src/classy_blocks/construct/operations/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from classy_blocks.types import AxisType, ChopArgs, NPPointType, OrientType, PointType, ProjectToType, VectorType
from classy_blocks.util import constants
from classy_blocks.util import functions as f
from classy_blocks.util.chop_collector import ChopCollector
from classy_blocks.util.constants import SIDES_MAP
from classy_blocks.util.frame import Frame
from classy_blocks.util.tools import edge_map
Expand All @@ -35,8 +34,7 @@ def __init__(self, bottom_face: Face, top_face: Face):
self.side_patches: List[Optional[str]] = [None, None, None, None]

# instructions for cell counts and gradings
# self.chops: Dict[AxisType, List[Chop]] = {0: [], 1: [], 2: []}
self.chops = ChopCollector()
self.chops: Dict[AxisType, List[Chop]] = {0: [], 1: [], 2: []}

# optionally, put the block in a cell zone
self.cell_zone = ""
Expand All @@ -60,13 +58,7 @@ def add_side_edge(self, corner_idx: int, edge_data: EdgeData) -> None:

self.side_edges[corner_idx] = edge_data

def chop(
self,
axis: Optional[AxisType] = None,
corner_1: Optional[int] = None,
corner_2: Optional[int] = None,
**kwargs: Unpack[ChopArgs],
) -> None:
def chop(self, axis: AxisType, **kwargs: Unpack[ChopArgs]) -> None:
"""Chop the operation (count/grading) either in given axis:
0: along first edge of a face
1: along second edge of a face
Expand All @@ -92,18 +84,16 @@ def chop(
Use length_ratio for multigrading (see documentation):
https://cfd.direct/openfoam/user-guide/v9-blockMesh/#multi-grading"""

chop = Chop(**kwargs)
self.chops[axis].append(Chop(**kwargs))

if axis is not None:
self.chops.add_axis_chop(axis, chop)
elif corner_1 is not None and corner_2 is not None:
self.chops.add_edge_chop(corner_1, corner_2, chop)
else:
raise ValueError("Provide axis or two corners for chopping")
def unchop(self, axis: Optional[AxisType] = None) -> None:
"""Removes existing chops from an operation (comes handy after copying etc.)"""
if axis is None:
for i in get_args(AxisType):
self.chops[i] = []
return

def unchop(self) -> None:
"""Removes all existing chops from an operation (comes handy after copying etc.)"""
self.chops.clear_all()
self.chops[axis] = []

def project_corner(self, corner: int, label: ProjectToType) -> None:
"""Project the vertex at given corner (local index 0...7) to a single
Expand Down
66 changes: 44 additions & 22 deletions src/classy_blocks/grading/chop.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Callable, List, Optional, Set, Union

from classy_blocks.grading import relations as rel
from classy_blocks.types import ChopPreserveType, ChopTakeType
from classy_blocks.types import ChopPreserveType, ChopTakeType, GradingSpecType


@dataclasses.dataclass
Expand Down Expand Up @@ -47,27 +47,39 @@ def get_possible_combinations() -> List["ChopRelation"]:

@dataclasses.dataclass
class ChopData:
"""Data that the user inputs for chopping parameters;
also a collection of results from Chop.calculate()"""

length_ratio: float = 1.0
start_size: Optional[float] = None
c2c_expansion: Optional[float] = None
count: Optional[int] = None
end_size: Optional[float] = None
total_expansion: Optional[float] = None
"""A collection of results from Chop.calculate()"""

# TODO: combine with Chop? (not the same thing, though?)
length_ratio: float
start_size: float
c2c_expansion: float
count: int
end_size: float
total_expansion: float
take: ChopTakeType = "avg"
preserve: ChopPreserveType = "c2c_expansion"
preserve: ChopPreserveType = "total_expansion"

def get_specification(self, invert: bool) -> GradingSpecType:
if invert:
return (self.length_ratio, self.count, 1 / self.total_expansion)

return (self.length_ratio, self.count, self.total_expansion)


@dataclasses.dataclass
class Chop(ChopData):
class Chop:
"""A single 'chop' represents a division in Grading object;
user-provided arguments are stored in this object and used
for creation of Gradient object"""

# The data inherited from ChopData is entered by the user and is never modified;
# whatever is calculated in .calculate() is stored in a new ChopData and returned
length_ratio: float = 1.0
start_size: Optional[float] = None
c2c_expansion: Optional[float] = None
count: Optional[int] = None
end_size: Optional[float] = None
total_expansion: Optional[float] = None
take: ChopTakeType = "avg"
preserve: ChopPreserveType = "total_expansion"

def __post_init__(self) -> None:
# default: take c2c_expansion=1 if there's less than 2 parameters given
Expand Down Expand Up @@ -111,13 +123,23 @@ def calculate(self, length: float) -> ChopData:

raise ValueError(f"Could not calculate count and grading for given parameters: {data}")

def invert(self) -> None:
"""Modifies this chop so that grading will have an opposite orientation,
a.k.a. start -> end and c2c -> 1/c2c."""
self.end_size, self.start_size = self.start_size, self.end_size
def copy(self, length: float) -> "Chop":
"""Returns a new Chop with same count but different
total_expansion, keeping the 'preserve'd value constant;
'length' argument refers to the original length (not the copied-to)"""
this_def = dataclasses.asdict(self)
this_data = dataclasses.asdict(self.calculate(length))
preserve_value = this_data[self.preserve]

# create a copy of this Chop with equal count but
# set other parameters from current data so that
# the correct start/end size or c2c is maintained"""
new_args = this_data
new_args["count"] = this_data["count"]

for arg in ["total_expansion", "c2c_expansion", "start_size", "end_size"]:
new_args[arg] = None

if self.c2c_expansion is not None:
self.c2c_expansion = 1 / self.c2c_expansion
new_args[this_def["preserve"]] = preserve_value

if self.total_expansion is not None:
self.total_expansion = 1 / self.total_expansion
return Chop(**new_args)
Loading

0 comments on commit 0197ffc

Please sign in to comment.