From 15ebd4194d29c108c7eb2e573f6894a3f61d06a8 Mon Sep 17 00:00:00 2001 From: Nejc Jurkovic Date: Fri, 25 Oct 2024 11:10:27 +0200 Subject: [PATCH 1/3] Add tests for probe etc. --- tests/test_grading/test_autograde.py | 38 ++++++++++++++ .../test_probe.py} | 49 ++++++++----------- tests/{ => test_grading}/test_relations.py | 0 3 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 tests/test_grading/test_autograde.py rename tests/{test_autograde.py => test_grading/test_probe.py} (67%) rename tests/{ => test_grading}/test_relations.py (100%) diff --git a/tests/test_grading/test_autograde.py b/tests/test_grading/test_autograde.py new file mode 100644 index 0000000..59514fa --- /dev/null +++ b/tests/test_grading/test_autograde.py @@ -0,0 +1,38 @@ +import unittest + +from classy_blocks.construct.flat.sketches.grid import Grid +from classy_blocks.construct.shapes.cylinder import Cylinder +from classy_blocks.construct.shapes.frustum import Frustum +from classy_blocks.construct.stack import ExtrudedStack +from classy_blocks.grading.autograding.grader import HighReGrader +from classy_blocks.grading.autograding.params import HighReChopParams +from classy_blocks.mesh import Mesh + + +class AutogradeTestsBase(unittest.TestCase): + def get_stack(self) -> ExtrudedStack: + # create a simple 3x3 grid for easy navigation + base = Grid([0, 0, 0], [1, 1, 0], 3, 3) + return ExtrudedStack(base, 1, 3) + + def get_cylinder(self) -> Cylinder: + return Cylinder([0, 0, 0], [1, 0, 0], [0, 1, 0]) + + def get_frustum(self) -> Frustum: + return Frustum([0, 0, 0], [1, 0, 0], [0, 1, 0], 0.3) + + def setUp(self): + self.mesh = Mesh() + + +class GraderTests(AutogradeTestsBase): + def test_highre_cylinder(self): + self.mesh.add(self.get_cylinder()) + self.mesh.assemble() + # TODO: Hack! Un-hack! + self.mesh.block_list.update() + + params = HighReChopParams(0.025) + grader = HighReGrader(self.mesh, params) + + grader.grade() diff --git a/tests/test_autograde.py b/tests/test_grading/test_probe.py similarity index 67% rename from tests/test_autograde.py rename to tests/test_grading/test_probe.py index 6c4c12a..24a6ab9 100644 --- a/tests/test_autograde.py +++ b/tests/test_grading/test_probe.py @@ -1,32 +1,36 @@ -import unittest from typing import get_args from parameterized import parameterized -from classy_blocks.construct.flat.sketches.grid import Grid -from classy_blocks.construct.shapes.cylinder import Cylinder -from classy_blocks.construct.stack import ExtrudedStack -from classy_blocks.grading.autograding.grader import HighReGrader -from classy_blocks.grading.autograding.params import HighReChopParams from classy_blocks.grading.autograding.probe import Probe, get_block_from_axis from classy_blocks.mesh import Mesh from classy_blocks.types import DirectionType +from tests.test_grading.test_autograde import AutogradeTestsBase -class AutogradeTestsBase(unittest.TestCase): - def get_stack(self) -> ExtrudedStack: - # create a simple 3x3 grid for easy navigation - base = Grid([0, 0, 0], [1, 1, 0], 3, 3) - return ExtrudedStack(base, 1, 3) +class ProbeTests(AutogradeTestsBase): + def test_block_from_axis_fail(self): + mesh_1 = self.mesh + mesh_1.add(self.get_stack()) + mesh_1.assemble() - def get_cylinder(self) -> Cylinder: - return Cylinder([0, 0, 0], [1, 0, 0], [0, 1, 0]) + mesh_2 = Mesh() + mesh_2.add(self.get_stack()) + mesh_2.assemble() - def setUp(self): - self.mesh = Mesh() + with self.assertRaises(RuntimeError): + get_block_from_axis(mesh_1, mesh_2.blocks[0].axes[0]) + @parameterized.expand((("min", 0.196889), ("max", 0.8), ("avg", 0.470923))) + def test_get_row_length(self, take, length): + self.mesh.add(self.get_frustum()) + self.mesh.assemble() + + probe = Probe(self.mesh) + row = probe.get_rows(0)[0] + + self.assertAlmostEqual(row.get_length(take), length, places=4) -class ProbeTests(AutogradeTestsBase): @parameterized.expand(((0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (2, 2))) def test_get_blocks_on_layer(self, block, axis): self.mesh.add(self.get_stack()) @@ -94,16 +98,3 @@ def test_get_blocks_cylinder(self, axis, row, blocks): indexes.add(block.index) self.assertSetEqual(indexes, blocks) - - -class GraderTests(AutogradeTestsBase): - def test_highre_cylinder(self): - self.mesh.add(self.get_cylinder()) - self.mesh.assemble() - # TODO: Hack! Un-hack! - self.mesh.block_list.update() - - params = HighReChopParams(0.025) - grader = HighReGrader(self.mesh, params) - - grader.grade() diff --git a/tests/test_relations.py b/tests/test_grading/test_relations.py similarity index 100% rename from tests/test_relations.py rename to tests/test_grading/test_relations.py From 40985351a0599a1c70f045e1bdc9e21e6f80fd82 Mon Sep 17 00:00:00 2001 From: Nejc Jurkovic Date: Fri, 25 Oct 2024 12:45:45 +0200 Subject: [PATCH 2/3] Remove grading params from user interface; Users now only have to import the appropriate grader. Also, sort out confusion with exceptions and whatnot. Also also, cleanup some TODO work --- examples/advanced/autograding_highre.py | 13 +-- examples/advanced/low_re_chops.py | 37 ------- examples/complex/cyclone/geometry.py | 5 +- examples/shape/quarter_cylinder.py | 6 +- src/classy_blocks/base/exceptions.py | 99 +++++++++++++++---- src/classy_blocks/construct/shape.py | 5 +- src/classy_blocks/construct/shapes/shell.py | 17 +--- .../grading/autograding/grader.py | 12 +-- .../grading/autograding/probe.py | 6 +- src/classy_blocks/grading/grading.py | 3 +- src/classy_blocks/mesh.py | 1 + .../modify/reorient/viewpoint.py | 5 +- src/classy_blocks/optimize/cell.py | 5 +- src/classy_blocks/optimize/grid.py | 9 +- src/classy_blocks/optimize/junction.py | 5 +- src/classy_blocks/optimize/optimizer.py | 4 - tests/test_construct/test_shell.py | 14 +-- tests/test_grading/test_autograde.py | 11 +-- tests/test_grading/test_grading.py | 3 +- tests/test_optimize/test_cell.py | 3 +- tests/test_optimize/test_optimizer.py | 2 +- 21 files changed, 114 insertions(+), 151 deletions(-) delete mode 100644 examples/advanced/low_re_chops.py diff --git a/examples/advanced/autograding_highre.py b/examples/advanced/autograding_highre.py index 471ce1b..34b93ea 100644 --- a/examples/advanced/autograding_highre.py +++ b/examples/advanced/autograding_highre.py @@ -2,7 +2,6 @@ import classy_blocks as cb from classy_blocks.grading.autograding.grader import HighReGrader -from classy_blocks.grading.autograding.params import HighReChopParams mesh = cb.Mesh() @@ -19,18 +18,12 @@ vertex = list(finder.find_in_sphere(point))[0] vertex.translate([0, 0.8, 0]) -# TODO! Un-hack -mesh.block_list.update() - mesh.set_default_patch("walls", "wall") +# TODO: Hack! mesh.assemble() won't work here but wires et. al. must be updated +mesh.block_list.update() -params = HighReChopParams(0.05) -grader = HighReGrader(mesh, params) +grader = HighReGrader(mesh, 0.05) grader.grade() -# params = SimpleChopParams(0.05) -# grader = SimpleGrader(mesh, params) -# grader.grade(take="max") - mesh.write(os.path.join("..", "case", "system", "blockMeshDict"), debug_path="debug.vtk") diff --git a/examples/advanced/low_re_chops.py b/examples/advanced/low_re_chops.py deleted file mode 100644 index 0f0f2a1..0000000 --- a/examples/advanced/low_re_chops.py +++ /dev/null @@ -1,37 +0,0 @@ -import os - -import classy_blocks as cb -from classy_blocks.grading.autograding.params import LowReChopParams - -mesh = cb.Mesh() - -start = cb.Box([0, 0, 0], [1, 1, 0.1]) - -start.chop(0, start_size=0.1) -start.chop(2, count=1) -mesh.add(start) - -expand_start = start.get_face("right") -expand = cb.Loft(expand_start, expand_start.copy().translate([1, 0, 0]).scale(2)) -expand.chop(2, start_size=0.1) - -# HACK: proper usage to be defined -low_re_chops = LowReChopParams(0.01, 0.1) -chops = low_re_chops.get_count(1) -for chop in chops: - expand.chops[1].append(chop) - -mesh.add(expand) - -contract_start = expand.get_face("top") -contract = cb.Loft(contract_start, contract_start.copy().translate([1, 0, 0]).scale(0.25)) -contract.chop(2, start_size=0.1) -mesh.add(contract) - -end = cb.Extrude(contract.get_face("top"), 1) -end.chop(2, start_size=0.1) -mesh.add(end) - -mesh.set_default_patch("walls", "wall") - -mesh.write(os.path.join("..", "case", "system", "blockMeshDict")) diff --git a/examples/complex/cyclone/geometry.py b/examples/complex/cyclone/geometry.py index 8e6e006..40ab86a 100644 --- a/examples/complex/cyclone/geometry.py +++ b/examples/complex/cyclone/geometry.py @@ -15,15 +15,12 @@ T_PIPE, ) +from classy_blocks.base.exceptions import GeometryConstraintError from classy_blocks.types import NPPointType from classy_blocks.util import functions as f from classy_blocks.util.constants import vector_format as fvect -class GeometryConstraintError(Exception): - """Raised when input parameters produce an invalid geometry""" - - @dataclasses.dataclass class Geometry: """Holds user-provided parameters and conversions to SI units; diff --git a/examples/shape/quarter_cylinder.py b/examples/shape/quarter_cylinder.py index f39fb6a..52b946b 100644 --- a/examples/shape/quarter_cylinder.py +++ b/examples/shape/quarter_cylinder.py @@ -3,7 +3,6 @@ import classy_blocks as cb from classy_blocks.construct.flat.sketches.disk import QuarterDisk from classy_blocks.grading.autograding.grader import HighReGrader -from classy_blocks.grading.autograding.params import HighReChopParams from classy_blocks.util import functions as f mesh = cb.Mesh() @@ -21,11 +20,8 @@ mesh.add(quarter_cylinder) mesh.assemble() -# TODO: automate or something -mesh.block_list.update() -params = HighReChopParams(0.05) -grader = HighReGrader(mesh, params) +grader = HighReGrader(mesh, 0.05) grader.grade() diff --git a/src/classy_blocks/base/exceptions.py b/src/classy_blocks/base/exceptions.py index a140d49..a8f4d15 100644 --- a/src/classy_blocks/base/exceptions.py +++ b/src/classy_blocks/base/exceptions.py @@ -1,27 +1,7 @@ from typing import Optional -class VertexNotFoundError(Exception): - """Raised when a vertex at a given point in space doesn't exist yet""" - - -class EdgeNotFoundError(Exception): - """Raised when an edge between a given pair of vertices doesn't exist yet""" - - -class CornerPairError(Exception): - """Raised when given pair of corners is not valid (for example, edge between 0 and 2)""" - - -class UndefinedGradingsError(Exception): - """Raised when the user hasn't supplied enough grading data to - define all blocks in the mesh""" - - -class InconsistentGradingsError(Exception): - """Raised when cell counts for edges on the same axis is not consistent""" - - +### Construction class ShapeCreationError(Exception): """Base class for shape creation errors (invalid parameters/types to shape constructors)""" @@ -75,3 +55,80 @@ class FrustumCreationError(ShapeCreationError): class ExtrudedRingCreationError(ShapeCreationError): pass + + +class GeometryConstraintError(Exception): + """Raised when input parameters produce an invalid geometry""" + + +class DegenerateGeometryError(Exception): + """Raised when orienting failed because of invalid geometry""" + + +# Shell/offsetting logic +class SharedPointError(Exception): + """Errors with shared points""" + + +class SharedPointNotFoundError(SharedPointError): + pass + + +class PointNotCoincidentError(SharedPointError): + pass + + +class DisconnectedChopError(SharedPointError): + """Issued when chopping a Shell that has disconnected faces""" + + +### Search/retrieval +class VertexNotFoundError(Exception): + """Raised when a vertex at a given point in space doesn't exist yet""" + + +class EdgeNotFoundError(Exception): + """Raised when an edge between a given pair of vertices doesn't exist yet""" + + +class CornerPairError(Exception): + """Raised when given pair of corners is not valid (for example, edge between 0 and 2)""" + + +### Grading +class UndefinedGradingsError(Exception): + """Raised when the user hasn't supplied enough grading data to + define all blocks in the mesh""" + + +class InconsistentGradingsError(Exception): + """Raised when cell counts for edges on the same axis is not consistent""" + + +class NoInstructionError(Exception): + """Raised when building a catalogue""" + + +class BlockNotFoundError(Exception): + """Raised when building a catalogue""" + + +### Optimization +class NoClampError(Exception): + """Raised when there's no junction defined for a given Clamp""" + + +class ClampExistsError(Exception): + """Raised when adding a clamp to a junction that already has one defined""" + + +class NoCommonSidesError(Exception): + """Raised when two cells don't share a side""" + + +class NoJunctionError(Exception): + """Raised when there's a clamp defined for a vertex that doesn't exist""" + + +class InvalidLinkError(Exception): + """Raised when a link has been added that doesn't connect two actual points""" diff --git a/src/classy_blocks/construct/shape.py b/src/classy_blocks/construct/shape.py index ab84895..3080d61 100644 --- a/src/classy_blocks/construct/shape.py +++ b/src/classy_blocks/construct/shape.py @@ -6,6 +6,7 @@ import numpy as np from classy_blocks.base.element import ElementBase +from classy_blocks.base.exceptions import ShapeCreationError from classy_blocks.construct.edges import Angle from classy_blocks.construct.flat.sketch import Sketch, SketchT from classy_blocks.construct.operations.loft import Loft @@ -16,10 +17,6 @@ ShapeT = TypeVar("ShapeT", bound="Shape") -class ShapeCreationError(Exception): - """Raised when creating a shape from errorneous data""" - - class Shape(ElementBase, abc.ABC): """A collection of Operations that form a predefined parametric shape""" diff --git a/src/classy_blocks/construct/shapes/shell.py b/src/classy_blocks/construct/shapes/shell.py index 0edad8a..07d4113 100644 --- a/src/classy_blocks/construct/shapes/shell.py +++ b/src/classy_blocks/construct/shapes/shell.py @@ -3,6 +3,7 @@ import numpy as np +from classy_blocks.base.exceptions import DisconnectedChopError, PointNotCoincidentError, SharedPointNotFoundError from classy_blocks.construct.flat.face import Face from classy_blocks.construct.operations.loft import Loft from classy_blocks.construct.point import Point @@ -11,22 +12,6 @@ from classy_blocks.util import functions as f -class SharedPointError(Exception): - """Errors with shared points""" - - -class SharedPointNotFoundError(SharedPointError): - pass - - -class PointNotCoincidentError(SharedPointError): - pass - - -class DisconnectedChopError(SharedPointError): - """Issued when chopping a Shell that has disconnected faces""" - - class SharedPoint: """A Point with knowledge of its "owner" Face(s)""" diff --git a/src/classy_blocks/grading/autograding/grader.py b/src/classy_blocks/grading/autograding/grader.py index 77f2883..79871a8 100644 --- a/src/classy_blocks/grading/autograding/grader.py +++ b/src/classy_blocks/grading/autograding/grader.py @@ -70,8 +70,8 @@ class FixedCountGrader(GraderBase): stages = 1 - def __init__(self, mesh: Mesh, params: FixedCountParams): - super().__init__(mesh, params) + def __init__(self, mesh: Mesh, count: int = 8): + super().__init__(mesh, FixedCountParams(count)) class SimpleGrader(GraderBase): @@ -81,8 +81,8 @@ class SimpleGrader(GraderBase): stages = 1 - def __init__(self, mesh: Mesh, params: SimpleChopParams): - super().__init__(mesh, params) + def __init__(self, mesh: Mesh, cell_size: float): + super().__init__(mesh, SimpleChopParams(cell_size)) class HighReGrader(GraderBase): @@ -93,5 +93,5 @@ class HighReGrader(GraderBase): stages = 3 - def __init__(self, mesh: Mesh, params: HighReChopParams): - super().__init__(mesh, params) + def __init__(self, mesh: Mesh, cell_size: float): + super().__init__(mesh, HighReChopParams(cell_size)) diff --git a/src/classy_blocks/grading/autograding/probe.py b/src/classy_blocks/grading/autograding/probe.py index 0e74d6c..a115672 100644 --- a/src/classy_blocks/grading/autograding/probe.py +++ b/src/classy_blocks/grading/autograding/probe.py @@ -1,6 +1,7 @@ import functools from typing import Dict, List, Optional, get_args +from classy_blocks.base.exceptions import BlockNotFoundError, NoInstructionError from classy_blocks.items.block import Block from classy_blocks.items.wires.axis import Axis from classy_blocks.items.wires.wire import Wire @@ -109,7 +110,7 @@ def _find_instruction(self, block: Block): if instruction.block == block: return instruction - raise RuntimeError(f"No instruction found for block {block}") + raise NoInstructionError(f"No instruction found for block {block}") def _add_block_to_row(self, row: Row, instruction: Instruction, direction: DirectionType) -> None: row.add_block(instruction.block, direction) @@ -142,8 +143,7 @@ def get_row_blocks(self, block: Block, direction: DirectionType) -> List[Block]: if block in row.blocks: return row.blocks - # TODO: make a custom exception - raise RuntimeError(f"Direction {direction} of {block} not in catalogue") + raise BlockNotFoundError(f"Direction {direction} of {block} not in catalogue") class Probe: diff --git a/src/classy_blocks/grading/grading.py b/src/classy_blocks/grading/grading.py index 69cf167..89084b5 100644 --- a/src/classy_blocks/grading/grading.py +++ b/src/classy_blocks/grading/grading.py @@ -39,6 +39,7 @@ import warnings from typing import List +from classy_blocks.base.exceptions import UndefinedGradingsError from classy_blocks.grading.chop import Chop, ChopData from classy_blocks.types import GradingSpecType from classy_blocks.util import constants @@ -150,7 +151,7 @@ def is_defined(self) -> bool: def description(self) -> str: """Output string for blockMeshDict""" if not self.is_defined: - raise ValueError(f"Grading not defined: {self}") + raise UndefinedGradingsError(f"Grading not defined: {self}") if len(self.specification) == 1: # its a one-number simpleGrading: diff --git a/src/classy_blocks/mesh.py b/src/classy_blocks/mesh.py index 36286cb..f967094 100644 --- a/src/classy_blocks/mesh.py +++ b/src/classy_blocks/mesh.py @@ -158,6 +158,7 @@ def assemble(self) -> None: self.block_list.add(block) self._add_geometry() + self.block_list.update() def clear(self) -> None: """Undoes the assemble() method; clears created blocks and other lists diff --git a/src/classy_blocks/modify/reorient/viewpoint.py b/src/classy_blocks/modify/reorient/viewpoint.py index 2b9d695..58d40ad 100644 --- a/src/classy_blocks/modify/reorient/viewpoint.py +++ b/src/classy_blocks/modify/reorient/viewpoint.py @@ -3,16 +3,13 @@ import numpy as np from scipy.spatial import ConvexHull +from classy_blocks.base.exceptions import DegenerateGeometryError from classy_blocks.construct.operations.operation import Operation from classy_blocks.types import NPPointListType, NPPointType, NPVectorType, OrientType, PointType from classy_blocks.util import constants from classy_blocks.util import functions as f -class DegenerateGeometryError(Exception): - """Raised when orienting by ObserverSorter failed because of invalid geometry""" - - class Triangle: """A 'Simplex' in scipy terms, but in 3D this is just a triangle.""" diff --git a/src/classy_blocks/optimize/cell.py b/src/classy_blocks/optimize/cell.py index 3c606b9..e654651 100644 --- a/src/classy_blocks/optimize/cell.py +++ b/src/classy_blocks/optimize/cell.py @@ -4,16 +4,13 @@ import numpy as np +from classy_blocks.base.exceptions import NoCommonSidesError from classy_blocks.optimize.connection import CellConnection from classy_blocks.types import FloatListType, IndexType, NPPointListType, NPPointType, OrientType from classy_blocks.util import functions as f from classy_blocks.util.constants import EDGE_PAIRS, VSMALL -class NoCommonSidesError(Exception): - """Raised when two cells don't share a side""" - - class CellBase(abc.ABC): side_names: ClassVar[List[OrientType]] side_indexes: ClassVar[List[IndexType]] diff --git a/src/classy_blocks/optimize/grid.py b/src/classy_blocks/optimize/grid.py index 20a7194..e5022a1 100644 --- a/src/classy_blocks/optimize/grid.py +++ b/src/classy_blocks/optimize/grid.py @@ -2,6 +2,7 @@ import numpy as np +from classy_blocks.base.exceptions import InvalidLinkError, NoJunctionError from classy_blocks.construct.flat.sketches.mapped import MappedSketch from classy_blocks.mesh import Mesh from classy_blocks.optimize.cell import CellBase, HexCell, QuadCell @@ -13,14 +14,6 @@ from classy_blocks.util.constants import TOL -class NoJunctionError(Exception): - """Raised when there's a clamp defined for a vertex that doesn't exist""" - - -class InvalidLinkError(Exception): - """Raised when a link has been added that doesn't connect two actual points""" - - class GridBase: """A list of cells and junctions""" diff --git a/src/classy_blocks/optimize/junction.py b/src/classy_blocks/optimize/junction.py index 1c941bc..42b9fb2 100644 --- a/src/classy_blocks/optimize/junction.py +++ b/src/classy_blocks/optimize/junction.py @@ -1,16 +1,13 @@ import dataclasses from typing import List, Optional, Set +from classy_blocks.base.exceptions import ClampExistsError from classy_blocks.optimize.cell import CellBase from classy_blocks.optimize.clamps.clamp import ClampBase from classy_blocks.optimize.links import LinkBase from classy_blocks.types import NPPointListType, NPPointType -class ClampExistsError(Exception): - """Raised when adding a clamp to a junction that already has one defined""" - - @dataclasses.dataclass class IndexedLink: # TODO: refactor / deuglify diff --git a/src/classy_blocks/optimize/optimizer.py b/src/classy_blocks/optimize/optimizer.py index c10111f..a1e81e0 100644 --- a/src/classy_blocks/optimize/optimizer.py +++ b/src/classy_blocks/optimize/optimizer.py @@ -18,10 +18,6 @@ MinimizationMethodType = Literal["SLSQP", "L-BFGS-B", "Nelder-Mead", "Powell"] -class NoClampError(Exception): - """Raised when there's no junction defined for a given Clamp""" - - class OptimizerBase(abc.ABC): """Provides tools for 2D (sketch) or 3D (mesh blocking) optimization""" diff --git a/tests/test_construct/test_shell.py b/tests/test_construct/test_shell.py index 53dcc53..d6ec679 100644 --- a/tests/test_construct/test_shell.py +++ b/tests/test_construct/test_shell.py @@ -3,19 +3,11 @@ import numpy as np from parameterized import parameterized +from classy_blocks.base.exceptions import DisconnectedChopError, PointNotCoincidentError, SharedPointNotFoundError from classy_blocks.construct.flat.face import Face from classy_blocks.construct.operations.box import Box from classy_blocks.construct.operations.loft import Loft -from classy_blocks.construct.shapes.shell import ( - AwareFace, - AwareFaceStore, - DisconnectedChopError, - PointNotCoincidentError, - SharedPoint, - SharedPointNotFoundError, - SharedPointStore, - Shell, -) +from classy_blocks.construct.shapes.shell import AwareFace, AwareFaceStore, SharedPoint, SharedPointStore, Shell from classy_blocks.types import OrientType from classy_blocks.util import functions as f from tests.fixtures.block import DataTestCase @@ -272,7 +264,7 @@ def test_chop(self): self.assertEqual(len(shell.operations[0].chops[2]), 1) def test_set_outer_patch(self): - orients = ["front", "right"] + orients: List[OrientType] = ["front", "right"] shell = self.get_shell(orients) shell.set_outer_patch("roof") diff --git a/tests/test_grading/test_autograde.py b/tests/test_grading/test_autograde.py index 59514fa..a5a7bab 100644 --- a/tests/test_grading/test_autograde.py +++ b/tests/test_grading/test_autograde.py @@ -5,7 +5,6 @@ from classy_blocks.construct.shapes.frustum import Frustum from classy_blocks.construct.stack import ExtrudedStack from classy_blocks.grading.autograding.grader import HighReGrader -from classy_blocks.grading.autograding.params import HighReChopParams from classy_blocks.mesh import Mesh @@ -29,10 +28,10 @@ class GraderTests(AutogradeTestsBase): def test_highre_cylinder(self): self.mesh.add(self.get_cylinder()) self.mesh.assemble() - # TODO: Hack! Un-hack! - self.mesh.block_list.update() - - params = HighReChopParams(0.025) - grader = HighReGrader(self.mesh, params) + grader = HighReGrader(self.mesh, 0.025) grader.grade() + + # make sure all blocks are defined + for block in self.mesh.blocks: + self.assertTrue(block.is_defined) diff --git a/tests/test_grading/test_grading.py b/tests/test_grading/test_grading.py index 0e3875a..5634f4a 100644 --- a/tests/test_grading/test_grading.py +++ b/tests/test_grading/test_grading.py @@ -3,6 +3,7 @@ import numpy as np from parameterized import parameterized +from classy_blocks.base.exceptions import UndefinedGradingsError from classy_blocks.grading import relations as rel from classy_blocks.grading.chop import Chop, ChopRelation from classy_blocks.grading.grading import Grading @@ -67,7 +68,7 @@ def add_chop(self, length_ratio, count, total_expansion): self.g.add_chop(chop) def test_output_empty(self): - with self.assertRaises(ValueError): + with self.assertRaises(UndefinedGradingsError): _ = self.g.description def test_output_single(self): diff --git a/tests/test_optimize/test_cell.py b/tests/test_optimize/test_cell.py index a41353d..71dbf90 100644 --- a/tests/test_optimize/test_cell.py +++ b/tests/test_optimize/test_cell.py @@ -1,7 +1,8 @@ import numpy as np from parameterized import parameterized -from classy_blocks.optimize.cell import HexCell, NoCommonSidesError +from classy_blocks.base.exceptions import NoCommonSidesError +from classy_blocks.optimize.cell import HexCell from tests.fixtures.mesh import MeshTestCase diff --git a/tests/test_optimize/test_optimizer.py b/tests/test_optimize/test_optimizer.py index 10f55da..8e0bef5 100644 --- a/tests/test_optimize/test_optimizer.py +++ b/tests/test_optimize/test_optimizer.py @@ -2,10 +2,10 @@ import numpy as np +from classy_blocks.base.exceptions import ClampExistsError from classy_blocks.construct.flat.sketches.mapped import MappedSketch from classy_blocks.optimize.clamps.free import FreeClamp from classy_blocks.optimize.clamps.surface import PlaneClamp -from classy_blocks.optimize.junction import ClampExistsError from classy_blocks.optimize.links import TranslationLink from classy_blocks.optimize.optimizer import MeshOptimizer, SketchOptimizer from classy_blocks.optimize.smoother import SketchSmoother From 550ea7dd6a051654b992b4fed57c29a8c22d5b6e Mon Sep 17 00:00:00 2001 From: Nejc Jurkovic Date: Fri, 25 Oct 2024 13:56:31 +0200 Subject: [PATCH 3/3] Update examples with autograding, add tests --- examples/chaining/labyrinth.py | 3 +- examples/chaining/tank.py | 4 +-- examples/shape/custom.py | 8 +---- examples/shape/cylinder.py | 7 ++--- examples/shape/quarter_cylinder.py | 2 +- src/classy_blocks/items/wires/wire.py | 2 -- src/classy_blocks/optimize/cell.py | 4 --- src/classy_blocks/optimize/junction.py | 5 --- tests/test_grading/test_autograde.py | 26 +++++++++++++++- tests/test_grading/test_grading.py | 30 +++++++++++++++--- tests/test_items/test_block.py | 10 ++++++ tests/test_items/test_wire.py | 42 +++++++++++++++++++++++++- 12 files changed, 109 insertions(+), 34 deletions(-) diff --git a/examples/chaining/labyrinth.py b/examples/chaining/labyrinth.py index 50a7d3c..9b6ebeb 100644 --- a/examples/chaining/labyrinth.py +++ b/examples/chaining/labyrinth.py @@ -3,7 +3,6 @@ import classy_blocks as cb from classy_blocks.grading.autograding.grader import FixedCountGrader -from classy_blocks.grading.autograding.params import FixedCountParams from classy_blocks.util import functions as f mesh = cb.Mesh() @@ -35,7 +34,7 @@ mesh.set_default_patch("walls", "wall") -grader = FixedCountGrader(mesh, FixedCountParams(5)) +grader = FixedCountGrader(mesh, 5) grader.grade() mesh.write(os.path.join("..", "case", "system", "blockMeshDict"), debug_path="debug.vtk") diff --git a/examples/chaining/tank.py b/examples/chaining/tank.py index 0afe143..966a272 100644 --- a/examples/chaining/tank.py +++ b/examples/chaining/tank.py @@ -2,7 +2,6 @@ import classy_blocks as cb from classy_blocks.grading.autograding.grader import SimpleGrader -from classy_blocks.grading.autograding.params import SimpleChopParams # a cylindrical tank with round end caps diameter = 0.5 @@ -26,8 +25,7 @@ mesh.add(start_cap) mesh.add(end_cap) -params = SimpleChopParams(0.05) -grader = SimpleGrader(mesh, params) +grader = SimpleGrader(mesh, 0.05) grader.grade() mesh.write(os.path.join("..", "case", "system", "blockMeshDict"), debug_path="debug.vtk") diff --git a/examples/shape/custom.py b/examples/shape/custom.py index 6c51d8d..63c1d13 100644 --- a/examples/shape/custom.py +++ b/examples/shape/custom.py @@ -4,7 +4,6 @@ import classy_blocks as cb from classy_blocks.grading.autograding.grader import HighReGrader -from classy_blocks.grading.autograding.params import HighReChopParams from classy_blocks.types import PointType from classy_blocks.util import functions as f @@ -63,15 +62,10 @@ def add_edges(self) -> None: shape = cb.ExtrudedShape(base_1, 1) -# for op in shape.operations: -# for i in range(3): -# op.chop(i, count=10) mesh.add(shape) mesh.assemble() -mesh.block_list.update() -params = HighReChopParams(0.03) -grader = HighReGrader(mesh, params) +grader = HighReGrader(mesh, 0.03) grader.grade(take="max") mesh.set_default_patch("walls", "wall") diff --git a/examples/shape/cylinder.py b/examples/shape/cylinder.py index ca58c71..1a206fe 100644 --- a/examples/shape/cylinder.py +++ b/examples/shape/cylinder.py @@ -3,7 +3,6 @@ import classy_blocks as cb from classy_blocks.construct.flat.sketches.disk import DiskBase from classy_blocks.grading.autograding.grader import HighReGrader -from classy_blocks.grading.autograding.params import HighReChopParams DiskBase.core_ratio = 0.4 # Default is 0.8 @@ -26,6 +25,7 @@ bl_thickness = 0.05 core_size = 0.2 +# manual grading # cylinder.chop_axial(count=30) # cylinder.chop_radial(start_size=core_size, end_size=bl_thickness) # cylinder.chop_tangential(start_size=core_size) @@ -35,9 +35,8 @@ mesh.assemble() mesh.block_list.update() -params = HighReChopParams(0.1) -grader = HighReGrader(mesh, params) +# automatic grading +grader = HighReGrader(mesh, 0.1) grader.grade() - mesh.write(os.path.join("..", "case", "system", "blockMeshDict"), debug_path="debug.vtk") diff --git a/examples/shape/quarter_cylinder.py b/examples/shape/quarter_cylinder.py index 52b946b..8b19f84 100644 --- a/examples/shape/quarter_cylinder.py +++ b/examples/shape/quarter_cylinder.py @@ -24,5 +24,5 @@ grader = HighReGrader(mesh, 0.05) grader.grade() - +mesh.set_default_patch("walls", "wall") mesh.write(os.path.join("..", "case", "system", "blockMeshDict"), debug_path="debug.vtk") diff --git a/src/classy_blocks/items/wires/wire.py b/src/classy_blocks/items/wires/wire.py index dc5514a..99b8a6a 100644 --- a/src/classy_blocks/items/wires/wire.py +++ b/src/classy_blocks/items/wires/wire.py @@ -100,8 +100,6 @@ def add_inline(self, candidate: "Wire") -> None: def copy_to_coincidents(self): """Copies the grading to all coincident wires""" for coincident in self.coincidents: - # TODO: why was this 'if' here?! Investigate and cry. - # if not coincident.is_defined: coincident.grading = self.grading.copy(self.length, not coincident.is_aligned(self)) def check_consistency(self) -> None: diff --git a/src/classy_blocks/optimize/cell.py b/src/classy_blocks/optimize/cell.py index e654651..988fbc9 100644 --- a/src/classy_blocks/optimize/cell.py +++ b/src/classy_blocks/optimize/cell.py @@ -170,10 +170,6 @@ def q_scale(base, exponent, factor, value): return quality - @property - def min_length(self) -> float: - return min(self.get_edge_lengths()) - class QuadCell(CellBase): # Like constants.FACE_MAP but for quadrangle sides as line segments diff --git a/src/classy_blocks/optimize/junction.py b/src/classy_blocks/optimize/junction.py index 42b9fb2..1c2ed26 100644 --- a/src/classy_blocks/optimize/junction.py +++ b/src/classy_blocks/optimize/junction.py @@ -85,8 +85,3 @@ def quality(self) -> float: this serves as an indicator of which junction to optimize, not a measurement of overall mesh quality""" return sum([cell.quality for cell in self.cells]) / len(self.cells) - - @property - def delta(self) -> float: - """Defining length for calculation of gradients, displacements, etc.""" - return min(cell.min_length for cell in self.cells) diff --git a/tests/test_grading/test_autograde.py b/tests/test_grading/test_autograde.py index a5a7bab..1a207ce 100644 --- a/tests/test_grading/test_autograde.py +++ b/tests/test_grading/test_autograde.py @@ -4,7 +4,7 @@ from classy_blocks.construct.shapes.cylinder import Cylinder from classy_blocks.construct.shapes.frustum import Frustum from classy_blocks.construct.stack import ExtrudedStack -from classy_blocks.grading.autograding.grader import HighReGrader +from classy_blocks.grading.autograding.grader import FixedCountGrader, HighReGrader, SimpleGrader from classy_blocks.mesh import Mesh @@ -25,6 +25,30 @@ def setUp(self): class GraderTests(AutogradeTestsBase): + def test_fixed_count_cylinder(self): + cylinder = self.get_cylinder() + self.mesh.add(cylinder) + self.mesh.assemble() + + grader = FixedCountGrader(self.mesh, 10) + grader.grade() + + for block in self.mesh.blocks: + for axis in block.axes: + self.assertEqual(axis.count, 10) + + def test_simple_grader_stack(self): + stack = self.get_stack() + self.mesh.add(stack) + self.mesh.assemble() + + grader = SimpleGrader(self.mesh, 0.1) + grader.grade() + + for block in self.mesh.blocks: + for axis in block.axes: + self.assertEqual(axis.count, 3) + def test_highre_cylinder(self): self.mesh.add(self.get_cylinder()) self.mesh.assemble() diff --git a/tests/test_grading/test_grading.py b/tests/test_grading/test_grading.py index 5634f4a..655e5f6 100644 --- a/tests/test_grading/test_grading.py +++ b/tests/test_grading/test_grading.py @@ -147,12 +147,11 @@ def test_add_division_inverted(self): self.assertAlmostEqual(self.g.specification[0][2], 1 / self.g.specification[1][2]) - def test_add_wrong_ratio(self): + @parameterized.expand(((0,), (1.1,))) + def test_add_wrong_ratio(self, ratio): """Add a chop with an invalid length ratio""" with self.assertRaises(ValueError): - self.g.add_chop(Chop(length_ratio=0, count=10)) - - _ = self.g.specification + self.g.add_chop(Chop(length_ratio=ratio, count=10)) def test_is_defined(self): self.g.add_chop(Chop(1, count=10, start_size=0.05)) @@ -239,3 +238,26 @@ def test_copy_preserve_end(self): self.assertEqual(g1.end_size, g2.end_size) self.assertGreater(g1.specification[0][2], g2.specification[0][2]) + + def test_start_size_exception(self): + grading = Grading(1) + + with self.assertRaises(RuntimeError): + _ = grading.start_size + + def test_end_size_exception(self): + grading = Grading(1) + + with self.assertRaises(RuntimeError): + _ = grading.end_size + + def test_grading_description_undefined(self): + grading = Grading(1) + + self.assertEqual(str(grading), "Grading (0)") + + def test_grading_description_define(self): + grading = Grading(1) + grading.add_chop(Chop(count=10)) + + self.assertEqual(str(grading), "Grading (1 chops 1)") diff --git a/tests/test_items/test_block.py b/tests/test_items/test_block.py index e489f9b..daf5b37 100644 --- a/tests/test_items/test_block.py +++ b/tests/test_items/test_block.py @@ -140,6 +140,16 @@ def test_add_foreign(self): self.assertEqual(len(block_0.axes[1].neighbours), 0) self.assertEqual(len(block_0.axes[2].neighbours), 0) + def test_axis_direction(self): + block = self.make_block(0) + axis = self.make_block(1).axes[0] + + with self.assertRaises(RuntimeError): + block.get_axis_direction(axis) + + def test_repr(self): + self.assertEqual(str(self.make_block(0)), "Block 0") + class BlockSimpleGradingTests(BlockTestCase): """Tests of neighbours, copying grading and whatnot simple variant; diff --git a/tests/test_items/test_wire.py b/tests/test_items/test_wire.py index a11ff18..aea1945 100644 --- a/tests/test_items/test_wire.py +++ b/tests/test_items/test_wire.py @@ -1,7 +1,12 @@ import copy +from classy_blocks.base.exceptions import InconsistentGradingsError +from classy_blocks.construct.edges import Arc +from classy_blocks.grading.chop import Chop +from classy_blocks.items.edges.arcs.arc import ArcEdge from classy_blocks.items.vertex import Vertex from classy_blocks.items.wires.wire import Wire +from classy_blocks.types import DirectionType from tests.fixtures.data import DataTestCase @@ -15,7 +20,7 @@ def setUp(self): self.corner_1 = 1 self.corner_2 = 2 - self.axis = 1 # make sure corners and axis are consistent + self.axis: DirectionType = 1 # make sure corners and axis are consistent @property def wire(self) -> Wire: @@ -88,3 +93,38 @@ def test_is_inverted(self): wire_2.vertices.reverse() self.assertFalse(wire_1.is_aligned(wire_2)) + + def test_add_inline_duplicate(self): + wire = self.wire + + wire.add_inline(wire) + self.assertEqual(len(wire.after), 0) + self.assertEqual(len(wire.before), 0) + + def test_check_consistency_count(self): + wire_1 = Wire(self.vertices, 0, 0, 1) + wire_1.grading.add_chop(Chop(count=10)) + wire_1.update() + + wire_2 = Wire(self.vertices, 0, 0, 1) + wire_2.grading.add_chop(Chop(count=5)) + wire_2.update() + + wire_1.coincidents.add(wire_2) + + with self.assertRaises(InconsistentGradingsError): + wire_1.check_consistency() + + def test_check_consistency_length(self): + # Add two different edges so that lengths are not equal + wire_1 = Wire(self.vertices, 0, 0, 1) + wire_1.edge = ArcEdge(self.vertices[0], self.vertices[1], Arc([0.5, 0.5, 0])) + + wire_2 = Wire(self.vertices, 0, 0, 1) + wire_2.edge = ArcEdge(self.vertices[0], self.vertices[1], Arc([0.5, 0.25, 0])) + wire_2.update() + + wire_1.add_coincident(wire_2) + + with self.assertRaises(InconsistentGradingsError): + wire_1.check_consistency()