LatticeGraph
The LatticeGraph class in QDK/Chemistry represents a weighted graph defining the connectivity and geometry of a lattice of sites.
It provides static methods to generate common lattice geometries.
As a core data class, it follows QDK/Chemistry’s immutable data pattern.
Overview
A LatticeGraph stores a (possibly weighted) adjacency matrix for a lattice of sites.
It is the primary input to the model Hamiltonian builders, where it defines which sites are connected and with what hopping strength.
Each qubit or orbital in the resulting Hamiltonian corresponds to a site in the lattice.
Properties
- Number of sites
Total number of vertices in the lattice.
- Number of edges
Number of unique undirected edges (counted once per pair).
- Adjacency matrix
Sparse or dense matrix of edge weights.
- Symmetry
Whether the adjacency matrix is symmetric (required for physical Hamiltonians).
Usage
The LatticeGraph is typically the starting point for any model Hamiltonian workflow in QDK/Chemistry.
It defines the lattice topology before model parameters (hopping, on-site energies, interactions) are applied.
Note
All built-in lattice factory methods produce symmetric (bidirectional) graphs by default.
For custom lattices constructed from edge dictionaries, use make_bidirectional() if needed.
Creating lattice graphs
QDK/Chemistry provides static methods to create lattice graphs for common geometries. For a brief overview of the available geometries, see the following table. For detailed information about each geometry and how to create them, see the following sections.
Lattice type |
Dimensions |
Total sites |
Description |
|---|---|---|---|
|
1D |
n |
Linear chain with nearest-neighbour edges |
|
2D |
nx × ny |
Square lattice with 4 neighbours per bulk site |
|
2D |
nx × ny |
Triangular lattice with 6 neighbours per bulk site |
|
2D |
2 × nx × ny |
Honeycomb with 3 neighbours per site (2 sites/unit cell) |
|
2D |
3 × nx × ny |
Kagome with corner-sharing triangles (3 sites/unit cell) |
One-dimensional lattices
Chain lattice
The simplest lattice geometry is a 1D chain of sites connected by nearest-neighbour edges.
Setting periodic=True adds an edge between the first and last site to form a ring.
Chain (n=6): 0 --- 1 --- 2 --- 3 --- 4 --- 5
Ring (n=6): 0 --- 1 --- 2 --- 3 --- 4 --- 5
|_____________________________|
#include <qdk/chemistry.hpp>
using namespace qdk::chemistry::data;
int main() {
// Create a 6-site open chain
auto chain = LatticeGraph::chain(6);
// Create a 6-site periodic chain (ring)
auto ring = LatticeGraph::chain(6, /*periodic=*/true);
// Create a chain with custom hopping weight
auto chain_weighted = LatticeGraph::chain(4, /*periodic=*/false, /*t=*/0.5);
# Create a 6-site open chain
chain = LatticeGraph.chain(6)
print(f"Chain: {chain.num_sites} sites, {chain.num_edges} edges")
# Create a 6-site periodic chain (ring)
ring = LatticeGraph.chain(6, periodic=True)
print(f"Ring: {ring.num_sites} sites, {ring.num_edges} edges")
# Create a chain with custom hopping weight
chain_weighted = LatticeGraph.chain(4, t=0.5)
Two-dimensional lattices
QDK/Chemistry provides static methods for the most commonly studied 2D lattice geometries. Sites are indexed in row-major order.
Each 2D geometry supports independent periodic boundary conditions along the x and y axes
(periodic_x and periodic_y).
When enabled, opposite edges of the lattice are connected, giving the lattice the topology of a
cylinder (one axis periodic) or a torus (both axes periodic).
See Periodic boundary conditions for more information.
Square lattice
The square lattice is the simplest 2D geometry, with four nearest neighbours per bulk site. With periodic boundary conditions, the horizontal and vertical edges wrap, so every site has exactly four neighbours.
4x3 square lattice:
8 --- 9 ---10 ---11
| | | |
4 --- 5 --- 6 --- 7
| | | |
0 --- 1 --- 2 --- 3
Triangular lattice
The triangular lattice adds a diagonal bond to each square plaquette, giving six nearest neighbours per bulk site. With periodic boundary conditions, all three bond directions (horizontal, vertical, and diagonal) wrap, so every site has exactly six neighbours.
3x3 triangular lattice:
6 --- 7 --- 8
| / | / |
3 --- 4 --- 5
| / | / |
0 --- 1 --- 2
Honeycomb lattice
The honeycomb lattice has two sites per unit cell (A and B sublattices), giving three nearest neighbours per site.
Total sites: 2 * nx * ny.
With periodic boundary conditions, the inter-cell bonds between the B and A sublattices wrap around the edges, so every site retains exactly three neighbours.
3x4 honeycomb lattice:
18-19-20-21-22-23
| | |
12-13-14-15-16-17
| | |
6--7--8--9-10-11
| | |
0--1--2--3--4--5
Kagome lattice
The kagome lattice has three sites per unit cell, arranged as corner-sharing triangles.
Total sites: 3 * nx * ny.
With periodic boundary conditions, the inter-cell bonds that form the down-triangles wrap around the edges, maintaining the corner-sharing pattern across the boundary.
3x2 kagome:
11 14 17
/ \ / \ / \
9---10--12---13--15---16
/ \ / \ /
2 5 8
/ \ / \ / \
0---1---3---4----6---7
// Create a 4x3 square lattice
auto square = LatticeGraph::square(4, 3);
// Create a 3x3 triangular lattice
auto triangular = LatticeGraph::triangular(3, 3);
// Create a 3x2 honeycomb lattice (2 sites per unit cell)
auto honeycomb = LatticeGraph::honeycomb(3, 2);
// Create a 3x2 kagome lattice (3 sites per unit cell)
auto kagome = LatticeGraph::kagome(3, 2);
// Create a periodic square lattice (torus topology)
auto torus =
LatticeGraph::square(4, 4, /*periodic_x=*/true, /*periodic_y=*/true);
# Create a 4x3 square lattice
square = LatticeGraph.square(4, 3)
print(f"Square: {square.num_sites} sites, {square.num_edges} edges")
# Create a 3x3 triangular lattice
triangular = LatticeGraph.triangular(3, 3)
print(f"Triangular: {triangular.num_sites} sites, {triangular.num_edges} edges")
# Create a 3x2 honeycomb lattice (2 sites per unit cell)
honeycomb = LatticeGraph.honeycomb(3, 2)
print(f"Honeycomb: {honeycomb.num_sites} sites, {honeycomb.num_edges} edges")
# Create a 3x2 kagome lattice (3 sites per unit cell)
kagome = LatticeGraph.kagome(3, 2)
print(f"Kagome: {kagome.num_sites} sites, {kagome.num_edges} edges")
# Create a periodic square lattice (torus topology)
torus = LatticeGraph.square(4, 4, periodic_x=True, periodic_y=True)
print(f"Torus: {torus.num_sites} sites, {torus.num_edges} edges")
Creating from adjacency data
For geometries not covered by the built-in methods, you can construct a LatticeGraph from a dense adjacency matrix, a sparse adjacency matrix, or an edge-weight dictionary.
// Create a lattice from a dense adjacency matrix (star graph)
Eigen::MatrixXd adj = Eigen::MatrixXd::Zero(5, 5);
for (int i = 1; i < 5; i++) {
adj(0, i) = 1.0;
adj(i, 0) = 1.0;
}
auto star_graph = LatticeGraph::from_dense_matrix(adj);
// Create a lattice from an edge-weight map
std::map<std::pair<std::uint64_t, std::uint64_t>, double> edges = {
{{0, 1}, 1.0}, {{1, 0}, 1.0}, {{1, 2}, 0.5}, {{2, 1}, 0.5}};
LatticeGraph custom_lattice(edges, /*num_sites=*/3);
// Make a directed graph bidirectional
std::map<std::pair<std::uint64_t, std::uint64_t>, double> directed_edges = {
{{0, 1}, 1.0}, {{1, 2}, 1.0}, {{2, 3}, 1.0}};
LatticeGraph directed(directed_edges, 4);
auto bidirectional = LatticeGraph::make_bidirectional(directed);
# Create a lattice from a dense adjacency matrix (star graph)
adj = np.zeros((5, 5))
for i in range(1, 5):
adj[0, i] = 1.0
adj[i, 0] = 1.0
star_graph = LatticeGraph.from_dense_matrix(adj)
print(f"Star graph: {star_graph.num_sites} sites, {star_graph.num_edges} edges")
# Create a lattice from an edge dictionary
edges = {(0, 1): 1.0, (1, 0): 1.0, (1, 2): 0.5, (2, 1): 0.5}
custom_lattice = LatticeGraph(edge_weights=edges, num_sites=3)
print(f"Custom: {custom_lattice.num_sites} sites, {custom_lattice.num_edges} edges")
# Make a directed graph bidirectional
directed_edges = {(0, 1): 1.0, (1, 2): 1.0, (2, 3): 1.0}
directed = LatticeGraph(edge_weights=directed_edges, num_sites=4)
bidirectional = LatticeGraph.make_bidirectional(directed)
print(f"Bidirectional: is_symmetric = {bidirectional.is_symmetric}")
Periodic boundary conditions
All built-in lattice factory methods support periodic boundary conditions.
For 1D chains, periodic=True adds an edge between the first and last site to form a ring.
For 2D lattices, boundary conditions along each axis are controlled independently:
periodic_x=Trueconnects the rightmost column back to the leftmost column, adding an edge for every row between the site atx = nx-1and the site atx = 0.periodic_y=Trueconnects the top row back to the bottom row, adding an edge for every column between the site aty = ny-1and the site aty = 0.When both are enabled, the lattice has the topology of a torus — there are no boundary sites, so every site has the same coordination number as a bulk site.
Periodic boundary conditions are commonly used to reduce finite-size effects in condensed matter simulations. Without them, sites on the edges and corners of the lattice have fewer neighbours than interior sites, which introduces artifacts. By wrapping the lattice, all sites become equivalent, better approximating the thermodynamic (infinite-lattice) limit.
The following diagram illustrates this for a 4×3 square lattice with both periodic_x and periodic_y enabled.
The ~~~ edges show the wrap-around connections that turn the open lattice into a torus:
4x3 square with periodic_x and periodic_y:
8 --- 9 ---10 ---11 ~~~ 8
| | | | |
4 --- 5 --- 6 --- 7 ~~~ 4
| | | | |
0 --- 1 --- 2 --- 3 ~~~ 0
~ ~ ~ ~
8 9 10 11
// Periodic chain (ring)
auto ring_example = LatticeGraph::chain(6, /*periodic=*/true);
// Cylinder: periodic in x only
auto cylinder =
LatticeGraph::square(4, 4, /*periodic_x=*/true, /*periodic_y=*/false);
// Torus: periodic in both directions
auto torus_example =
LatticeGraph::square(4, 4, /*periodic_x=*/true, /*periodic_y=*/true);
# Periodic chain (ring)
ring = LatticeGraph.chain(6, periodic=True)
print(f"Ring: {ring.num_sites} sites, {ring.num_edges} edges")
# Cylinder: periodic in x only
cylinder = LatticeGraph.square(4, 4, periodic_x=True, periodic_y=False)
print(f"Cylinder: {cylinder.num_sites} sites, {cylinder.num_edges} edges")
# Torus: periodic in both directions
torus = LatticeGraph.square(4, 4, periodic_x=True, periodic_y=True)
print(f"Torus: {torus.num_sites} sites, {torus.num_edges} edges")
Accessing lattice data
The LatticeGraph class provides methods to query connectivity, edge weights, and structural properties.
// Query lattice properties
auto lattice = LatticeGraph::chain(4);
// Check connectivity
bool connected_01 = lattice.are_connected(0, 1); // true
bool connected_02 = lattice.are_connected(0, 2); // false
// Get edge weight
double w01 = lattice.weight(0, 1); // 1.0
double w02 = lattice.weight(0, 2); // 0.0
// Check symmetry
bool symmetric = lattice.is_symmetric(); // true
// Get the full adjacency matrix
Eigen::MatrixXd adj_matrix = lattice.adjacency_matrix();
# Query lattice properties
lattice = LatticeGraph.chain(4)
# Check connectivity
print(f"Sites 0-1 connected: {lattice.are_connected(0, 1)}")
print(f"Sites 0-2 connected: {lattice.are_connected(0, 2)}")
# Get edge weight
print(f"Weight(0, 1) = {lattice.weight(0, 1)}")
print(f"Weight(0, 2) = {lattice.weight(0, 2)}")
# Check symmetry
print(f"Is symmetric: {lattice.is_symmetric}")
# Get the full adjacency matrix
adj_matrix = lattice.adjacency_matrix()
print(f"Adjacency matrix:\n{adj_matrix}")
Serialization
The LatticeGraph class supports serialization to and from JSON and HDF5 formats.
For detailed information about serialization in QDK/Chemistry, see the Serialization documentation.
Note
Lattice graph files use the .lattice_graph suffix before the file type extension, for example chain.lattice_graph.json and square.lattice_graph.hdf5.
auto lg = LatticeGraph::chain(4);
// Save to JSON
lg.to_json_file("chain.lattice_graph.json");
// Load from JSON
auto loaded = LatticeGraph::from_json_file("chain.lattice_graph.json");
// Save to HDF5
lg.to_hdf5_file("chain.lattice_graph.hdf5");
lattice = LatticeGraph.chain(4)
# Save to JSON
lattice.to_json_file(Path("chain.lattice_graph.json"))
# Load from JSON
loaded = LatticeGraph.from_json_file(Path("chain.lattice_graph.json"))
print(f"Loaded lattice: {loaded.num_sites} sites, {loaded.num_edges} edges")
# Save to HDF5
lattice.to_hdf5_file(Path("chain.lattice_graph.hdf5"))
Further reading
The above examples can be downloaded as complete C++ and Python scripts.
Serialization: Data serialization and deserialization in QDK/Chemistry