Skip to content

Repository files navigation

LatticeGeom

PyPI Python CI Documentation License: MIT

English | 简体中文

Typed periodic lattice geometry for condensed-matter models.

The library describes geometry and periodic relations. It intentionally does not store hopping amplitudes, exchange constants, Peierls phases, flux values, or Hamiltonian matrices.

Design goals

  • directed bonds declared once in a reference unit cell;
  • arbitrary hashable type labels for sites, bonds, and plaquettes;
  • oriented plaquettes, so up/down triangles remain different geometry classes;
  • exact integer cell_shift for every bond;
  • compressed super_idx for finite-cell boundary crossings;
  • multi-edges and nontrivial periodic self-bonds are preserved;
  • read-only, NumPy-backed compiled arrays;
  • no mandatory graph library, SciPy, Rust, or Cython dependency.

Installation

Install the NumPy-only core, or add optional plotting and Brillouin-zone support:

python -m pip install latticegeom
python -m pip install "latticegeom[plot,bz]"

For a source checkout:

python -m pip install -e ".[test,plot,bz,docs]"
python -m pytest -q

The numerical runtime dependency is only NumPy. Matplotlib is an optional dependency used by the plotting layer; SciPy is imported only when constructing a two-dimensional Wigner--Seitz Brillouin zone.

Quick start

from latticegeom import triangular

lat = triangular(
    extent=(4, 4),
    pbc=True,
    bond_types=("a", "b", "c"),
)

print(lat.n_sites)              # 16
print(lat.bond_types)           # ('a', 'b', 'c')
print(lat.plaquette_types)      # ('triangle_up', 'triangle_down')

# Fast columnar access
src = lat.bonds.source
dst = lat.bonds.target
bond_type = lat.bonds.type_id
super_idx = lat.bonds.super_idx

Common constructors include chain, square, triangular, triangular_nnn, honeycomb, kagome, arbitrary-dimensional grid/hypercubic, and the 3D bcc, fcc, diamond, and pyrochlore lattices.

Reciprocal geometry and Bloch phases

Primitive and finite-supercell reciprocal objects are named explicitly. A regular mesh samples a continuous reciprocal cell, whereas allowed_momenta() returns the discrete momenta permitted by the finite PBC axes.

k = lat.k_from_coordinates([0.2, 0.3], cell="primitive")
q = lat.reciprocal_coordinates(k, cell="primitive")

k_mesh = lat.sample_kmesh((80, 80), cell="primitive")
finite_k = lat.allowed_momenta(centered=True)

Boundary phases use the already-compressed super_idx directly:

phase_by_super = lat.translation_phases(k, sign=-1)
phase_by_bond = phase_by_super[lat.bonds.super_idx]

This evaluates geometry-derived exp(-1j * k @ translation) factors; it does not assign amplitudes or construct a Hamiltonian. lat.unit_bond_phases(k, gauge="cell") similarly returns one phase per unit-cell bond. Use gauge="position" when the phase convention includes sublattice offsets.

With the bz extra installed, the primitive or folded first BZ is an explicit object:

bz = lat.brillouin_zone(cell="primitive")
vertices = bz.vertices
inside = bz.contains(k_mesh)
first_bz_mesh = bz.sample((100, 100))

The object is not cached on Lattice, so one sampling request cannot silently change another calculation.

Inspect the geometry visually

Plotting is deliberately optional and returns a regular Matplotlib axes without calling show(). Bond arrows preserve declared direction, while site, bond, and plaquette colors follow their geometry type labels.

import matplotlib.pyplot as plt

from latticegeom import triangular

lat = triangular((3, 3), pbc=True)
ax = lat.plot(
    draw_plaquettes=True,
    site_labels=True,
    periodic="unfold",
    plaquette_values=None,  # external values may be supplied without being stored
)
plt.show()

Boundary-crossing relations have three display modes:

  • periodic="unfold" (default) places the endpoint in its actual periodic image;
  • periodic="hide" omits crossing bonds and plaquettes;
  • periodic="fold" connects to the wrapped finite site with a dashed line.

unfold is the most faithful view for checking super_idx; fold is compact but can draw visually long bonds through the cell. Visible crossing relations are dashed. Other useful switches include include_reverse, arrows, plaquette_arrows (to show stored loop orientation), show_boundary, show_unit_cell, show_basis, and projection=(i, j) for a lattice embedded above two Cartesian dimensions. Run python examples/plot_geometry.py for a typed triangular/Kagome example.

Query exact bonds and plaquette boundaries

Compiled relation tables can be filtered without converting to Python graph objects:

rows = lat.find_bonds(
    type="a",
    source=0,
    cell_shift=(1, 0),
    crossing=False,
)

boundary = lat.plaquette_boundary(0)
source = boundary.source
target = boundary.target
cell_shift = boundary.cell_shift

PlaquetteBoundary follows the stored vertex orientation and gives one exact integer relation per perimeter edge. Additional geometric queries include plaquette_centers(), plaquette_signed_areas(), and lat.plaquettes.indices(type=..., crossing=...).

External bond or flux results can be annotated without placing model data on the lattice:

lat.plot(
    bond_values=phase_by_bond,
    plaquette_values=flux,
    value_format=".3f",
)

Compose and enlarge unit cells

Declarations remain immutable, while UnitCellBuilder provides a temporary mutable construction layer for multilayers and other composed cells:

from latticegeom import UnitCellBuilder

builder = UnitCellBuilder(lower_cell)
upper_sites = builder.extend(
    lower_cell,
    offset=(0.0, 0.3),
    site_type_map=lambda kind: ("upper", kind),
)
builder.add_bond(0, upper_sites[0], (0, 0), "interlayer")
bilayer_cell = builder.build()

translate_unit_cell and merge_unit_cells cover simple composition. For a magnetic or tilted crystallographic supercell, an integer transformation remaps sites, bonds, and plaquettes exactly:

from latticegeom import Lattice, make_unit_cell_supercell

new_basis, new_cell = make_unit_cell_supercell(
    lat.basis,
    lat.unit_cell,
    [[2, 1], [0, 1]],
)
enlarged = Lattice(new_basis, new_cell, extent=(3, 3), pbc=True)

Discover neighbors once, then freeze them

Distance-based neighbor inference is an explicit authoring tool, not a Lattice construction feature. It lives outside the top-level API so importing or constructing a lattice can never trigger it accidentally:

from latticegeom import Site
from latticegeom.tools import discover_neighbors

draft = discover_neighbors(
    basis=((1.0, 0.0), (0.5, 3**0.5 / 2.0)),
    sites=[Site((0.0, 0.0), "A")],
    shells=2,
)

print(draft.summary())
print(draft.render_python(type_by_shell={1: "nearest", 2: "next_nearest"}))

# After reviewing the output, write fixed Bond(...) declarations once.
draft.freeze(
    "fixed_triangular_bonds.py",
    type_by_shell={1: "nearest", 2: "next_nearest"},
)

Every discovery call emits a reminder to review and freeze the result. freeze() refuses to overwrite an existing file unless overwrite=True, and the generated file contains only explicit Bond(...) declarations—no runtime search call.

Distance shells are geometric, not physical. Equal bond lengths can still have different hopping or exchange amplitudes. Pass classify=candidate_to_type to inspect each candidate's source, target, integer cell_shift, Cartesian vector, distance, and one-based shell, or edit the generated source manually. Canonical orientations are deterministic but carry no inferred hopping direction.

The search supports skew bases, multiple sublattices, and lower-dimensional lattices in a larger Cartesian embedding. It expands the integer translation range until a geometric bound proves that no requested neighbor can still be missing. max_distance= is available as an explicit alternative to shells=. Shell grouping uses a relative tolerance by default so the result does not depend on whether positions are measured in lattice units, angstroms, or metres; rtol= and atol= remain explicit controls.

Declare custom geometry

from math import sqrt

from latticegeom import Bond, Lattice, Plaquette, Site, UnitCell

cell = UnitCell(
    sites=[Site((0.0, 0.0))],
    bonds=[
        Bond(0, 0, cell_shift=(1, 0), type=1),
        Bond(0, 0, cell_shift=(0, 1), type=2),
        Bond(0, 0, cell_shift=(-1, 1), type=3),
    ],
    plaquettes=[
        Plaquette(
            [(0, (0, 0)), (0, (1, 0)), (0, (0, 1))],
            type="triangle_up",
        ),
        Plaquette(
            [(0, (0, 0)), (0, (1, -1)), (0, (1, 0))],
            type="triangle_down",
        ),
    ],
)

lat = Lattice(
    basis=((1.0, 0.0), (0.5, sqrt(3.0) / 2.0)),
    unit_cell=cell,
    extent=(8, 8),
    pbc=True,
)

Plaquette vertex order is retained. The closing edge is implicit, so the first vertex must not be repeated at the end.

cell_shift and super_idx

These values answer different questions:

  • lat.bonds.cell_shift[i] is the exact primitive-cell displacement in the infinite lattice. It is the same geometric relation regardless of finite extent.
  • lat.bonds.super_idx[i] indexes the boundary crossing of that concrete finite bond.
  • lat.supercell_shifts[super_idx] is the integer number of simulation supercells crossed.
  • lat.supercell_translations[super_idx] is the corresponding Cartesian translation.

This supports block or Bloch-phase assembly without putting Hamiltonian data into the geometry layer:

for super_idx, translation in enumerate(lat.supercell_translations):
    rows = lat.bonds.indices(super_idx=super_idx)
    if not len(rows):
        continue

    source = lat.bonds.source[rows]
    target = lat.bonds.target[rows]
    kind = lat.bonds.type_id[rows]
    # A downstream model assigns amplitudes and exp(-1j * k @ translation).

lat.reverse_super_idx maps every boundary class to its negative. Reverse bonds can be obtained without storing a second copy:

reverse = lat.reversed_bonds()

Repository layout

src/latticegeom/   package (public API plus private compilation pipeline)
src/latticegeom/tools/ explicit one-shot authoring and code-generation tools
tests/           geometry and boundary-condition tests
examples/        runnable usage examples
docs/            design notes
tmp/             ignored reference implementations

Inside the package, lattice.py contains the public object and queries, while the staged finite-geometry implementation lives in the private _compiler.py module. Composition lives in builders.py, reciprocal-space derivations in reciprocal.py, optional Matplotlib rendering in plotting.py, and one-shot neighbor discovery in tools/neighbors.py.

The initial API was informed by experience with NetKet-style lattice graphs, but the core here is a clean NumPy-based implementation centered on explicit integer periodic geometry.

examples/double_kagome.py shows how the six-site example from the reference work can be expressed with explicit integer shifts and independently typed bonds/plaquettes.

Documentation

The publishable documentation is available in both languages:

Build the bilingual site locally with python -m mkdocs serve. LatticeGeom is currently an alpha-stage library: its geometry invariants are tested, while the public API may still evolve before a stable 1.0 release.

Contributing

Bug reports, focused pull requests, and new lattice definitions are welcome. See CONTRIBUTING.md for the development workflow and project boundaries.

Citation

If LatticeGeom contributes to published work, cite the software using the metadata in CITATION.cff. GitHub can export the citation in common bibliography formats from that file.

License

LatticeGeom is released under the MIT License.

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages