Source code for jitxlib.landpatterns.grid_planner

from __future__ import annotations
from collections.abc import Callable
from typing import Self, override

from jitx.transform import IDENTITY

from .grid_layout import GridLayoutInterface, GridPosition


[docs] class GridPlanner: """Grid Planner Class This base class provides an interface for determining which pads in a grid are active. Each subclass defines a 'pattern' of grid locations which are inactive or active. These can be composed together to create more complex grid patterns. """
[docs] def is_active(self, pos: GridPosition, num_rows: int, num_cols: int) -> bool | None: """Determine if a pad is active based on the row and column. Args: pos: The grid location to check. num_rows: The total number of rows in the grid. num_cols: The total number of columns in the grid. Returns: True if the pad is active, False if the pad is inactive. Returns None if this planner does not specify whether this pad is active or inactive (the decision is deferred to the next planner in the composition). If all planners return None for a pad, the pad is considered active by default. """ # Default is planner is a full grid return True
[docs] def num_active(self, num_rows: int, num_cols: int) -> int: """Convenience helper function to determine the number of active pads in the grid.""" return sum( 1 for row in range(num_rows) for col in range(num_cols) if self.is_active(GridPosition(row, col, IDENTITY), num_rows, num_cols) is not False )
[docs] class GridPlannerMixin(GridLayoutInterface): __grid_planner: GridPlanner | None = None __grid_planner_fn: Callable[[GridPosition, int, int], bool] | None = None
[docs] def grid_planner( self, grid_planner: GridPlanner | Callable[[GridPosition, int, int], bool] ) -> Self: if isinstance(grid_planner, Callable): self.__grid_planner = None self.__grid_planner_fn = grid_planner else: self.__grid_planner = grid_planner self.__grid_planner_fn = None return self
@override def _active_pad(self, pos: GridPosition) -> bool: if self.__grid_planner is None: if self.__grid_planner_fn is None: return True return self.__grid_planner_fn(pos, self._num_rows, self._num_cols) active = self.__grid_planner.is_active(pos, self._num_rows, self._num_cols) if active is None: return True return active
[docs] class CompositeGridPlanner(GridPlanner): """Composite Grid Planner This planner is a composite of other planners. For a given pad, the first planner that returns a non-None result is used. If all planners return None then None is returned. This allows more complex grid patterns to be created by composing multiple planners together. """ def __init__(self, *planners: GridPlanner): """Initialize the composite grid planner Args: planners: The planners to compose together, in order of priority. Earlier planners take precedence over later planners. """ self.planners = list(planners)
[docs] def is_active(self, pos: GridPosition, num_rows: int, num_cols: int) -> bool | None: for planner in self.planners: result = planner.is_active(pos, num_rows, num_cols) if result is not None: return result return None
[docs] class StaggeredGridPlanner(GridPlanner): """Staggered Grid Planner This planner creates a grid where every other pad is inactive. The parity can be flipped by changing the ``top_left_active`` parameter. """ def __init__(self, *, top_left_active: bool = True): """Initialize the staggered grid planner Args: top_left_active: whether the top-left pad is active. """ self.top_left_active = top_left_active
[docs] def is_active(self, pos: GridPosition, num_rows: int, num_cols: int) -> bool | None: row = pos.row col = pos.column return (row + col) % 2 == (0 if self.top_left_active else 1)
[docs] class PerimeterGridPlanner(GridPlanner): """Perimeter Grid Planner This planner creates a grid where only the pads on the perimeter are active. The width of the perimeter can be controlled by the ``perimeter_width`` parameter. """ def __init__(self, perimeter_width: int): """Initialize the perimeter grid planner Args: perimeter_width: The width of the perimeter to keep in number of rows/columns. The width is measured from the edge of the grid. """ assert perimeter_width >= 0 self.perimeter_width = perimeter_width
[docs] def is_active(self, pos: GridPosition, num_rows: int, num_cols: int) -> bool: row = pos.row col = pos.column return not ( self.perimeter_width <= row < num_rows - self.perimeter_width and self.perimeter_width <= col < num_cols - self.perimeter_width )
[docs] class IslandGridPlanner(GridPlanner): """Island Grid Planner This planner creates a rectangular region of pads that are either active or inactive. The indices are one-indexed and inclusive (both start and end indices are included). Rows go from top to bottom, columns from left to right. Negative indices are allowed, and indicate counting from the other side (i.e. -1 is the bottom row or rightmost column). 0 is not a valid index. The planner returns ``None`` for pads that are not in the island. """ def __init__( self, row_start: int, row_end: int, col_start: int, col_end: int, *, active: bool = False, ): """Initialize the island grid planner Args: row_start: starting row of the island, 1-indexed and inclusive. row_end: ending row of the island, 1-indexed and inclusive. col_start: starting column of the island, 1-indexed and inclusive. col_end: ending column of the island, 1-indexed and inclusive. active: whether the island is active or inactive. Default is inactive. """ assert row_start != 0, f"{row_start} is not a valid index" assert row_end != 0, f"{row_end} is not a valid index" assert col_start != 0, f"{col_start} is not a valid index" assert col_end != 0, f"{col_end} is not a valid index" self.row_start = row_start self.row_end = row_end self.col_start = col_start self.col_end = col_end self.active = active
[docs] def is_active(self, pos: GridPosition, num_rows: int, num_cols: int) -> bool | None: row = pos.row col = pos.column row = row + 1 # zero-indexed to one-indexed col = col + 1 # zero-indexed to one-indexed def wrap(index: int, mod: int) -> int: if index > 0: return index else: return index + mod + 1 rs = wrap(self.row_start, num_rows) re = wrap(self.row_end, num_rows) cs = wrap(self.col_start, num_cols) ce = wrap(self.col_end, num_cols) if row >= rs and row <= re and col >= cs and col <= ce: return self.active return None
[docs] def InactiveIslandGridPlanner( row_start: int, row_end: int, col_start: int, col_end: int ) -> IslandGridPlanner: """Create an inactive island grid planner Args: row_start: starting row of the island, 1-indexed and inclusive. row_end: ending row of the island, 1-indexed and inclusive. col_start: starting column of the island, 1-indexed and inclusive. col_end: ending column of the island, 1-indexed and inclusive. Returns: An inactive island grid planner """ return IslandGridPlanner(row_start, row_end, col_start, col_end, active=False)
[docs] def ActiveIslandGridPlanner( row_start: int, row_end: int, col_start: int, col_end: int ) -> IslandGridPlanner: """Create an active island grid planner Args: row_start: starting row of the island, 1-indexed and inclusive. row_end: ending row of the island, 1-indexed and inclusive. col_start: starting column of the island, 1-indexed and inclusive. col_end: ending column of the island, 1-indexed and inclusive. Returns: An active island grid planner """ return IslandGridPlanner(row_start, row_end, col_start, col_end, active=True)
[docs] class CornerCutGridPlanner(GridPlanner): """Corner Cut Grid Planner This planner creates a grid where the pads in the triangles at the corners of the grid are inactive. The width (the "legs" of the right triangle) of the triangles is controlled by the ``corner_width`` parameter. """ def __init__(self, corner_width: int): """Initialize the corner cut grid planner Args: corner_width: The width of the corner triangles to be left inactive. """ assert corner_width >= 0 self.corner_width = corner_width
[docs] def is_active(self, pos: GridPosition, num_rows: int, num_cols: int) -> bool | None: row = pos.row col = pos.column vgap = min(row, num_rows - row - 1) hgap = min(col, num_cols - col - 1) if vgap + hgap < self.corner_width: return False else: return None