Skip to content

Commit

Permalink
Fix bugs and Refactor: prepare for addition of InflationGrader
Browse files Browse the repository at this point in the history
  • Loading branch information
FranzBangar committed Nov 14, 2024
1 parent 60874f7 commit 559ad0b
Show file tree
Hide file tree
Showing 14 changed files with 496 additions and 287 deletions.
5 changes: 4 additions & 1 deletion classy_blocks.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
"Pylance",
"scipy"
],
"editor.autoClosingBrackets": "never"
"editor.autoClosingBrackets": "never",
"isort.importStrategy": "fromEnvironment",
"editor.defaultFormatter": null,
"ruff.organizeImports": false
},
"extensions": {
"recommendations": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

import numpy as np

import classy_blocks as cb
from classy_blocks.grading.autograding.grader import SmoothGrader

Expand All @@ -8,7 +10,13 @@
base = cb.Grid([0, 0, 0], [3, 2, 0], 3, 2)

shape = cb.ExtrudedShape(base, 1)

# turn one block around to test grader's skillz
shape.grid[1][0].rotate(np.pi, [0, 0, 1])

mesh.add(shape)

# move some points to get a mesh with uneven blocks
mesh.assemble()
finder = cb.GeometricFinder(mesh)

Expand All @@ -19,10 +27,7 @@
vertex.translate([0, 0.8, 0])

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()

grader = SmoothGrader(mesh, 0.05)
grader.grade()

Expand Down
4 changes: 4 additions & 0 deletions src/classy_blocks/base/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ class CornerPairError(Exception):
"""Raised when given pair of corners is not valid (for example, edge between 0 and 2)"""


class PatchNotFoundError(Exception):
"""Raised when searching for a non-existing Patch"""


### Grading
class UndefinedGradingsError(Exception):
"""Raised when the user hasn't supplied enough grading data to
Expand Down
90 changes: 90 additions & 0 deletions src/classy_blocks/grading/autograding/catalogue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import functools
from typing import Dict, List, get_args

from classy_blocks.base.exceptions import BlockNotFoundError, NoInstructionError
from classy_blocks.grading.autograding.row import Row
from classy_blocks.items.block import Block
from classy_blocks.items.wires.axis import Axis
from classy_blocks.mesh import Mesh
from classy_blocks.types import DirectionType


@functools.lru_cache(maxsize=3000) # that's for 1000 blocks
def get_block_from_axis(mesh: Mesh, axis: Axis) -> Block:
for block in mesh.blocks:
if axis in block.axes:
return block

raise RuntimeError("Block for Axis not found!")


class Instruction:
"""A descriptor that tells in which direction the specific block can be chopped."""

def __init__(self, block: Block):
self.block = block
self.directions: List[bool] = [False] * 3

@property
def is_defined(self):
return all(self.directions)

def __hash__(self) -> int:
return id(self)


class Catalogue:
"""A collection of rows on a specified axis"""

def __init__(self, mesh: Mesh):
self.mesh = mesh

self.rows: Dict[DirectionType, List[Row]] = {0: [], 1: [], 2: []}
self.instructions = [Instruction(block) for block in mesh.blocks]

for i in get_args(DirectionType):
self._populate(i)

def _get_undefined_instructions(self, direction: DirectionType) -> List[Instruction]:
return [i for i in self.instructions if not i.directions[direction]]

def _find_instruction(self, block: Block):
# TODO: perform dedumbing on this exquisite piece of code
for instruction in self.instructions:
if instruction.block == block:
return instruction

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)
instruction.directions[direction] = True

block = instruction.block

for neighbour_axis in block.axes[direction].neighbours:
neighbour_block = get_block_from_axis(self.mesh, neighbour_axis)

if neighbour_block in row.blocks:
continue

instruction = self._find_instruction(neighbour_block)

self._add_block_to_row(row, instruction, neighbour_block.get_axis_direction(neighbour_axis))

def _populate(self, direction: DirectionType) -> None:
while True:
undefined_instructions = self._get_undefined_instructions(direction)
if len(undefined_instructions) == 0:
break

row = Row()
self._add_block_to_row(row, undefined_instructions[0], direction)
self.rows[direction].append(row)

def get_row_blocks(self, block: Block, direction: DirectionType) -> List[Block]:
for row in self.rows[direction]:
if block in row.blocks:
return row.blocks

raise BlockNotFoundError(f"Direction {direction} of {block} not in catalogue")
125 changes: 80 additions & 45 deletions src/classy_blocks/grading/autograding/grader.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import abc
from typing import get_args

from classy_blocks.grading.autograding.params import (
Expand All @@ -8,13 +7,29 @@
SimpleGraderParams,
SmoothGraderParams,
)
from classy_blocks.grading.autograding.probe import Probe, Row
from classy_blocks.grading.autograding.probe import Probe
from classy_blocks.grading.autograding.row import Row
from classy_blocks.grading.chop import Chop
from classy_blocks.mesh import Mesh
from classy_blocks.types import ChopTakeType, DirectionType


class GraderBase(abc.ABC):
stages: int
class GraderBase:
"""One grader to rule them all. Grading procedure depends on given GraderParams object
that decides whether to grade a specific block (wire) or to let it pass to
Behold the most general procedure for grading _anything_.
For each row in every direction:
1. Set count
If there's a wire on the wall - determine 'wall' count (low-re grading etc)
If not, determine 'bulk' count
That involves the 'take' keyword so that appropriate block is taken as a reference;
2. Chop 'cramped' blocks
Where there's not enough space to fit graded cells, use a simple grading
3. Chop other blocks
optionally use multigrading to match neighbours' cell sizes
"""

def __init__(self, mesh: Mesh, params: ChopParams):
self.mesh = mesh
Expand All @@ -23,59 +38,85 @@ def __init__(self, mesh: Mesh, params: ChopParams):
self.mesh.assemble()
self.probe = Probe(self.mesh)

def get_count(self, row: Row, take: ChopTakeType) -> int:
count = row.get_count()

if count is None:
# take length from a row, as requested by 'take'
length = row.get_length(take)
# and set count from it
count = self.params.get_count(length)

return count

def grade_axis(self, axis: DirectionType, take: ChopTakeType, stage: int) -> None:
handled_wires = set()

for row in self.probe.get_rows(axis):
count = self.get_count(row, take)

for wire in row.get_wires():
if wire in handled_wires:
def set_counts(self, row: Row, take: ChopTakeType) -> None:
if row.count > 0:
# stuff, pre-defined by the user
return

# at_wall: List[Entry] = []

# Check if there are blocks at the wall;
# for entry in row.entries:
# for wire in entry.wires:
# # TODO: cache WireInfo
# info = self.probe.get_wire_info(wire, entry.block)
# if info.starts_at_wall or info.ends_at_wall:
# at_wall.append(entry)

length = row.get_length(take)

# if len(at_wall) > 0:
# # find out whether one or two sides are to be counted
# pass

row.count = self.params.get_count(length)

def grade_squeezed(self, row: Row) -> None:
for entry in row.entries:
# TODO! don't touch wires, defined by USER
# if wire.is_defined:
# # TODO: test
# continue
for wire in entry.wires:
if wire.is_defined:
continue

# don't touch defined wires
# TODO! don't touch wires, defined by USER
# if wire.is_defined:
# # TODO: test
# continue
info = self.probe.get_wire_info(wire, entry.block)
if self.params.is_squeezed(row.count, info):
wire.grading.clear()
wire.grading.add_chop(Chop(count=row.count))
wire.copy_to_coincidents()

def finalize(self, row: Row) -> None:
count = row.count

for entry in row.entries:
# TODO! don't touch wires, defined by USER
# if wire.is_defined:
# # TODO: test
# continue
for wire in entry.wires:
if wire.is_defined:
continue

size_before = wire.size_before
size_after = wire.size_after
# TODO: cache wire info
info = self.probe.get_wire_info(wire, entry.block)

chops = self.params.get_chops(stage, count, wire.length, size_before, size_after)
chops = self.params.get_chops(count, info)

wire.grading.clear()
for chop in chops:
wire.grading.add_chop(chop)

wire.copy_to_coincidents()

handled_wires.add(wire)
handled_wires.update(wire.coincidents)

def grade(self, take: ChopTakeType = "avg") -> None:
for axis in get_args(DirectionType):
for stage in range(self.stages):
self.grade_axis(axis, take, stage)
for direction in get_args(DirectionType):
rows = self.probe.get_rows(direction)
for row in rows:
self.set_counts(row, take)
for row in rows:
self.grade_squeezed(row)
for row in rows:
self.finalize(row)

self.mesh.block_list.check_consistency()


class FixedCountGrader(GraderBase):
"""The simplest possible mesh grading: use a constant cell count for all axes on all blocks;
useful during mesh building and some tutorial cases"""

stages = 1

def __init__(self, mesh: Mesh, count: int = 8):
super().__init__(mesh, FixedCountParams(count))

Expand All @@ -85,8 +126,6 @@ class SimpleGrader(GraderBase):
A single chop is used that sets cell count based on size.
Cell sizes between blocks differ as blocks' sizes change."""

stages = 1

def __init__(self, mesh: Mesh, cell_size: float):
super().__init__(mesh, SimpleGraderParams(cell_size))

Expand All @@ -97,8 +136,6 @@ class SmoothGrader(GraderBase):
are utilized to keep cell sizes between blocks consistent
(as much as possible)"""

stages = 3

def __init__(self, mesh: Mesh, cell_size: float):
super().__init__(mesh, SmoothGraderParams(cell_size))

Expand Down Expand Up @@ -134,8 +171,6 @@ class InflationGrader(GraderBase):
The finest grid will be obtained with 'max', the coarsest with 'min'.
"""

stages = 3

def __init__(
self,
mesh: Mesh,
Expand Down
Loading

0 comments on commit 559ad0b

Please sign in to comment.