diff --git a/src/classy_blocks/grading/autograding/probe.py b/src/classy_blocks/grading/autograding/probe.py index a115672..b1979f9 100644 --- a/src/classy_blocks/grading/autograding/probe.py +++ b/src/classy_blocks/grading/autograding/probe.py @@ -1,12 +1,15 @@ import functools -from typing import Dict, List, Optional, get_args +from typing import Dict, List, Optional, Set, get_args from classy_blocks.base.exceptions import BlockNotFoundError, NoInstructionError from classy_blocks.items.block import Block +from classy_blocks.items.vertex import Vertex from classy_blocks.items.wires.axis import Axis from classy_blocks.items.wires.wire import Wire from classy_blocks.mesh import Mesh -from classy_blocks.types import ChopTakeType, DirectionType +from classy_blocks.optimize.grid import HexGrid +from classy_blocks.types import ChopTakeType, DirectionType, OrientType +from classy_blocks.util.constants import FACE_MAP @functools.lru_cache(maxsize=3000) # that's for 1000 blocks @@ -18,6 +21,20 @@ def get_block_from_axis(mesh: Mesh, axis: Axis) -> Block: raise RuntimeError("Block for Axis not found!") +@functools.lru_cache(maxsize=2) +def get_defined_wall_vertices(mesh: Mesh) -> Set[Vertex]: + """Returns vertices that are on the 'wall' patches""" + wall_vertices: set[Vertex] = set() + + # explicitly defined walls + for patch in mesh.patches: + if patch.kind == "wall": + for side in patch.sides: + wall_vertices.update(set(side.vertices)) + + return wall_vertices + + class Instruction: """A descriptor that tells in which direction the specific block can be chopped.""" @@ -151,10 +168,46 @@ class Probe: def __init__(self, mesh: Mesh): self.mesh = mesh + + # maps blocks to rows self.catalogue = Catalogue(self.mesh) + # finds blocks' neighbours + self.grid = HexGrid.from_mesh(self.mesh) + def get_row_blocks(self, block: Block, direction: DirectionType) -> List[Block]: return self.catalogue.get_row_blocks(block, direction) def get_rows(self, direction: DirectionType) -> List[Row]: return self.catalogue.rows[direction] + + def get_explicit_wall_vertices(self, block: Block) -> Set[Vertex]: + """Returns vertices from a block that lie on explicitly defined wall patches""" + mesh_vertices = get_defined_wall_vertices(self.mesh) + block_vertices = set(block.vertices) + + return block_vertices.intersection(mesh_vertices) + + def get_default_wall_vertices(self, block: Block) -> Set[Vertex]: + """Returns vertices that lie on default 'wall' patch""" + wall_vertices: Set[Vertex] = set() + + # other sides when mesh has a default wall patch + if self.mesh.patch_list.default["kind"] == "wall": + # find block boundaries + block_index = self.mesh.blocks.index(block) + cell = self.grid.cells[block_index] + + # sides with no neighbours are on boundary + boundaries: List[OrientType] = [ + orient for orient, neighbours in cell.neighbours.items() if neighbours is None + ] + + for orient in boundaries: + wall_vertices.union({block.vertices[i] for i in FACE_MAP[orient]}) + + return wall_vertices + + def get_wall_vertices(self, block: Block) -> Set[Vertex]: + """Returns vertices that are on the 'wall' patches""" + return self.get_explicit_wall_vertices(block).union(self.get_default_wall_vertices(block)) diff --git a/src/classy_blocks/mesh.py b/src/classy_blocks/mesh.py index f967094..489a2ab 100644 --- a/src/classy_blocks/mesh.py +++ b/src/classy_blocks/mesh.py @@ -8,6 +8,7 @@ from classy_blocks.construct.shape import Shape from classy_blocks.construct.stack import Stack from classy_blocks.items.block import Block +from classy_blocks.items.patch import Patch from classy_blocks.items.vertex import Vertex from classy_blocks.lists.block_list import BlockList from classy_blocks.lists.edge_list import EdgeList @@ -243,6 +244,10 @@ def is_assembled(self) -> bool: def vertices(self) -> List[Vertex]: return self.vertex_list.vertices + @property + def patches(self) -> List[Patch]: + return list(self.patch_list.patches.values()) + @property def operations(self) -> List[Operation]: """Returns a list of operations from all entities in depot""" diff --git a/tests/test_grading/test_probe.py b/tests/test_grading/test_probe.py index 24a6ab9..55fdc6b 100644 --- a/tests/test_grading/test_probe.py +++ b/tests/test_grading/test_probe.py @@ -1,9 +1,11 @@ -from typing import get_args +from typing import Set, get_args from parameterized import parameterized from classy_blocks.grading.autograding.probe import Probe, get_block_from_axis +from classy_blocks.items.vertex import Vertex from classy_blocks.mesh import Mesh +from classy_blocks.modify.find.shape import RoundSolidFinder from classy_blocks.types import DirectionType from tests.test_grading.test_autograde import AutogradeTestsBase @@ -98,3 +100,64 @@ def test_get_blocks_cylinder(self, axis, row, blocks): indexes.add(block.index) self.assertSetEqual(indexes, blocks) + + def test_wall_vertices_defined(self) -> None: + """Catch wall vertices from explicitly defined wall patches""" + cylinder = self.get_cylinder() + cylinder.set_outer_patch("outer") + + self.mesh.add(cylinder) + self.mesh.modify_patch("outer", "wall") + self.mesh.assemble() + + probe = Probe(self.mesh) + + finder = RoundSolidFinder(self.mesh, cylinder) + shell_vertices = finder.find_shell(True).union(finder.find_shell(False)) + wall_vertices: Set[Vertex] = set() + + for block in self.mesh.blocks: + wall_vertices.update(probe.get_explicit_wall_vertices(block)) + + self.assertSetEqual(shell_vertices, wall_vertices) + + def test_wall_vertices_default(self) -> None: + """Catch wall vertices from default patch""" + cylinder = self.get_cylinder() + cylinder.set_start_patch("inlet") + cylinder.set_end_patch("outlet") + + self.mesh.set_default_patch("outer", "wall") + self.mesh.assemble() + + probe = Probe(self.mesh) + + finder = RoundSolidFinder(self.mesh, cylinder) + shell_vertices = finder.find_shell(True).union(finder.find_shell(False)) + wall_vertices: Set[Vertex] = set() + + for block in self.mesh.blocks: + wall_vertices.update(probe.get_default_wall_vertices(block)) + + self.assertSetEqual(shell_vertices, wall_vertices) + + def test_wall_vertices_combined(self) -> None: + cylinder = self.get_cylinder() + cylinder.set_end_patch("outlet") + + cylinder.set_start_patch("bottom") + self.mesh.modify_patch("bottom", "wall") + + self.mesh.set_default_patch("outer", "wall") + self.mesh.assemble() + + probe = Probe(self.mesh) + + finder = RoundSolidFinder(self.mesh, cylinder) + shell_vertices = finder.find_shell(True).union(finder.find_shell(False)).union(finder.find_core(False)) + wall_vertices: Set[Vertex] = set() + + for block in self.mesh.blocks: + wall_vertices.update(probe.get_default_wall_vertices(block)) + + self.assertSetEqual(shell_vertices, wall_vertices)