Lazy / infinite lattices
LatticeCore has two small hooks for working with lattices that are conceptually infinite, or that need to be materialised at a chosen cutoff before use:
materialize— an infinite-abstract-to-finite-lattice generic function.require_finite— a guard that throws if a lattice is not finite.
Together with the size trait hierarchy (see Traits reference), they cover the lattice-side half of chapter 06 of the architecture notes. The concept-side story lives in Quasiperiodic order.
Size traits
Every AbstractLattice advertises its extent through size_trait(lat), which returns one of:
FiniteSize{D}— an ordinary finite lattice with known per-axis cell counts. Safe for Monte Carlo.InfiniteSize— a lattice that is infinite in the honest sense. You can compute with it spectrally (tight binding on a translationally invariant chain, for instance), but you cannot run a Monte Carlo sweep on it.QuasiInfiniteSize{T}— infinite in principle, meant to be lowered to a finite lattice throughmaterializebefore being consumed.Tis the numeric type of the cutoff parameter.
The predicate is_finite(lat) is a one-liner derived from this trait:
is_finite(lat) = size_trait(lat) isa FiniteSizeReference lattices always return a FiniteSize, so is_finite(LineLattice(5)) and is_finite(SimpleSquareLattice(3, 3)) are both true.
Guarding Monte Carlo entry points
Monte Carlo algorithms walk every site, so running them on a non-finite lattice is a bug. The canonical guard is require_finite(lat), which throws ArgumentError if is_finite is false:
function run!(rng, state, lat::AbstractLattice, model, alg; kwargs...)
require_finite(lat)
# ... safe to iterate 1:num_sites(lat) from here ...
endThe error message mentions the offending lattice's size_trait and hints at materialize, so a user who passes an infinite abstract lattice by mistake gets a pointer to the fix.
The infinite-abstract → finite-materialisation pattern
The pattern LatticeCore encourages for any "infinite" structure is to keep the abstract description as its own type and implement a method of materialize that turns it into a FiniteSize lattice. materialize is a generic function with no typed supertype, so packages can plug in without inheriting from a LatticeCore hierarchy.
A minimal example for a hypothetical infinite Fibonacci chain:
struct InfiniteFibonacci
rules::Dict{Char, String}
axiom::Char
end
function LatticeCore.materialize(inf::InfiniteFibonacci; depth::Int)
word = inf.axiom |> Ref |> string # start from the axiom
for _ in 1:depth
word = join(get(inf.rules, c, string(c)) for c in word)
end
n = length(word) # number of physical sites
return LineLattice(n, PeriodicAxis()) # returns a FiniteSize lattice
endThe user's code then looks like
inf = InfiniteFibonacci(Dict('L' => "LS", 'S' => "L"), 'L')
lat = materialize(inf; depth = 10) # LineLattice{Float64, ...}
require_finite(lat) # safe
run!(Random.default_rng(), initial_state(lat), lat, model, alg)The same pattern applies in reciprocal space: HyperReciprocalLattice is the "infinite abstract" description of a cut-and-project quasicrystal's Fourier module, and BraggPeakSet is its finite materialisation at a cutoff. Both patterns are two halves of the same design rule: infinite structures live as abstract types; finite slices of them are subtypes of the main lattice hierarchy.
Cutoff conventions
The cutoff keyword argument is implementation-defined. You can make it anything that parameterises your structure, as long as you document it clearly. Common choices:
depth::Int— substitution depth (Fibonacci, L-system).radius::Real— spatial radius (Penrose cut-and-project).dims::NTuple{D, Int}— explicit per-axis sizes (generic rectangular sampling).
materialize is just a stable name; the semantics belong to each concrete infinite abstract type.
Unhandled cases
materialize has no default implementation, so calling it on a type that has not specialised it raises MethodError:
materialize("not an infinite lattice"; depth = 1) # MethodErrorThat is a deliberate choice: a default fallback would either silently succeed (dangerous) or always fail with a misleading error. MethodError tells the user exactly which type needs a new method.