slackwater-lattice
Exact integer geometry on the Eisenstein A₂ hexagonal lattice — the densest packing of circles in a plane. Every point has exactly six equidistant neighbors. No privileged axis. No floating-point drift. This package provides Eisenstein integer arithmetic, build placement with collision detection, and A* pathfinding on the hexagonal neighbor graph.
Installation
pip install slackwater-lattice
Mathematical Foundation
Eisenstein integers are ℤ[ω] where ω = e^(2πi/3) = −1/2 + i√3/2. They form a triangular/hexagonal lattice in the complex plane.
Coordinate system: A point (a, b) in Eisenstein coordinates maps to Cartesian:
x = (a − b/2) · scale
y = b · (√3/2) · scale
Norm (squared distance from origin):
N(a + bω) = a² − ab + b²
This is always a non-negative integer. All arithmetic is exact — no floating point, no drift.
Six neighbor directions (unit norm elements):
| Direction | Offset | Cartesian |
|---|---|---|
| +1 | (1, 0) | East |
| −1 | (−1, 0) | West |
| +ω | (0, 1) | Northeast |
| −ω | (0, −1) | Southwest |
| +(1+ω) | (1, 1) | Southeast |
| −(1+ω) | (−1, −1) | Northwest |
API Reference
EisensteinInteger
from slackwater_lattice import EisensteinInteger
EisensteinInteger(a: int, b: int)
An exact point on the A₂ lattice. Represents the value a + bω. Frozen dataclass — hashable, comparable.
Arithmetic operators:
| Operator | Signature | Formula |
|---|---|---|
+ |
(ei) -> ei |
(a+c, b+d) |
− |
(ei) -> ei |
(a−c, b−d) |
* |
(ei) -> ei |
(ac−bd, ad+bc−bd) |
conjugate() |
() -> ei |
(a−b, −b) |
norm() |
() -> int |
a² − ab + b² |
− (unary) |
() -> ei |
(−a, −b) |
Coordinate conversion:
ei.to_cartesian(scale: float = 1.0) -> tuple[float, float]
EisensteinInteger.from_cartesian(x: float, y: float, scale: float = 1.0) -> EisensteinInteger
The from_cartesian method rounds to the nearest lattice point. This is the snapping algorithm — continuous space → discrete lattice. It is deterministic and exact.
Lattice operations:
ei.neighbors() -> list[EisensteinInteger] # 6 equidistant neighbors
ei.rings(radius: int) -> list[EisensteinInteger] # all points within hex distance
ei.is_unit() -> bool # norm == 1?
ei.is_zero() -> bool # a==0 and b==0?
Module-level functions:
snap(x: float, z: float, scale: float = 1.0) -> EisensteinInteger
distance(a: EisensteinInteger, b: EisensteinInteger) -> int # squared Euclidean
hex_distance(a: EisensteinInteger, b: EisensteinInteger) -> int # graph steps
midpoint_region(a: EisensteinInteger, b: EisensteinInteger) -> list[EisensteinInteger]
Hex distance formula:
if da·db ≥ 0: d = max(|da|, |db|) # same sector
else: d = |da| + |db| # opposite sectors
BuildPlacement
from slackwater_lattice import BuildPlacement, PlacementError
BuildPlacement(
scale: float = 4.0, # world units per lattice unit
)
Manages occupied lattice points for a build. Collision detection is exact integer comparison — O(1) hash lookup, no tolerance bands.
Snapping:
bp.snap_position(x: float, z: float) -> EisensteinInteger
bp.snap_rotation(rotation_deg: float) -> int # nearest 60° increment
Collision and reservation:
bp.check_collision(lattice_point: EisensteinInteger) -> bool
bp.check_region_clear(center: EisensteinInteger, radius: int = 1) -> bool
bp.reserve(lattice_point: EisensteinInteger, meta: dict | None = None) -> EisensteinInteger
bp.release(lattice_point: EisensteinInteger) -> bool
bp.place(x: float, z: float, meta: dict | None = None) -> EisensteinInteger # snap + reserve
reserve() raises PlacementError on collision. place() is the combined snap-and-reserve convenience.
Queries:
bp.nearest_free(point: EisensteinInteger, max_radius: int = 20) -> EisensteinInteger | None
bp.occupied_neighbors(point: EisensteinInteger) -> list[EisensteinInteger]
bp.free_neighbors(point: EisensteinInteger) -> list[EisensteinInteger]
bp.boundary_points() -> list[EisensteinInteger] # frontier of the build
bp.footprint_area() -> int # count of occupied points
bp.diameter() -> int # max hex distance between any two points
Supports in operator, len(), and iteration.
LatticePathfinder
from slackwater_lattice import LatticePathfinder, LatticePath
LatticePathfinder(
placement: BuildPlacement,
allow_through_occupied: bool = False,
)
A* pathfinder on the Eisenstein lattice. Uses hex_distance as the heuristic — admissible and consistent on the neighbor graph, guaranteeing optimal paths.
Methods:
pf.find_path(
start: EisensteinInteger,
goal: EisensteinInteger,
max_steps: int = 10_000,
) -> LatticePath | None
Returns the shortest path through free lattice points, or None if unreachable.
pf.find_all_paths(start, goal, max_paths: int = 5) -> list[LatticePath]
pf.reachable(start, max_distance: int = 20) -> dict[EisensteinInteger, int]
find_all_paths uses a simplified Yen's algorithm for k-shortest paths. reachable does BFS within max_distance steps.
LatticePath:
@dataclass
class LatticePath:
points: list[EisensteinInteger]
length() -> int # number of steps (edges)
is_empty() -> bool # single point or less
Supports iteration and indexing.
Examples
Basic lattice arithmetic
from slackwater_lattice import EisensteinInteger, distance, hex_distance
z = EisensteinInteger(3, 1)
print(z.norm()) # 7 (9 - 3 + 1)
print(z.to_cartesian(4.0)) # (10.0, 3.464...)
neighbors = z.neighbors() # 6 points at distance 1
Snapping world positions to the lattice
from slackwater_lattice import BuildPlacement
bp = BuildPlacement(scale=4.0)
point = bp.place(10.5, 3.2)
print(point) # nearest lattice point to (10.5, 3.2) at scale 4
# Collision-free placement
free = bp.nearest_free(point)
Pathfinding around obstacles
from slackwater_lattice import EisensteinInteger, BuildPlacement, LatticePathfinder
bp = BuildPlacement()
# Build a wall
for i in range(-2, 3):
bp.reserve(EisensteinInteger(0, i))
pf = LatticePathfinder(placement=bp)
path = pf.find_path(EisensteinInteger(-3, 0), EisensteinInteger(3, 0))
print(path) # LatticePath(E(-3) → E(-2) → ... → E(3))
print(path.length()) # hex-optimal, avoids all wall points
License
MIT
Metadata
Release files for slackwater-lattice 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| slackwater_lattice-0.1.1.tar.gz | 23.1 kB | Without provenance | Details for slackwater_lattice-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| slackwater_lattice-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for slackwater_lattice-0.1.1-py3-none-any.whl |
Total release size: 38.6 kB
Release files / slackwater_lattice-0.1.1.tar.gz
| Download URL | slackwater_lattice-0.1.1.tar.gz |
|---|---|
| Size | 23.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c6b58980679554ba390c3b198a348e2c2be40ef2d6713e5d6224d4362618130f
|
|
BLAKE2b-256 checksum How to use checksums |
b4992e974b78fa758d161271379974f3a4a85dc4df2b8e09bf5364e2d2796f97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / slackwater_lattice-0.1.1-py3-none-any.whl
| Download URL | slackwater_lattice-0.1.1-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
546d8af30c4195d643a6cfd91bacc89a0c8938dfbad9edab3d2cb92f445bea56
|
|
BLAKE2b-256 checksum How to use checksums |
4677a3d5c43b9c4a1222ac8da5373832ab4f4e3648f1a119069d9b532361ac4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for slackwater-lattice 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.