From cd92ebee261f0aa210c7aec30c617739e7b2af5b Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 8 Sep 2025 17:48:53 +0200 Subject: [PATCH 01/61] Add accumulation register utilities to LQLGA lattice --- qlbm/lattice/lattices/lqlga_lattice.py | 74 ++++++++++++++++++++------ 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/qlbm/lattice/lattices/lqlga_lattice.py b/qlbm/lattice/lattices/lqlga_lattice.py index 893043d..22df380 100644 --- a/qlbm/lattice/lattices/lqlga_lattice.py +++ b/qlbm/lattice/lattices/lqlga_lattice.py @@ -97,11 +97,19 @@ def __init__(self, lattice_data, logger=...): else 0 ) + self.num_accumulation_qubits = 0 + self.indices_to_accumulate = [] + + self.__update_registers() + + def __update_registers(self): self.num_total_qubits = self.num_base_qubits + self.num_marker_qubits temp_registers = self.get_registers() - self.velocity_register, self.marker_register = temp_registers + self.velocity_register, self.marker_register, self.accumulation_register = ( + temp_registers + ) self.registers = tuple(flatten(temp_registers)) self.circuit = QuantumCircuit(*self.registers) @@ -129,7 +137,13 @@ def get_registers(self) -> Tuple[List[QuantumRegister], ...]: else [] ) - return (velocity_registers, marker_register) + accumulation_register = ( + [QuantumRegister(self.num_accumulation_qubits, name="acc")] + if self.has_accumulation_register() + else [] + ) + + return (velocity_registers, marker_register, accumulation_register) def gridpoint_index_tuple(self, gridpoint: Tuple[int, ...]) -> int: """ @@ -357,20 +371,50 @@ def set_geometries(self, geometries): """ self.geometries = [self.parse_geometry_dict(g) for g in geometries] - # Update the class attribute that depend on the register setup - temp_registers = self.get_registers() - - self.velocity_register, self.marker_register = temp_registers - self.registers = tuple(flatten(temp_registers)) - self.num_marker_qubits = ( - int(ceil(log2(len(self.geometries)))) - if self.has_multiple_geometries() - else 0 - ) - - self.num_total_qubits = self.num_base_qubits + self.num_marker_qubits - self.circuit = QuantumCircuit(*self.registers) + self.__update_registers() @override def has_multiple_geometries(self) -> bool: return len(self.geometries) > 1 + + def has_accumulation_register(self) -> bool: + """ + Whether the lattice has a register that accumulates quantities at each step. + + Returns + ------- + bool + Whether the lattice has a register that accumulates quantities at each step. + """ + return self.num_accumulation_qubits > 0 + + def use_accumulation_register(self, size: int, indices_to_accumulate: List[int]): + """ + Sets up the accumulation register of the lattice. + + This register has a weighted value added to it at the end of each time step. + The value is a linear combination of the occupancy of several velocity channels. + This in turn allows for time-averaged calculations for values related to pressure, + mass, or drag/lift coefficients. + + Parameters + ---------- + size : int + The size of the register that accumulates the value. + indices_to_accumulate : List[int] + The qubit indices that contribute to the accumulated value. + """ + if size <= 0: + raise LatticeException( + f"Accumulation register size has to be positive. Provided value: {size}" + ) + + if any(i < 0 or i >= self.num_base_qubits for i in indices_to_accumulate): + raise LatticeException( + f"Accumulation indices have to be between 0 and {self.num_base_qubits} for this lattice." + ) + + self.num_accumulation_qubits = size + self.indices_to_accumulate = indices_to_accumulate + + self.__update_registers() From 8819e10e69e6f616bfee84c208faed4bbb20a521 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 8 Sep 2025 17:52:17 +0200 Subject: [PATCH 02/61] Add Hamming Weight adder primitive --- qlbm/components/__init__.py | 2 + qlbm/components/common/__init__.py | 5 +- qlbm/components/common/primitives.py | 73 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/qlbm/components/__init__.py b/qlbm/components/__init__.py index 0cd14be..1afb9b4 100644 --- a/qlbm/components/__init__.py +++ b/qlbm/components/__init__.py @@ -28,6 +28,7 @@ EQCCollisionOperator, EQCPermutation, EQCRedistribution, + HammingWeightAdder, ) from .lqlga import ( LQLGA, @@ -71,4 +72,5 @@ "EQCCollisionOperator", "EQCPermutation", "EQCRedistribution", + "HammingWeightAdder", ] diff --git a/qlbm/components/common/__init__.py b/qlbm/components/common/__init__.py index 75ca482..139a116 100644 --- a/qlbm/components/common/__init__.py +++ b/qlbm/components/common/__init__.py @@ -1,13 +1,12 @@ """Common primitives used for multiple encodings.""" from .cbse_collision import EQCCollisionOperator, EQCPermutation, EQCRedistribution -from .primitives import ( - EmptyPrimitive, -) +from .primitives import EmptyPrimitive, HammingWeightAdder __all__ = [ "EmptyPrimitive", "EQCCollisionOperator", "EQCPermutation", "EQCRedistribution", + "HammingWeightAdder", ] diff --git a/qlbm/components/common/primitives.py b/qlbm/components/common/primitives.py index 79901a1..9c13715 100644 --- a/qlbm/components/common/primitives.py +++ b/qlbm/components/common/primitives.py @@ -4,8 +4,11 @@ from time import perf_counter_ns from typing import List, Tuple +import numpy as np +from numpy import pi from qiskit import QuantumCircuit from qiskit.circuit.library import MCMTGate, XGate +from qiskit.synthesis import synth_qft_full as QFT from typing_extensions import override from qlbm.components.base import LBMPrimitive @@ -106,3 +109,73 @@ def create_circuit(self) -> QuantumCircuit: @override def __str__(self) -> str: return f"[Primitive MCSwap with lattice {self.lattice}]" + + +class HammingWeightAdder(LBMPrimitive): + """ + QFT-based Hamming Weight adder. + + This primitive adds the hamming weight (number of 1s) in a given register :math:`x` + to the binary-encoded value of a second register :math:`y`. + """ + + x_register_size: int + """ + The size of the register encoding the hamming weight value to add. + """ + + y_register_size: int + """ + The size of the register to which the hamming weight is added. + """ + + def __init__( + self, + x_register_size: int, + y_register_size: int, + logger: Logger = getLogger("qlbm"), + ): + super().__init__(logger) + self.x_register_size = x_register_size + self.y_register_size = y_register_size + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self) -> QuantumCircuit: + circuit = QuantumCircuit(self.x_register_size + self.y_register_size) + + circuit.compose( + QFT(self.y_register_size), + inplace=True, + qubits=list( + range(self.x_register_size, self.x_register_size + self.y_register_size) + ), + ) + + angles = np.zeros(self.y_register_size) + for i in range(self.y_register_size): + angles[i] = 2 * pi / (2 ** (self.y_register_size - i)) + + for xi in range(self.x_register_size): + for k, yi in enumerate(range(self.y_register_size)): + circuit.cp(angles[k], xi, self.x_register_size + yi) + + circuit.compose( + QFT(self.y_register_size, inverse=True), + inplace=True, + qubits=list( + range(self.x_register_size, self.x_register_size + self.y_register_size) + ), + ) + + return circuit + + @override + def __str__(self): + return f"[Primitive HWAdder with with register size {self.x_register_size} and {self.y_register_size}]" From dde6a9152f6fb8b04b3ad861fd32707fd52a8097 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 8 Sep 2025 17:52:59 +0200 Subject: [PATCH 03/61] Add Hamming Weight Adder primitive tests --- test/unit/lqlga/lqlga_lattice_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/lqlga/lqlga_lattice_test.py b/test/unit/lqlga/lqlga_lattice_test.py index d25fa2c..931be65 100644 --- a/test/unit/lqlga/lqlga_lattice_test.py +++ b/test/unit/lqlga/lqlga_lattice_test.py @@ -5,7 +5,7 @@ def test_lqlga_lattice_num_registers_d1q2(lattice_d1q2_256): - assert len(lattice_d1q2_256.registers) == 256 # One empty register + assert len(lattice_d1q2_256.registers) == 256 # assert all(reg.size == 2 for reg in lattice_d1q2_256.velocity_register) assert lattice_d1q2_256.circuit.num_qubits == 512 From 67a93b4f28f895e2d21ce589d01cf8dec2729ce0 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 8 Sep 2025 17:53:28 +0200 Subject: [PATCH 04/61] Add Hamming Weight Adder primitive tests --- .../unit/lqlga/circuits/hamming_adder_test.py | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 test/unit/lqlga/circuits/hamming_adder_test.py diff --git a/test/unit/lqlga/circuits/hamming_adder_test.py b/test/unit/lqlga/circuits/hamming_adder_test.py new file mode 100644 index 0000000..7eba82a --- /dev/null +++ b/test/unit/lqlga/circuits/hamming_adder_test.py @@ -0,0 +1,105 @@ +import pytest +from qiskit import QuantumCircuit, transpile +from qiskit_aer import AerSimulator +from qiskit.result import Counts + +from qlbm.components.common import HammingWeightAdder + + +def bit_string_to_bool_list(bitstring): + return [x == "1" for x in bitstring] + + +def hamming_weight(bitstring): + return len([True for x in bitstring if x == "1"]) + + +def get_count_from_circuit(circuit, num_shots=128) -> Counts: + sim = AerSimulator() + tqc = transpile(circuit, sim) + + res = sim.run(tqc, num_shots=num_shots).result() + return res.get_counts() + + +def test_hamming_adder_all_0s(): + adder = HammingWeightAdder(3, 5).circuit + adder.measure_all() + counts = get_count_from_circuit(adder) + + assert len(counts) == 1 + assert (int(s, 2) == 0 for s in counts) + + +def test_hamming_adder_1plus0(): + circuit = QuantumCircuit(8) + circuit.x(3) + adder = HammingWeightAdder(4, 4).circuit + adder.measure_all() + circuit.compose(adder, inplace=True) + counts = get_count_from_circuit(circuit) + + assert len(counts) == 1 + assert all(int(s[4:][::-1], 2) == 1 for s in counts) + + +def test_hamming_adder_2plus0(): + # Hamming weight 2 in the x register + # Number 0 in the y register + circuit = QuantumCircuit(8) + circuit.x(0) + circuit.x(3) + adder = HammingWeightAdder(4, 4).circuit + adder.measure_all() + circuit.compose(adder, inplace=True) + counts = get_count_from_circuit(circuit) + + assert len(counts) == 1 + print(counts) + assert all(int(s[:4], 2) == 2 for s in counts) + + +def test_hamming_adder_2plus4(): + # Hamming weight 2 in the x register + # Number 4 in the y register + circuit = QuantumCircuit(8) + circuit.x(0) + circuit.x(3) + circuit.x(6) + adder = HammingWeightAdder(4, 4).circuit + adder.measure_all() + circuit.compose(adder, inplace=True) + counts = get_count_from_circuit(circuit) + + assert len(counts) == 1 + print(counts) + assert all(int(s[:4], 2) == 6 for s in counts) + + +def test_hamming_adder_twice(): + # Hamming weight 2 in the x register + # Number 0 in the y register + circuit = QuantumCircuit(8) + circuit.x(0) + circuit.x(3) + adder = HammingWeightAdder(4, 4).circuit + circuit.compose(adder, inplace=True) + circuit.compose(adder, inplace=True) + circuit.measure_all() + counts = get_count_from_circuit(circuit) + + assert len(counts) == 1 + assert all(int(s[:4], 2) == 4 for s in counts) + + +def test_hamming_adder_superposition_x(): + circuit = QuantumCircuit(8) + circuit.h([0, 1, 2, 3]) + circuit.x(6) + adder = HammingWeightAdder(4, 4).circuit + adder.measure_all() + circuit.compose(adder, inplace=True) + counts = get_count_from_circuit(circuit) + print(counts) + assert len(counts) == 16 + assert all(int(s[:4], 2) == (hamming_weight(s[4:]) + 4) for s in counts) From 4273caef8a3950cb5ea8aee822394b214039f420 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 27 Oct 2025 15:52:33 +0100 Subject: [PATCH 05/61] Improve support for accumulation and marker qubits in LQLGA lattice --- qlbm/lattice/lattices/lqlga_lattice.py | 31 ++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/qlbm/lattice/lattices/lqlga_lattice.py b/qlbm/lattice/lattices/lqlga_lattice.py index 22df380..2415e5b 100644 --- a/qlbm/lattice/lattices/lqlga_lattice.py +++ b/qlbm/lattice/lattices/lqlga_lattice.py @@ -1,6 +1,7 @@ """Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.LQLGA` algorithm.""" from itertools import product +from logging import getLogger from math import prod from typing import Dict, List, Tuple, cast, override @@ -75,7 +76,11 @@ class LQLGALattice(Lattice): velocity_register: QuantumRegister """The quantum register representing the velocities of the lattice.""" - def __init__(self, lattice_data, logger=...): + def __init__( + self, + lattice_data, + logger=getLogger("qlbm"), + ): super().__init__(lattice_data, logger) self.num_gridpoints, self.num_velocities, self.shapes, self.discretization = ( @@ -370,7 +375,11 @@ def set_geometries(self, geometries): A list of geometries to simulate on the same lattice. """ self.geometries = [self.parse_geometry_dict(g) for g in geometries] - + self.num_marker_qubits = ( + int(ceil(log2(len(self.geometries)))) + if self.has_multiple_geometries() + else 0 + ) self.__update_registers() @override @@ -418,3 +427,21 @@ def use_accumulation_register(self, size: int, indices_to_accumulate: List[int]) self.indices_to_accumulate = indices_to_accumulate self.__update_registers() + + def accumulation_index(self) -> List[int]: + """ + Get the indices of the qubits used for the accumulation register. + + Returns + ------- + List[int] + The absolute indices of the accumulation qubits. + """ + return list( + range( + self.num_base_qubits + self.num_marker_qubits, + self.num_base_qubits + + self.num_marker_qubits + + self.num_accumulation_qubits, + ) + ) From 67b73fe26e5981a6333a4877718dc1540fe635a7 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 27 Oct 2025 15:53:04 +0100 Subject: [PATCH 06/61] Change error to exception in discrtetization properties --- qlbm/lattice/spacetime/properties_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qlbm/lattice/spacetime/properties_base.py b/qlbm/lattice/spacetime/properties_base.py index 0d7d29a..6ec50c1 100644 --- a/qlbm/lattice/spacetime/properties_base.py +++ b/qlbm/lattice/spacetime/properties_base.py @@ -13,6 +13,8 @@ import numpy as np from qiskit import QuantumRegister +from qlbm.tools.exceptions import LatticeException + class LatticeDiscretization(Enum): """ @@ -118,7 +120,7 @@ def get_num_velocities( The number of velocities corresponding to the discretization. """ if discretization not in LatticeDiscretizationProperties.num_velocities: - raise ValueError(f"Discretization {discretization} is not supported.") + raise LatticeException(f"Discretization {discretization} is not supported.") return LatticeDiscretizationProperties.num_velocities[discretization] From 3f055527c4a873dbf446566ab5d34b005d672f9d Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 27 Oct 2025 15:53:41 +0100 Subject: [PATCH 07/61] Add ABE Lattice --- qlbm/lattice/__init__.py | 2 + qlbm/lattice/lattices/abe_lattice.py | 287 +++++++++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 qlbm/lattice/lattices/abe_lattice.py diff --git a/qlbm/lattice/__init__.py b/qlbm/lattice/__init__.py index b709602..45364e4 100644 --- a/qlbm/lattice/__init__.py +++ b/qlbm/lattice/__init__.py @@ -13,6 +13,7 @@ Circle, ) from .lattices import CollisionlessLattice, Lattice +from .lattices.abe_lattice import ABELattice from .lattices.lqlga_lattice import LQLGALattice from .lattices.spacetime_lattice import SpaceTimeLattice from .spacetime.properties_base import ( @@ -22,6 +23,7 @@ __all__ = [ "Lattice", + "ABELattice", "CollisionlessLattice", "SpaceTimeLattice", "LQLGALattice", diff --git a/qlbm/lattice/lattices/abe_lattice.py b/qlbm/lattice/lattices/abe_lattice.py new file mode 100644 index 0000000..635b5c6 --- /dev/null +++ b/qlbm/lattice/lattices/abe_lattice.py @@ -0,0 +1,287 @@ +"""Implementation of the Amplitude-Based Encoding (ABE) lattice for generic DdQq discretizations.""" + +from logging import getLogger +from typing import Dict, List, Tuple + +from numpy import ceil, log2 +from qiskit import QuantumCircuit, QuantumRegister +from typing_extensions import override + +from qlbm.lattice.geometry.shapes.base import Shape +from qlbm.lattice.spacetime.properties_base import ( + LatticeDiscretization, + LatticeDiscretizationProperties, +) +from qlbm.tools.exceptions import LatticeException +from qlbm.tools.utils import dimension_letter, flatten, is_two_pow + +from .base import Lattice + + +class ABELattice(Lattice): + """TODO.""" + + discretization: LatticeDiscretization + """The discretization of the lattice, one of :class:`.LatticeDiscretization`.""" + + num_gridpoints: List[int] + """The number of gridpoints in each dimension of the lattice. + **Important** : for easier compatibility with binary arithmetic, the number of gridpoints + specified in the input dictionary is one larger than the one held in the ``Lattice``.""" + + shapes: Dict[str, List[Shape]] + """The shapes of the lattice, which are used to define the geometry of the lattice. + The key consists of the type of the shape and the name of the shape, e.g. "bounceback" or "specular". + """ + + num_base_qubits: int + """The number of qubits required to represent the lattice.""" + + registers: Tuple[QuantumRegister, ...] + + def __init__( + self, + lattice_data, + logger=getLogger("qlbm"), + ): + super().__init__(lattice_data, logger) + self.num_gridpoints, self.num_velocities, self.shapes, self.discretization = ( + self.parse_input_data(lattice_data) + ) # type: ignore + self.geometries: List[Dict[str, List[Shape]]] = [self.shapes] + self.num_dims = len(self.num_gridpoints) + self.num_velocities_per_point = ( + LatticeDiscretizationProperties.get_num_velocities(self.discretization) + ) + + for dim in range(self.num_dims): + if not is_two_pow(self.num_gridpoints[dim] + 1): # type: ignore + raise LatticeException( + f"Lattice has a number of grid points that is not divisible by 2 in dimension {dimension_letter(dim)}." + ) + + self.num_grid_qubits = int( + sum(map(lambda x: ceil(log2(x)), self.num_gridpoints)) + ) + self.num_velocity_qubits = int(ceil(log2(self.num_velocities_per_point))) + self.num_base_qubits = self.num_grid_qubits + self.num_velocity_qubits + + self.num_obstacle_qubits = self.__num_obstacle_qubits() + self.num_comparator_qubits = 2 * (self.num_dims - 1) + self.num_ancilla_qubits = self.num_comparator_qubits + self.num_obstacle_qubits + + self.num_total_qubits = self.num_base_qubits + self.num_ancilla_qubits + + temporary_registers = self.get_registers() + ( + self.grid_registers, + self.velocity_registers, + self.ancilla_comparator_register, + self.ancilla_object_register, + ) = temporary_registers + + self.registers = tuple(flatten(temporary_registers)) + self.circuit = QuantumCircuit(*self.registers) + + def grid_index(self, dim: int | None = None) -> List[int]: + """Get the indices of the qubits used that encode the grid values for the specified dimension. + + Parameters + ---------- + dim : int | None, optional + The dimension of the grid for which to retrieve the grid qubit indices, by default ``None``. + When ``dim`` is ``None``, the indices of all grid qubits for all dimensions are returned. + + Returns + ------- + List[int] + A list of indices of the qubits used to encode the grid values for the given dimension. + + Raises + ------ + LatticeException + If the dimension does not exist. + """ + if dim is None: + return list( + range( + self.num_grid_qubits, + ) + ) + + if dim >= self.num_dims or dim < 0: + raise LatticeException( + f"Cannot index grid register for dimension {dim} in {self.num_dims}-dimensional lattice." + ) + + previous_qubits = sum([self.num_gridpoints[d].bit_length() for d in range(dim)]) + + return list( + range( + previous_qubits, previous_qubits + self.num_gridpoints[dim].bit_length() + ) + ) + + def velocity_index(self) -> List[int]: + """Get the indices of the qubits used that encode the discrete velocities. + + Returns + ------- + List[int] + A list of indices of the qubits that encode the velocity discretization. + """ + return list( + range( + self.num_grid_qubits, + self.num_grid_qubits + self.num_velocity_qubits, + ) + ) + + def ancillae_comparator_index(self, index: int | None = None) -> List[int]: + """Get the indices of the qubits used as comparator ancillae for the specified index. + + Parameters + ---------- + index : int | None, optional + The index for which to retrieve the comparator qubit indices, by default ``None``. + There are `num_dims-1` available indices (i.e., 1 for 2D and 2 for 3D). + When `index` is ``None``, the indices of ancillae qubits for all dimensions are returned. + + Returns + ------- + List[int] + A list of indices of the qubits used as obstacle ancilla for the given dimension. + By convention, the 0th qubit in the returned list is used + for lower bound comparison and the 1st is used for upper bound comparisons. + + Raises + ------ + LatticeException + If the dimension does not exist. + """ + if index is None: + return list( + range( + self.num_base_qubits, + self.num_base_qubits + 2 * (self.num_dims - 1), + ) + ) + + if index >= self.num_dims - 1 or index < 0: + raise LatticeException( + f"Cannot index ancilla comparator register for index {index} in {self.num_dims}-dimensional lattice. Maximum is {self.num_dims - 2}." + ) + + return list( + range( + self.num_base_qubits, self.num_base_qubits + self.num_comparator_qubits + ) + ) + + def ancillae_obstacle_index(self, index: int | None = None) -> List[int]: + """Get the indices of the qubits used as obstacle ancilla for the specified dimension. + + Parameters + ---------- + index : int | None, optional + The index of the grid for which to retrieve the obstacle qubit index, by default ``None``. + When ``index`` is ``None``, the indices of ancillae qubits for all dimensions are returned. + For 2D lattices with only bounce-back boundary-conditions, only one obstacle + qubit is required. + For all other configurations, the algorithm uses ``2d-2`` obstacle qubits. + + Returns + ------- + List[int] + A list of indices of the qubits used as obstacle ancilla for the given dimension. + + Raises + ------ + LatticeException + If the dimension does not exist. + """ + if index is None: + return list( + range( + self.num_base_qubits + self.num_comparator_qubits, + self.num_base_qubits + + self.num_comparator_qubits + + self.num_obstacle_qubits, + ) + ) + + if index >= self.num_obstacle_qubits or index < 0: + raise LatticeException( + f"Cannot index ancilla obstacle register for index {index}. Maximum index for this lattice is {self.num_obstacle_qubits - 1}." + ) + + return [self.num_base_qubits + self.num_comparator_qubits + index] + + def __num_obstacle_qubits(self) -> int: + all_obstacle_bounceback: bool = len( + [ + b + for b in flatten(list(self.shapes.values())) + if b.boundary_condition == "bounceback" + ] + ) == len(flatten(list(self.shapes.values()))) + if all_obstacle_bounceback: + # A single qubit suffices to determine + # Whether particles have streamed inside the object + return 1 + # If there is at least one object with specular reflection + # 2 ancilla qubits are required for velocity inversion + else: + return self.num_dims + + @override + def get_registers(self) -> Tuple[List[QuantumRegister], ...]: + """Generates the encoding-specific register required for the streaming step. + + For this encoding, different registers encode (i) the velocity direction, + (ii) the velocity discretization, (iii) the velocity ancillae, + and (iv) the grid encoding. + + Returns + ------- + List[int] + Tuple[QuantumRegister]: The 4-tuple of qubit registers encoding the streaming step. + """ + # d ancilla qubits used to conditionally reflect velocities + ancilla_object_register = [ + QuantumRegister(self.num_obstacle_qubits, name="a_o") + ] + + # 2(d-1) ancilla qubits + ancilla_comparator_register = [ + QuantumRegister(self.num_comparator_qubits, name="a_c") + ] + + # Velocity qubits + velocity_registers = [QuantumRegister(self.num_velocity_qubits, name="v")] + + # Grid qubits + grid_registers = [ + QuantumRegister(gp.bit_length(), name=f"g_{dimension_letter(c)}") + for c, gp in enumerate(self.num_gridpoints) + ] + + return ( + grid_registers, + velocity_registers, + ancilla_comparator_register, + ancilla_object_register, + ) + + @override + def logger_name(self) -> str: + gp_string = "" + for c, gp in enumerate(self.num_gridpoints): + gp_string += f"{gp + 1}" + if c < len(self.num_gridpoints) - 1: + gp_string += "x" + return f"abelattice-{self.num_dims}d-{gp_string}-{len(flatten(list(self.shapes.values())))}-obstacle" + + @override + def has_multiple_geometries(self): + return False # multiple geometries unsupported for CQBM From 65f31dbd106bf00402a52aac59b5f83c689f7edf Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 27 Oct 2025 15:54:00 +0100 Subject: [PATCH 08/61] Add ABE Lattice tests --- test/unit/lattice/__init__.py | 0 .../lattice/abe_lattice_exception_test.py | 83 +++++++++++++++++++ .../lattice/abe_lattice_properties_test.py | 58 +++++++++++++ test/unit/lattice/conftest.py | 79 ++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 test/unit/lattice/__init__.py create mode 100644 test/unit/lattice/abe_lattice_exception_test.py create mode 100644 test/unit/lattice/abe_lattice_properties_test.py create mode 100644 test/unit/lattice/conftest.py diff --git a/test/unit/lattice/__init__.py b/test/unit/lattice/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/unit/lattice/abe_lattice_exception_test.py b/test/unit/lattice/abe_lattice_exception_test.py new file mode 100644 index 0000000..05ed6f4 --- /dev/null +++ b/test/unit/lattice/abe_lattice_exception_test.py @@ -0,0 +1,83 @@ +import pytest + +from qlbm.lattice import ABELattice +from qlbm.tools.exceptions import LatticeException + + +def test_lattice_exception_empty_dict(): + with pytest.raises(LatticeException) as excinfo: + ABELattice({}) + + assert 'Input configuration missing "lattice" properties.' == str(excinfo.value) + + +def test_lattice_exception_no_dims(): + with pytest.raises(LatticeException) as excinfo: + ABELattice({"lattice": {}}) + + assert 'Lattice configuration missing "dim" properties.' == str(excinfo.value) + + +def test_lattice_exception_no_velocities(): + with pytest.raises(LatticeException) as excinfo: + ABELattice({"lattice": {"dim": {}}}) + + assert 'Lattice configuration missing "velocities" properties.' == str( + excinfo.value + ) + +def test_lattice_exception_mismatched_velocities_and_dims(): + with pytest.raises(LatticeException) as excinfo: + ABELattice({"lattice": {"dim": {"x": 64}, "velocities": "D2Q4"}}) + + assert "Velocity specification dimensions (2) do not match lattice dimensions (1)." == str(excinfo.value) + + +def test_lattice_exception_unsupported_discretization(): + with pytest.raises(LatticeException) as excinfo: + ABELattice({"lattice": {"dim": {"x": 64}, "velocities": {"x": 4}}}) + + assert 'Discretization LatticeDiscretization.CFLDISCRETIZATION is not supported.' == str( + excinfo.value + ) + +def test_lattice_exception_mismatched_bad_dimensions(): + with pytest.raises(LatticeException) as excinfo: + ABELattice( + { + "lattice": { + "dim": {"x": 64, "y": 127}, + "velocities": "D2Q4", + } + } + ) + + assert ( + "Lattice has a number of grid points that is not divisible by 2 in dimension y." + == str(excinfo.value) + ) + +def test_lattice_exception_mismatched_bad_object_dimensions(): + with pytest.raises(LatticeException) as excinfo: + ABELattice( + { + "lattice": { + "dim": {"x": 64, "y": 64}, + "velocities": "D2Q4", + }, + "geometry": [ + { + "shape": "cuboid", + "x": [5, 6], + "y": [1, 2], + "z": [1, 2], + "boundary": "specular", + }, + ], + } + ) + + assert "Obstacle 1 has 3 dimensions whereas the lattice has 2." == str( + excinfo.value + ) + \ No newline at end of file diff --git a/test/unit/lattice/abe_lattice_properties_test.py b/test/unit/lattice/abe_lattice_properties_test.py new file mode 100644 index 0000000..cf8db50 --- /dev/null +++ b/test/unit/lattice/abe_lattice_properties_test.py @@ -0,0 +1,58 @@ +import pytest + +from qlbm.lattice import ABELattice +from qlbm.tools.exceptions import LatticeException + + +def test_2d_abe_lattice_basic_properties(lattice_2d_16x16_1_obstacle: ABELattice): + assert lattice_2d_16x16_1_obstacle.num_dims == 2 + assert lattice_2d_16x16_1_obstacle.num_gridpoints == [15, 15] + assert lattice_2d_16x16_1_obstacle.num_ancilla_qubits == 3 + assert lattice_2d_16x16_1_obstacle.num_grid_qubits == 8 + assert lattice_2d_16x16_1_obstacle.num_velocity_qubits == 2 + assert lattice_2d_16x16_1_obstacle.num_total_qubits == 13 + + +def test_2d_lattice_grid_register(lattice_2d_16x16_1_obstacle: ABELattice): + assert lattice_2d_16x16_1_obstacle.grid_index(0) == list(range(4)) + assert lattice_2d_16x16_1_obstacle.grid_index(1) == list(range(4, 8)) + assert lattice_2d_16x16_1_obstacle.grid_index() == list(range(8)) + + with pytest.raises(LatticeException) as excinfo: + lattice_2d_16x16_1_obstacle.grid_index(2) + assert ( + "Cannot index grid register for dimension 2 in 2-dimensional lattice." + == str(excinfo.value) + ) + + +def test_2d_lattice_velocity_register( + lattice_2d_16x16_1_obstacle: ABELattice, +): + assert lattice_2d_16x16_1_obstacle.velocity_index() == [8, 9] + +def test_2d_lattice_ancilla_comparator_register( + lattice_2d_16x16_1_obstacle: ABELattice, +): + assert lattice_2d_16x16_1_obstacle.ancillae_comparator_index(0) == [10, 11] + assert lattice_2d_16x16_1_obstacle.ancillae_comparator_index() == [10, 11] + + with pytest.raises(LatticeException) as excinfo: + lattice_2d_16x16_1_obstacle.ancillae_comparator_index(1) + assert ( + "Cannot index ancilla comparator register for index 1 in 2-dimensional lattice. Maximum is 0." + == str(excinfo.value) + ) + + +def test_2d_lattice_ancilla_obstacle_register( + lattice_2d_16x16_1_obstacle: ABELattice, +): + assert lattice_2d_16x16_1_obstacle.ancillae_obstacle_index() == [8 + 2 + 2] + + with pytest.raises(LatticeException) as excinfo: + lattice_2d_16x16_1_obstacle.ancillae_obstacle_index(2) + assert ( + "Cannot index ancilla obstacle register for index 2. Maximum index for this lattice is 0." + == str(excinfo.value) + ) diff --git a/test/unit/lattice/conftest.py b/test/unit/lattice/conftest.py new file mode 100644 index 0000000..1d5fa0d --- /dev/null +++ b/test/unit/lattice/conftest.py @@ -0,0 +1,79 @@ +import pytest + +from qlbm.lattice.geometry.shapes.block import Block +from qlbm.lattice.lattices.abe_lattice import ABELattice + + +# 1D Lattices +@pytest.fixture +def dummy_1d_lattice() -> ABELattice: + return ABELattice( + 0, + { + "lattice": { + "dim": {"x": 256}, + "velocities": "D1Q3", + }, + }, + ) + + +@pytest.fixture +def lattice_1d_16_1_obstacle() -> ABELattice: + return ABELattice( + { + "lattice": { + "dim": {"x": 16}, + "velocities": "D1Q2" + }, + "geometry": [ + {"shape": "cuboid", "x": [4, 6], "boundary": "bounceback"}, + ], + }, + ) + + + +# 2D Lattices +@pytest.fixture +def dummy_2d_lattice() -> ABELattice: + return ABELattice( + 0, + { + "lattice": { + "dim": {"x": 32, "y": 32}, + "velocities": "D2Q4" + }, + }, + ) + + +@pytest.fixture +def lattice_2d_16x16_1_obstacle() -> ABELattice: + return ABELattice( + { + "lattice": { + "dim": {"x": 16, "y": 16}, + "velocities": "D2Q4" + }, + "geometry": [ + { + "shape": "cuboid", + "x": [2, 6], + "y": [5, 10], + "boundary": "bounceback", + }, + ], + }, + ) + + +# Shapes +@pytest.fixture +def simple_1d_block() -> Block: + return Block([(5, 11)], [4], "bounceback") + + +@pytest.fixture +def simple_large_1d_block() -> Block: + return Block([(2, 14)], [4], "bounceback") From 49fcd920f02c195207eb7d993d20af8b10adeac6 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:35:00 +0100 Subject: [PATCH 09/61] Add ABE streaming operator --- qlbm/components/abe/streaming.py | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 qlbm/components/abe/streaming.py diff --git a/qlbm/components/abe/streaming.py b/qlbm/components/abe/streaming.py new file mode 100644 index 0000000..2148b18 --- /dev/null +++ b/qlbm/components/abe/streaming.py @@ -0,0 +1,101 @@ +from logging import Logger, getLogger +from math import pi +from time import perf_counter_ns +from typing import List + +import numpy as np +from qiskit import QuantumCircuit +from qiskit.circuit.library import MCXGate +from qiskit.synthesis import synth_qft_full as QFT +from typing_extensions import override + +from qlbm.components.base import CQLBMOperator, LBMOperator, LBMPrimitive +from qlbm.components.collisionless.streaming import PhaseShift +from qlbm.lattice import CollisionlessLattice +from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.spacetime.properties_base import LatticeDiscretization +from qlbm.tools import CircuitException, bit_value +from qlbm.tools.exceptions import LatticeException + + +class ABEStreamingOperator(LBMOperator): + """TODO.""" + + lattice: ABELattice + + def __init__( + self, + lattice: ABELattice, + logger: Logger = getLogger("qlbm"), + ) -> None: + super().__init__(lattice, logger) + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self) -> QuantumCircuit: + if self.lattice.discretization == LatticeDiscretization.D1Q3: + return self.__create_circuit_d1q3() + + raise LatticeException("ABE only currently supported in D1Q3") + + def __create_circuit_d1q3(self): + circuit = self.lattice.circuit.copy() + + circuit.compose( + QFT(self.lattice.num_grid_qubits), + qubits=self.lattice.grid_index(), + inplace=True, + ) + + # 01 streaming in the positive direction + circuit.x(self.lattice.velocity_index()[0]) + + # Controlled Phase Gates for the positive direction + circuit.compose( + PhaseShift( + num_qubits=len(self.lattice.grid_index()), + positive=True, + logger=self.logger, + ) + .circuit.control(2) + .decompose(), + qubits=self.lattice.velocity_index() + self.lattice.grid_index(), + inplace=True, + ) + + # 10 Streaming in the negative direction (and resetting the previous state prep) + circuit.x(self.lattice.velocity_index()) + + circuit.compose( + PhaseShift( + num_qubits=len(self.lattice.grid_index()), + positive=False, # Negative this time + logger=self.logger, + ) + .circuit.control(2) + .decompose(), + qubits=self.lattice.velocity_index() + self.lattice.grid_index(), + inplace=True, + ) + + # Undo the second state prep + circuit.x(self.lattice.velocity_index()[1]) + + # Inverse QFT to return the grid to the computational basis + circuit.compose( + QFT(self.lattice.num_grid_qubits, inverse=True), + qubits=self.lattice.grid_index(), + inplace=True, + ) + + return circuit + + @override + def __str__(self) -> str: + return f"[Operator ABEStreaming with lattice {self.lattice}]" From bcec240ded883cd9c713e52f95741eba1391b20a Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:35:35 +0100 Subject: [PATCH 10/61] Add ABE grid measurement primitive --- qlbm/components/abe/measurement.py | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 qlbm/components/abe/measurement.py diff --git a/qlbm/components/abe/measurement.py b/qlbm/components/abe/measurement.py new file mode 100644 index 0000000..69762dd --- /dev/null +++ b/qlbm/components/abe/measurement.py @@ -0,0 +1,56 @@ +from enum import Enum +from logging import Logger, getLogger +from time import perf_counter_ns +from typing import List + +from qiskit import ClassicalRegister, QuantumCircuit +from qiskit.synthesis import synth_qft_full as QFT +from typing_extensions import override + +from qlbm.components.base import LBMPrimitive +from qlbm.components.collisionless.streaming import SpeedSensitivePhaseShift +from qlbm.lattice import CollisionlessLattice +from qlbm.lattice.geometry.encodings.collisionless import ReflectionResetEdge +from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.tools import flatten + + +class ABEGridMeasurement(LBMPrimitive): + """TODO.""" + + def __init__( + self, + lattice: ABELattice, + logger: Logger = getLogger("qlbm"), + ) -> None: + super().__init__(logger) + self.lattice = lattice + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self) -> QuantumCircuit: + circuit = self.lattice.circuit.copy() + circuit.add_register( + ClassicalRegister( + self.lattice.num_grid_qubits + self.lattice.num_velocity_qubits + ) + ) + + circuit.measure( + self.lattice.grid_index() + self.lattice.velocity_index(), + list( + range(self.lattice.num_grid_qubits + self.lattice.num_velocity_qubits) + ), + ) + + return circuit + + @override + def __str__(self) -> str: + return f"[Primitive ABEGridMeasurement with lattice {self.lattice}]" From 3e1ce08f113efec633d899d3301ad1772056358d Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:36:28 +0100 Subject: [PATCH 11/61] Add ABE initial conditions --- qlbm/components/abe/initial.py | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 qlbm/components/abe/initial.py diff --git a/qlbm/components/abe/initial.py b/qlbm/components/abe/initial.py new file mode 100644 index 0000000..0505210 --- /dev/null +++ b/qlbm/components/abe/initial.py @@ -0,0 +1,55 @@ +from enum import Enum +from logging import Logger, getLogger +from time import perf_counter_ns +from typing import List + +from qiskit import ClassicalRegister, QuantumCircuit +from qiskit.synthesis import synth_qft_full as QFT +from typing_extensions import override + +from qlbm.components.base import LBMPrimitive +from qlbm.components.collisionless.streaming import SpeedSensitivePhaseShift +from qlbm.components.common.primitives import TruncatedQFT +from qlbm.lattice import CollisionlessLattice +from qlbm.lattice.geometry.encodings.collisionless import ReflectionResetEdge +from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.tools import flatten + + +class ABEInitialConditions(LBMPrimitive): + """TODO.""" + + def __init__( + self, + lattice: ABELattice, + logger: Logger = getLogger("qlbm"), + ) -> None: + super().__init__(logger) + self.lattice = lattice + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self) -> QuantumCircuit: + circuit = QuantumCircuit(*self.lattice.registers) + + circuit.compose( + TruncatedQFT( + self.lattice.num_velocity_qubits, + self.lattice.num_velocities_per_point, + self.logger, + ).circuit, + qubits=self.lattice.velocity_index(), + inplace=True, + ) + + return circuit + + @override + def __str__(self) -> str: + return f"[Primitive ABEInitialConditions with lattice {self.lattice}]" From d40a3bbeb4c4402e8917f81c5e064194948759dc Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:36:52 +0100 Subject: [PATCH 12/61] Add ABE QLBM --- qlbm/components/abe/abe.py | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 qlbm/components/abe/abe.py diff --git a/qlbm/components/abe/abe.py b/qlbm/components/abe/abe.py new file mode 100644 index 0000000..505c48e --- /dev/null +++ b/qlbm/components/abe/abe.py @@ -0,0 +1,64 @@ +"""The end-to-end algorithm of the Collisionless Quantum Lattice Boltzmann Algorithm first introduced in :cite:t:`collisionless` and later extended in :cite:t:`qmem`.""" + +from logging import Logger, getLogger +from time import perf_counter_ns + +from qiskit import QuantumCircuit +from typing_extensions import override + +from qlbm.components.abe.averaged_collision import ABEAveragedCollisionOperator +from qlbm.components.base import LBMAlgorithm +from qlbm.lattice import CollisionlessLattice +from qlbm.lattice.geometry.shapes.block import Block +from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.tools.exceptions import LatticeException +from qlbm.tools.utils import get_time_series + +from .streaming import ABEStreamingOperator + + +class ABECQLBM(LBMAlgorithm): + """TODO.""" + + def __init__( + self, + lattice: ABELattice, + logger: Logger = getLogger("qlbm"), + ) -> None: + super().__init__(lattice, logger) + self.lattice: ABELattice = lattice + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self): + circuit = QuantumCircuit( + *self.lattice.registers, + ) + + circuit.compose( + ABEStreamingOperator( + self.lattice, + logger=self.logger, + ).circuit, + inplace=True, + ) + + # circuit.compose( + # ABEAveragedCollisionOperator( + # self.lattice, + # logger=self.logger, + # ).circuit, + # inplace=True, + # ) + + return circuit + + @override + def __str__(self) -> str: + return f"[Algorithm ABECQLBM with lattice {self.lattice}]" From b01b66999aaa9c2fe235e94add231756d71f4780 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:37:28 +0100 Subject: [PATCH 13/61] Fix gird measurement primitive str rerpesentation --- qlbm/components/collisionless/primitives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qlbm/components/collisionless/primitives.py b/qlbm/components/collisionless/primitives.py index d9147ee..77c1400 100644 --- a/qlbm/components/collisionless/primitives.py +++ b/qlbm/components/collisionless/primitives.py @@ -94,7 +94,7 @@ def create_circuit(self) -> QuantumCircuit: @override def __str__(self) -> str: - return f"[Primitive InitialConditions with lattice {self.lattice}]" + return f"[Primitive DVGridMeasurement with lattice {self.lattice}]" class CollisionlessInitialConditions(LBMPrimitive): From 37aee8179e0aec018dfa734255ba685c63ac28e8 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:37:54 +0100 Subject: [PATCH 14/61] Extract truncated QFT primitive --- qlbm/components/common/primitives.py | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/qlbm/components/common/primitives.py b/qlbm/components/common/primitives.py index 9c13715..3140f74 100644 --- a/qlbm/components/common/primitives.py +++ b/qlbm/components/common/primitives.py @@ -8,6 +8,7 @@ from numpy import pi from qiskit import QuantumCircuit from qiskit.circuit.library import MCMTGate, XGate +from qiskit.quantum_info import Operator from qiskit.synthesis import synth_qft_full as QFT from typing_extensions import override @@ -179,3 +180,51 @@ def create_circuit(self) -> QuantumCircuit: @override def __str__(self): return f"[Primitive HWAdder with with register size {self.x_register_size} and {self.y_register_size}]" + + +class TruncatedQFT(LBMPrimitive): + """TODO.""" + + def __init__( + self, + num_qubits: int, + dft_size: int, + logger: Logger = getLogger("qlbm"), + ): + super().__init__(logger) + self.num_qubits = num_qubits + self.dft_size = dft_size + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self): + circuit = QuantumCircuit(self.num_qubits) + + QFT = np.array( + [ + [ + np.exp(2j * np.pi * i * j / self.dft_size) / np.sqrt(self.dft_size) + for j in range(self.dft_size) + ] + for i in range(self.dft_size) + ] + ) + + U = np.eye(2**self.num_qubits, dtype=complex) + U[: self.dft_size, : self.dft_size] = QFT + op = Operator(U) + assert op.is_unitary() + + circuit.append(op, list(range(self.num_qubits))) + + return circuit + + @override + def __str__(self): + return f"[Primitive TuncatedQFT({self.num_qubits}, {self.dft_size})]" From 58b2d4c3becd7318eeffa5ec6969ab3d3d276797 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:38:31 +0100 Subject: [PATCH 15/61] Refactor CBSE redistribution to use truncarted QFT --- .../common/cbse_collision/cbse_redistribution.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/qlbm/components/common/cbse_collision/cbse_redistribution.py b/qlbm/components/common/cbse_collision/cbse_redistribution.py index 25d66fa..a998878 100644 --- a/qlbm/components/common/cbse_collision/cbse_redistribution.py +++ b/qlbm/components/common/cbse_collision/cbse_redistribution.py @@ -9,6 +9,7 @@ from qiskit.quantum_info import Operator from qlbm.components.base import LBMPrimitive +from qlbm.components.common.primitives import TruncatedQFT from qlbm.lattice.eqc.eqc import EquivalenceClass from qlbm.lattice.spacetime.properties_base import LatticeDiscretizationProperties from qlbm.tools.utils import is_two_pow @@ -108,19 +109,7 @@ def create_circuit(self): if is_two_pow(n): redistribution_circuit.ry(np.pi / 2, list(range(nq)), label="RY(π/2)") else: - QFT = np.array( - [ - [np.exp(2j * np.pi * i * j / n) / np.sqrt(n) for j in range(n)] - for i in range(n) - ] - ) - - U = np.eye(2**nq, dtype=complex) - U[:n, :n] = QFT - op = Operator(U) - assert op.is_unitary() - - redistribution_circuit.append(op, list(range(nq))) + redistribution_circuit = TruncatedQFT(nq, n).circuit circuit.compose( redistribution_circuit.control( From 6db6fe416e5b56145502e434e13c9ba4e0aa6b8a Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:38:58 +0100 Subject: [PATCH 16/61] Add ABE components module --- qlbm/components/abe/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 qlbm/components/abe/__init__.py diff --git a/qlbm/components/abe/__init__.py b/qlbm/components/abe/__init__.py new file mode 100644 index 0000000..3f9d50e --- /dev/null +++ b/qlbm/components/abe/__init__.py @@ -0,0 +1,11 @@ +from .abe import ABECQLBM +from .initial import ABEInitialConditions +from .measurement import ABEGridMeasurement +from .streaming import ABEStreamingOperator + +__all__ = [ + "ABECQLBM", + "ABEInitialConditions", + "ABEGridMeasurement", + "ABEStreamingOperator", +] From 4984ac5d6a41b938c230a500848f114585bddf13 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:42:15 +0100 Subject: [PATCH 17/61] Add average occupancy LQLGA initial conditions --- qlbm/components/lqlga/initial.py | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/qlbm/components/lqlga/initial.py b/qlbm/components/lqlga/initial.py index c73f5c0..588c8a9 100644 --- a/qlbm/components/lqlga/initial.py +++ b/qlbm/components/lqlga/initial.py @@ -8,6 +8,7 @@ from qlbm.components.base import LBMPrimitive from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice +from qlbm.tools.utils import flatten class LQGLAInitialConditions(LBMPrimitive): @@ -90,3 +91,47 @@ def create_circuit(self): @override def __str__(self): return f"[Primitive LQGLAInitialConditions on lattice={self.lattice}, grid_data={self.grid_data})]" + + +class LQGLAAveragedInitialConditions(LBMPrimitive): + """TODO.""" + + gridpoints: List[int] + """TODO.""" + + def __init__( + self, + lattice: LQLGALattice, + gridpoints: List[int], + logger: Logger = getLogger("qlbm"), + ): + super().__init__(logger) + + self.lattice = lattice + self.gridpoints = gridpoints + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self): + circuit = self.lattice.circuit.copy() + + circuit.h( + flatten( + [ + list(range(gp, gp + self.lattice.num_velocities_per_point)) + for gp in self.gridpoints + ] + ) + ) + + return circuit + + @override + def __str__(self): + return f"[Primitive LQGLAAveragedInitialConditions on lattice={self.lattice}, gps={self.gridpoints})]" From 877e5887eccab9722ea21b81e7b3a94f5c3d716d Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:43:03 +0100 Subject: [PATCH 18/61] Update CollisionlessResult to work with ABE Lattices --- qlbm/infra/result/collisionless_result.py | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/qlbm/infra/result/collisionless_result.py b/qlbm/infra/result/collisionless_result.py index ea9b88c..a27769a 100644 --- a/qlbm/infra/result/collisionless_result.py +++ b/qlbm/infra/result/collisionless_result.py @@ -10,6 +10,7 @@ from vtkmodules.util import numpy_support from qlbm.lattice import CollisionlessLattice +from qlbm.lattice.lattices.abe_lattice import ABELattice from .base import QBMResult @@ -38,12 +39,12 @@ class CollisionlessResult(QBMResult): output_file_name: str """The name of the file to output the artifacts to.""" - lattice: CollisionlessLattice + lattice: CollisionlessLattice | ABELattice """The lattice the result corresponds to.""" def __init__( self, - lattice: CollisionlessLattice, + lattice: CollisionlessLattice | ABELattice, directory: str, output_file_name: str = "step", ) -> None: @@ -70,7 +71,23 @@ def save_timestep_counts( else 0, ) - if self.lattice.num_dims == 2: + if self.lattice.num_dims == 1: + # The second dimension is a dirty rendering trick for VTK and Paraview + count_history = np.zeros((self.lattice.num_gridpoints[0] + 1, 2)) + for count in counts: + x = int( + count[self.lattice.num_velocity_qubits :], + 2, + ) + # Another dirty rendering trick for VTK and Paraview + count_history[x][0] += counts[count] * ( + 1 + (int(count[: self.lattice.num_velocity_qubits] == "00")) + ) + count_history[x][1] += counts[count] * ( + 1 + (int(count[: self.lattice.num_velocity_qubits] == "00")) + ) + + elif self.lattice.num_dims == 2: count_history = np.zeros( (self.lattice.num_gridpoints[0] + 1, self.lattice.num_gridpoints[1] + 1) ) @@ -105,7 +122,10 @@ def save_timestep_counts( count_history[x][y][z] = counts[count] self.save_timestep_array( - count_history, timestep, create_vis=create_vis, save_counts_array=save_array + count_history if self.lattice.num_dims > 1 else np.transpose(count_history), + timestep, + create_vis=create_vis, + save_counts_array=save_array, ) @override From 4d615a8cd2113b80a0a39c1e86eb45284ddedbd8 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 29 Oct 2025 10:43:58 +0100 Subject: [PATCH 19/61] Update runner to supporte ABELattice --- qlbm/infra/runner/base.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/qlbm/infra/runner/base.py b/qlbm/infra/runner/base.py index 0de08ca..2d2cb31 100644 --- a/qlbm/infra/runner/base.py +++ b/qlbm/infra/runner/base.py @@ -21,6 +21,7 @@ SpaceTimeResult, ) from qlbm.lattice import CollisionlessLattice, Lattice +from qlbm.lattice.lattices.abe_lattice import ABELattice from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice from qlbm.tools.exceptions import CircuitException, ResultsException @@ -123,9 +124,11 @@ def new_result(self, output_directory: str, output_file_name: str) -> QBMResult: ResultsException If there is no matching result object for the runner's lattice. """ - if isinstance(self.lattice, CollisionlessLattice): + if isinstance(self.lattice, CollisionlessLattice) or isinstance( + self.lattice, ABELattice + ): return CollisionlessResult( - cast(CollisionlessLattice, self.lattice), + self.lattice, # type: ignore output_directory, output_file_name, ) @@ -154,8 +157,10 @@ def new_reinitializer(self) -> Reinitializer: ResultsException If the underlying algorithm does not support reinitialization. """ - if isinstance(self.lattice, CollisionlessLattice) or isinstance( - self.lattice, LQLGALattice + if ( + isinstance(self.lattice, CollisionlessLattice) + or isinstance(self.lattice, LQLGALattice) + or isinstance(self.lattice, ABELattice) ): return IdentityReinitializer( self.lattice, From 317ddc096c65cefb33c6a1c2a0cb1da879be16cf Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:12:29 +0100 Subject: [PATCH 20/61] Add option to save statevector to all results --- qlbm/infra/result/base.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qlbm/infra/result/base.py b/qlbm/infra/result/base.py index 8987f5a..8b9c647 100644 --- a/qlbm/infra/result/base.py +++ b/qlbm/infra/result/base.py @@ -2,10 +2,12 @@ from abc import ABC, abstractmethod from os.path import isdir +from pathlib import Path from typing import Dict import numpy as np import vtk +from qiskit.quantum_info import Statevector from vtkmodules.util import numpy_support from qlbm.lattice import Lattice @@ -167,3 +169,12 @@ def save_timestep_counts( def visualize_all_numpy_data(self): """Converts all numpy data saved to disk to ``vti`` files.""" pass + + def save_statevector(self, statevector: Statevector, step: int): + statevector_dir = f"{self.directory}/statevectors" + if not isdir(statevector_dir): + create_directory_and_parents(statevector_dir) + state = np.asarray(statevector, dtype=np.complex128) # shape (2**n,) + + out_path = Path(f"{statevector_dir}/step_{step}.npy") + np.save(out_path, state) From b31ac7451bfc24addaa63136910eab0debcde2c0 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:16:34 +0100 Subject: [PATCH 21/61] Add geometry data for DdQq discretizations in AB lattices --- qlbm/lattice/geometry/shapes/block.py | 97 ++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/qlbm/lattice/geometry/shapes/block.py b/qlbm/lattice/geometry/shapes/block.py index 2fcece0..c8c67ea 100644 --- a/qlbm/lattice/geometry/shapes/block.py +++ b/qlbm/lattice/geometry/shapes/block.py @@ -19,7 +19,11 @@ SpaceTimeVolumetricReflectionData, ) from qlbm.lattice.geometry.shapes.base import LQLGAShape, SpaceTimeShape -from qlbm.lattice.spacetime.properties_base import SpaceTimeLatticeBuilder +from qlbm.lattice.spacetime.properties_base import ( + LatticeDiscretization, + SpaceTimeLatticeBuilder, +) +from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import bit_value, dimension_letter, flatten, get_qubits_to_invert @@ -97,6 +101,47 @@ class Block(SpaceTimeShape, LQLGAShape): ), ] + ab_wall_indices_to_reset: Dict[ + LatticeDiscretization, Dict[Tuple[int, bool], List[int]] + ] = { + LatticeDiscretization.D2Q9: { + (0, False): [3, 6, 7], + (0, True): [1, 5, 8], + (1, False): [4, 7, 8], + (1, True): [2, 5, 6], + } + } + + ab_near_corner_indices_to_reset: Dict[ + LatticeDiscretization, Dict[int, Dict[Tuple[bool, ...], List[int]]] + ] = { + LatticeDiscretization.D2Q9: { + 0: { + (False, False): [6], + (False, True): [7], + (True, False): [5], + (True, True): [8], + }, + 1: { + (False, False): [8], + (False, True): [5], + (True, False): [7], + (True, True): [6], + }, + } + } + + ab_corner_indices_to_reset: Dict[ + LatticeDiscretization, Dict[Tuple[bool, ...], List[int]] + ] = { + LatticeDiscretization.D2Q9: { + (False, False): [7], + (False, True): [6], + (True, False): [8], + (True, True): [5], + } + } + def __init__( self, bounds: List[Tuple[int, int]], @@ -712,6 +757,56 @@ def get_d2q4_surfaces(self) -> List[List[List[Tuple[int, ...]]]]: return surfaces + def get_lbm_wall_velocity_indices_to_reflect( + self, discretization: LatticeDiscretization, dim: int, bound: bool + ) -> List[int]: + if discretization not in self.ab_wall_indices_to_reset: + raise LatticeException( + f"Discretization {discretization} not supported. Supported discretizations are: {self.ab_wall_indices_to_reset.keys()}" + ) + + if (dim, bound) not in self.ab_wall_indices_to_reset[discretization]: + raise LatticeException( + f"{(dim, bound)} is not a valid description of a wall for {discretization}." + ) + + return self.ab_wall_indices_to_reset[discretization][(dim, bound)] + + def get_lbm_near_corner_velocity_indices_to_reflect( + self, discretization: LatticeDiscretization, dim: int, bounds: Tuple[bool, ...] + ) -> List[int]: + if discretization not in self.ab_near_corner_indices_to_reset: + raise LatticeException( + f"Discretization {discretization} not supported. Supported discretizations are: {self.ab_wall_indices_to_reset.keys()}" + ) + + if dim not in self.ab_near_corner_indices_to_reset[discretization]: + raise LatticeException( + f"{dim} is not a valid dimension for {discretization}." + ) + + if bounds not in self.ab_near_corner_indices_to_reset[discretization][dim]: + raise LatticeException( + f"{bounds} is not a valid description of a near corner gridpoint of {discretization}." + ) + + return self.ab_near_corner_indices_to_reset[discretization][dim][bounds] + + def get_lbm_outside_corner_indices_to_reflect( + self, discretization: LatticeDiscretization, bounds: Tuple[bool, ...] + ): + if discretization not in self.ab_corner_indices_to_reset: + raise LatticeException( + f"Discretization {discretization} not supported. Supported discretizations are: {self.ab_wall_indices_to_reset.keys()}" + ) + + if bounds not in self.ab_corner_indices_to_reset[discretization]: + raise LatticeException( + f"{bounds} is not a valid description of a wall for {discretization}." + ) + + return self.ab_corner_indices_to_reset[discretization][bounds] + @override def get_lqlga_reflection_data_d1q2(self): print(self.num_grid_qubits[0]) From 24b19a4f5c7b6290abb90e2bda36a36e47c608af Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:17:11 +0100 Subject: [PATCH 22/61] Add D2Q9 discretization --- qlbm/lattice/spacetime/properties_base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/qlbm/lattice/spacetime/properties_base.py b/qlbm/lattice/spacetime/properties_base.py index 6ec50c1..e3e2b42 100644 --- a/qlbm/lattice/spacetime/properties_base.py +++ b/qlbm/lattice/spacetime/properties_base.py @@ -28,6 +28,7 @@ class LatticeDiscretization(Enum): D2Q4 = (2,) D3Q6 = (3,) D1Q3 = (4,) + D2Q9 = (5,) class LatticeDiscretizationProperties: @@ -53,6 +54,19 @@ class LatticeDiscretizationProperties: [[1, 0, 0], [0, 1, 0], [0, 0, 1], [-1, 0, 0], [0, -1, 0], [0, 0, -1]] ), LatticeDiscretization.D1Q3: np.array([[0], [1], [-1]]), + LatticeDiscretization.D2Q9: np.array( + [ + [0, 0], + [1, 0], + [0, 1], + [-1, 0], + [0, -1], + [1, 1], + [-1, 1], + [-1, -1], + [1, -1], + ] + ), } channel_masses: Dict[LatticeDiscretization, np.ndarray] = { @@ -60,6 +74,7 @@ class LatticeDiscretizationProperties: LatticeDiscretization.D2Q4: np.ones(4), LatticeDiscretization.D3Q6: np.ones(6), LatticeDiscretization.D1Q3: np.concatenate((np.array([2]), np.ones(2))), + LatticeDiscretization.D2Q9: np.concatenate((np.array([2]), np.ones(8))), } num_velocities: Dict[LatticeDiscretization, int] = { @@ -67,6 +82,7 @@ class LatticeDiscretizationProperties: LatticeDiscretization.D2Q4: 4, LatticeDiscretization.D3Q6: 6, LatticeDiscretization.D1Q3: 3, + LatticeDiscretization.D2Q9: 9, } num_dimensions: Dict[LatticeDiscretization, int] = { @@ -74,6 +90,7 @@ class LatticeDiscretizationProperties: LatticeDiscretization.D2Q4: 2, LatticeDiscretization.D3Q6: 3, LatticeDiscretization.D1Q3: 1, + LatticeDiscretization.D2Q9: 2, } string_representation: Dict[LatticeDiscretization, str] = { @@ -81,6 +98,7 @@ class LatticeDiscretizationProperties: LatticeDiscretization.D2Q4: "D2Q4", LatticeDiscretization.D3Q6: "D3Q6", LatticeDiscretization.D1Q3: "D1Q3", + LatticeDiscretization.D2Q9: "D2Q9", } @staticmethod From c4080ae588a1807e3f5124c2071d7dd1829e0cf0 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:20:32 +0100 Subject: [PATCH 23/61] Fix bug in LQLGA averaged initial conditions --- qlbm/components/lqlga/initial.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/qlbm/components/lqlga/initial.py b/qlbm/components/lqlga/initial.py index 588c8a9..743c607 100644 --- a/qlbm/components/lqlga/initial.py +++ b/qlbm/components/lqlga/initial.py @@ -124,7 +124,13 @@ def create_circuit(self): circuit.h( flatten( [ - list(range(gp, gp + self.lattice.num_velocities_per_point)) + list( + range( + gp * self.lattice.num_velocities_per_point, + gp * self.lattice.num_velocities_per_point + + self.lattice.num_velocities_per_point, + ) + ) for gp in self.gridpoints ] ) From ad8c6162bab3118947eccff7cfa0687365ae5e39 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:21:27 +0100 Subject: [PATCH 24/61] Add optional parameter to measure velocity qubits in AB grid measurement --- qlbm/components/abe/measurement.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/qlbm/components/abe/measurement.py b/qlbm/components/abe/measurement.py index 69762dd..632375d 100644 --- a/qlbm/components/abe/measurement.py +++ b/qlbm/components/abe/measurement.py @@ -21,10 +21,12 @@ class ABEGridMeasurement(LBMPrimitive): def __init__( self, lattice: ABELattice, + measure_velocity_qubits: bool = False, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(logger) self.lattice = lattice + self.measure_velocity_qubits = measure_velocity_qubits self.logger.info(f"Creating circuit {str(self)}...") circuit_creation_start_time = perf_counter_ns() @@ -38,14 +40,27 @@ def create_circuit(self) -> QuantumCircuit: circuit = self.lattice.circuit.copy() circuit.add_register( ClassicalRegister( - self.lattice.num_grid_qubits + self.lattice.num_velocity_qubits + self.lattice.num_grid_qubits + + ( + self.lattice.num_velocity_qubits + if self.measure_velocity_qubits + else 0 + ) ) ) circuit.measure( - self.lattice.grid_index() + self.lattice.velocity_index(), + self.lattice.grid_index() + + (self.lattice.velocity_index() if self.measure_velocity_qubits else []), list( - range(self.lattice.num_grid_qubits + self.lattice.num_velocity_qubits) + range( + self.lattice.num_grid_qubits + + ( + self.lattice.num_velocity_qubits + if self.measure_velocity_qubits + else 0 + ) + ) ), ) From 31618aa68d567c3a870f5f4dcaa50f733de13532 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:22:19 +0100 Subject: [PATCH 25/61] Add d2q9 streaming for AB lattice --- qlbm/components/abe/streaming.py | 75 +++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/qlbm/components/abe/streaming.py b/qlbm/components/abe/streaming.py index 2148b18..d517334 100644 --- a/qlbm/components/abe/streaming.py +++ b/qlbm/components/abe/streaming.py @@ -16,6 +16,7 @@ from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools import CircuitException, bit_value from qlbm.tools.exceptions import LatticeException +from qlbm.tools.utils import get_qubits_to_invert class ABEStreamingOperator(LBMOperator): @@ -26,10 +27,13 @@ class ABEStreamingOperator(LBMOperator): def __init__( self, lattice: ABELattice, + additional_control_qubit_indices: List[int] = [], logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(lattice, logger) + self.additional_control_qubit_indices = additional_control_qubit_indices + self.logger.info(f"Creating circuit {str(self)}...") circuit_creation_start_time = perf_counter_ns() self.circuit = self.create_circuit() @@ -42,9 +46,14 @@ def create_circuit(self) -> QuantumCircuit: if self.lattice.discretization == LatticeDiscretization.D1Q3: return self.__create_circuit_d1q3() - raise LatticeException("ABE only currently supported in D1Q3") + if self.lattice.discretization == LatticeDiscretization.D2Q9: + return self.__create_circuit_d2q9() + + raise LatticeException("ABE only currently supported in D1Q3 and D2Q9") def __create_circuit_d1q3(self): + # TODO Remove? + # TODO add geometry circuit = self.lattice.circuit.copy() circuit.compose( @@ -95,7 +104,69 @@ def __create_circuit_d1q3(self): ) return circuit - + + def __create_circuit_d2q9(self): + circuit = self.lattice.circuit.copy() + + dim_indices = [ + [ + [1, 5, 8], # f1, f5, f8 x <- x + 1 + [3, 6, 7], # f3, f6, f7 x <- x - 1 + ], + [ + [2, 5, 6], # f2, f5, f6 y <- y + 1 + [4, 7, 8], # f4, f7, f8 y <- y + 1 + ], + ] + + for dim, dim_population_to_update in enumerate(dim_indices): + circuit.compose( + QFT(len(self.lattice.grid_index(dim))), + qubits=self.lattice.grid_index(dim), + inplace=True, + ) + + for direction, indices in enumerate(dim_population_to_update): + positive = bool(1 - direction) + + for index in indices: + velocity_inversion_qubits = [ + self.lattice.num_grid_qubits + q + for q in get_qubits_to_invert( + index, self.lattice.num_velocity_qubits + ) + ] + if velocity_inversion_qubits: + circuit.x(velocity_inversion_qubits) + + circuit.compose( + PhaseShift( + num_qubits=len(self.lattice.grid_index(dim)), + positive=positive, + logger=self.logger, + ) + .circuit.control( + self.lattice.num_velocity_qubits + + len(self.additional_control_qubit_indices) + ) + .decompose(), + qubits=self.additional_control_qubit_indices + + self.lattice.velocity_index() + + self.lattice.grid_index(dim), + inplace=True, + ) + + if velocity_inversion_qubits: + circuit.x(velocity_inversion_qubits) + + circuit.compose( + QFT(len(self.lattice.grid_index(dim)), inverse=True), + qubits=self.lattice.grid_index(dim), + inplace=True, + ) + + return circuit + @override def __str__(self) -> str: return f"[Operator ABEStreaming with lattice {self.lattice}]" From 700b30979aa141baaddbd193aca921df793eeee6 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:24:12 +0100 Subject: [PATCH 26/61] Add d2q9 reflection for AB lattice --- qlbm/components/abe/reflection.py | 372 ++++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 qlbm/components/abe/reflection.py diff --git a/qlbm/components/abe/reflection.py b/qlbm/components/abe/reflection.py new file mode 100644 index 0000000..d8fa149 --- /dev/null +++ b/qlbm/components/abe/reflection.py @@ -0,0 +1,372 @@ +from itertools import product +from logging import Logger, getLogger +from math import pi +from time import perf_counter_ns +from typing import List, Tuple + +import numpy as np +from qiskit import QuantumCircuit +from qiskit.circuit.library import MCMTGate, MCXGate, XGate +from qiskit.synthesis import synth_qft_full as QFT +from typing_extensions import override + +from qlbm.components.abe.streaming import ABEStreamingOperator +from qlbm.components.base import CQLBMOperator, LBMOperator, LBMPrimitive +from qlbm.components.collisionless.specular_reflection import SpecularWallComparator +from qlbm.components.collisionless.streaming import PhaseShift +from qlbm.lattice import CollisionlessLattice +from qlbm.lattice.geometry.encodings.collisionless import ReflectionPoint +from qlbm.lattice.geometry.shapes.block import Block +from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.spacetime.properties_base import LatticeDiscretization +from qlbm.tools import CircuitException, bit_value +from qlbm.tools.exceptions import LatticeException +from qlbm.tools.utils import flatten, get_qubits_to_invert + + +class ABEReflectionOperator(LBMOperator): + """TODO.""" + + lattice: ABELattice + + def __init__( + self, + lattice: ABELattice, + blocks: List[Block], + logger: Logger = getLogger("qlbm"), + ) -> None: + super().__init__(lattice, logger) + + self.blocks = blocks + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self) -> QuantumCircuit: + if self.lattice.discretization == LatticeDiscretization.D2Q9: + return self.__create_circuit_d2q9() + + raise LatticeException("ABE reflection only currently supported in D2Q9") + + def __create_circuit_d2q9(self): + circuit = self.lattice.circuit.copy() + + # Mark populations inside the object + for block in self.blocks: + circuit.compose(self.set_wall_ancilla_state(block), inplace=True) + + circuit.compose( + self.reset_ancilla_of_point_state( + flatten( + [[(p, None) for p in block.corners_inside] for block in self.blocks] + ), + ignore_velocity_data=True, + ), + inplace=True, + ) + + # Stream the particles back + circuit.compose(self.permute_and_stream(), inplace=True) + + # Reset the ancilla state of reflected populations + for block in self.blocks: + circuit.compose(self.reset_outside_wall_ancilla_state(block), inplace=True) + + # Re-reset near corner point ancillas + point_data: List[Tuple[ReflectionPoint, List[int]]] = [] + + for block in self.blocks: + for dim in range(self.lattice.num_dims): + for c, bounds in enumerate( + product(*[[False, True]] * self.lattice.num_dims) + ): + point_data.append( + ( + block.near_corner_points_2d[dim * 4 + c], + block.get_lbm_near_corner_velocity_indices_to_reflect( + self.lattice.discretization, dim, bounds + ), + ) + ) + for c, bounds in enumerate( + product(*[[False, True]] * self.lattice.num_dims) + ): + point_data.append( + ( + block.corners_outside[c], + block.get_lbm_outside_corner_indices_to_reflect( + self.lattice.discretization, bounds + ), + ) + ) + # Re-reset the ancilla state of the populations that + # Shouldn't have been flipped in the previous step + circuit.compose( + self.reset_ancilla_of_point_state(point_data, ignore_velocity_data=False), + inplace=True, + ) + + return circuit + + def set_wall_ancilla_state(self, block: Block): + circuit = self.lattice.circuit.copy() + + for dim in range(self.lattice.num_dims): + for wall in block.walls_inside[dim]: + comparator_circuit = SpecularWallComparator( + self.lattice, wall, self.logger + ).circuit + + grid_qubit_indices_to_invert = [ + self.lattice.grid_index(0)[0] + qubit + for qubit in wall.data.qubits_to_invert + ] + + circuit.compose(comparator_circuit, inplace=True) + + if grid_qubit_indices_to_invert: + circuit.x(grid_qubit_indices_to_invert) + + control_qubits = ( + self.lattice.grid_index(wall.dim) + + self.lattice.ancillae_comparator_index() + ) + + target_qubits = self.lattice.ancillae_obstacle_index(0) + + circuit.compose( + MCMTGate( + XGate(), + len(control_qubits), + len(target_qubits), + ), + qubits=control_qubits + target_qubits, + inplace=True, + ) + + if grid_qubit_indices_to_invert: + circuit.x(grid_qubit_indices_to_invert) + + circuit.compose(comparator_circuit, inplace=True) + + return circuit + + def reset_outside_wall_ancilla_state(self, block: Block): + circuit = self.lattice.circuit.copy() + + for dim in range(self.lattice.num_dims): + for bound, wall in enumerate(block.walls_outside[dim]): + comparator_circuit = SpecularWallComparator( + self.lattice, wall, self.logger + ).circuit + + grid_qubit_indices_to_invert = [ + self.lattice.grid_index(0)[0] + qubit + for qubit in wall.data.qubits_to_invert + ] + + circuit.compose(comparator_circuit, inplace=True) + + if grid_qubit_indices_to_invert: + circuit.x(grid_qubit_indices_to_invert) + + # Reset the state for each velocity we care about: + for v in block.get_lbm_wall_velocity_indices_to_reflect( + self.lattice.discretization, dim, bool(bound) + ): + qs = [ + self.lattice.velocity_index()[0] + q + for q in get_qubits_to_invert( + v, self.lattice.num_velocity_qubits + ) + ] + + if qs: + circuit.x(qs) + + control_qubits = ( + self.lattice.grid_index(wall.dim) + + self.lattice.ancillae_comparator_index() + + self.lattice.velocity_index() # The reset step is additionally controlled on the velocity register + ) + + target_qubits = self.lattice.ancillae_obstacle_index(0) + + circuit.compose( + MCMTGate( + XGate(), + len(control_qubits), + len(target_qubits), + ), + qubits=control_qubits + target_qubits, + inplace=True, + ) + + if qs: + circuit.x(qs) + + if grid_qubit_indices_to_invert: + circuit.x(grid_qubit_indices_to_invert) + + circuit.compose(comparator_circuit, inplace=True) + + return circuit + + def reset_ancilla_of_point_state( + self, + points_data: List[Tuple[ReflectionPoint, List[int]]], + ignore_velocity_data: bool, + ): + circuit = self.lattice.circuit.copy() + + for point, velocities in points_data: + grid_qubit_indices_to_invert = [ + self.lattice.grid_index(0)[0] + qubit + for qubit in point.qubits_to_invert + ] + velocity_data = ( + [ + [ + self.lattice.velocity_index()[0] + qubit + for qubit in get_qubits_to_invert( + velocity_index, self.lattice.num_velocity_qubits + ) + ] + for velocity_index in velocities + ] + if not ignore_velocity_data + else [[]] + ) + if grid_qubit_indices_to_invert: + circuit.x(grid_qubit_indices_to_invert) + + # Reset the state for each velocity we care about: + for velocity_qubit_indices_to_invert in velocity_data: + if velocity_qubit_indices_to_invert: + circuit.x(velocity_qubit_indices_to_invert) + + control_qubits = ( + self.lattice.grid_index() + + ( + self.lattice.velocity_index() + if not ignore_velocity_data + else [] + ) # The reset step is additionally controlled on the velocity register + ) + + target_qubits = self.lattice.ancillae_obstacle_index(0) + + circuit.compose( + MCMTGate( + XGate(), + len(control_qubits), + len(target_qubits), + ), + qubits=control_qubits + target_qubits, + inplace=True, + ) + + if velocity_qubit_indices_to_invert: + circuit.x(velocity_qubit_indices_to_invert) + + if grid_qubit_indices_to_invert: + circuit.x(grid_qubit_indices_to_invert) + + return circuit + + def permute_and_stream(self) -> QuantumCircuit: + circuit = self.lattice.circuit.copy() + + # Permute the velocities according to reflection rules + circuit.compose( + ABEReflectionPermutation( + self.lattice.num_velocity_qubits, + self.lattice.discretization, + self.logger, + ) + .circuit.control(1) + .decompose(), + qubits=self.lattice.ancillae_obstacle_index() + + self.lattice.velocity_index(), + inplace=True, + ) + + circuit.compose( + ABEStreamingOperator( + self.lattice, self.lattice.ancillae_obstacle_index(), self.logger + ).circuit, + inplace=True, + ) + + return circuit + + @override + def __str__(self) -> str: + return f"[Operator ABEStreaming with lattice {self.lattice}]" + + +class ABEReflectionPermutation(LBMPrimitive): + def __init__( + self, + num_qubits: int, + discretization: LatticeDiscretization, + logger: Logger = getLogger("qlbm"), + ) -> None: + super().__init__(logger) + + self.num_qubits = num_qubits + self.discretization = discretization + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self) -> QuantumCircuit: + if self.discretization == LatticeDiscretization.D2Q9: + return self.__create_circuit_d2q9() + + raise LatticeException("ABE reflection only currently supported in D2Q9") + + def __create_circuit_d2q9(self): + circuit = QuantumCircuit(self.num_qubits) + + # 1 <-> 3 + circuit.x([0, 1]) + circuit.mcx([0, 1, 3], 2) + circuit.x([0, 1]) + + # 2 <-> 4 + circuit.x([0, 3]) + circuit.cx(1, 2) + circuit.mcx([0, 2, 3], 1) + circuit.cx(1, 2) + circuit.x([0, 3]) + + # 5 <-> 7 + circuit.x(0) + circuit.mcx([0, 1, 3], 2) + circuit.x(0) + + # 6 <-> 8 + circuit.cx(0, 1) + circuit.cx(0, 2) + circuit.x(3) + circuit.mcx([1, 2, 3], 0) + circuit.cx(0, 2) + circuit.cx(0, 1) + circuit.x(3) + + return circuit.reverse_bits() + + @override + def __str__(self) -> str: + return f"[Primitive ABEReflectionPermutation with {self.num_qubits} qubits on {self.discretization}]" From 739b01a346ddba0d6664f782d8864b60dc33d9e6 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:24:43 +0100 Subject: [PATCH 27/61] Add d2q9 reflection to AB loop --- qlbm/components/abe/abe.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/qlbm/components/abe/abe.py b/qlbm/components/abe/abe.py index 505c48e..827da5f 100644 --- a/qlbm/components/abe/abe.py +++ b/qlbm/components/abe/abe.py @@ -6,13 +6,13 @@ from qiskit import QuantumCircuit from typing_extensions import override -from qlbm.components.abe.averaged_collision import ABEAveragedCollisionOperator +from qlbm.components.abe.reflection import ABEReflectionOperator from qlbm.components.base import LBMAlgorithm from qlbm.lattice import CollisionlessLattice from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.lattices.abe_lattice import ABELattice from qlbm.tools.exceptions import LatticeException -from qlbm.tools.utils import get_time_series +from qlbm.tools.utils import flatten, get_time_series from .streaming import ABEStreamingOperator @@ -49,13 +49,24 @@ def create_circuit(self): inplace=True, ) - # circuit.compose( - # ABEAveragedCollisionOperator( - # self.lattice, - # logger=self.logger, - # ).circuit, - # inplace=True, - # ) + for bc in ["bounceback", "specular"]: + if self.lattice.shapes[bc]: + if not all( + isinstance(shape, Block) + for shape in self.lattice.shapes["specular"] + ): + raise LatticeException( + f"All shapes with the {bc} boundary condition must be cuboids for the CQLBM algorithm. " + ) + + circuit.compose( + ABEReflectionOperator( + self.lattice, + flatten(list(self.lattice.shapes.values())), # type: ignore + logger=self.logger, + ).circuit, + inplace=True, + ) return circuit From 7fa5d72588174469c6299fca1e7bb0174dfdf0de Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:25:09 +0100 Subject: [PATCH 28/61] Fix bug in CQLBM geometry checking --- qlbm/components/collisionless/cqlbm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qlbm/components/collisionless/cqlbm.py b/qlbm/components/collisionless/cqlbm.py index b9b54b5..81fb1cd 100644 --- a/qlbm/components/collisionless/cqlbm.py +++ b/qlbm/components/collisionless/cqlbm.py @@ -87,14 +87,14 @@ def create_circuit(self): inplace=True, ) - if self.lattice.shapes["bounceback"]: - if self.lattice.shapes["specular"]: + for bc in ["bounceback", "specular"]: + if self.lattice.shapes[bc]: if not all( isinstance(shape, Block) for shape in self.lattice.shapes["specular"] ): raise LatticeException( - "All shapes with the 'bounceback' boundary condition must be of type Block for the CQLBM algorithm. " + f"All shapes with the {bc} boundary condition must be cuboids for the CQLBM algorithm. " ) circuit.compose( BounceBackReflectionOperator( From 87fc057d432fd9318585137be80b5e5e86ac2157 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:25:37 +0100 Subject: [PATCH 29/61] Add the option to save the statevector in qlbm runner --- qlbm/infra/runner/qiskit_runner.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qlbm/infra/runner/qiskit_runner.py b/qlbm/infra/runner/qiskit_runner.py index ec12b8b..eb38d10 100644 --- a/qlbm/infra/runner/qiskit_runner.py +++ b/qlbm/infra/runner/qiskit_runner.py @@ -3,6 +3,7 @@ from logging import Logger, getLogger from time import perf_counter_ns +import numpy as np from qiskit import QuantumCircuit as QiskitQC from qiskit import transpile from qiskit.circuit.library import Initialize @@ -49,11 +50,13 @@ def __init__( lattice: Lattice, logger: Logger = getLogger("qlbm"), device: str = "CPU", # ! TODO reimplement + save_statevector_to_disk: bool = False, ) -> None: super().__init__(config, lattice, logger, device) self.execution_backend = self.config.execution_backend self.sampling_backend = self.config.sampling_backend + self.statevector_to_disk = save_statevector_to_disk @override def run( @@ -183,6 +186,11 @@ def _run_snapshot_time_loop( qiskit_execution_result.get_counts(), step ) + if self.statevector_to_disk: + simulation_result.save_statevector( + qiskit_execution_result.data(0)["step"], step=step + ) + # Update the initial conditions for the next step initial_conditions = self.reinitializer.reinitialize( statevector=qiskit_execution_result.data(0)["step"] From 89f714f0364ecdfb562336fcc352afe3ecba377f Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 15:30:34 +0100 Subject: [PATCH 30/61] Fix imports --- qlbm/components/abe/abe.py | 3 +-- qlbm/components/abe/initial.py | 9 +-------- qlbm/components/abe/measurement.py | 7 ------- qlbm/components/abe/reflection.py | 10 ++-------- qlbm/components/abe/streaming.py | 7 +------ .../common/cbse_collision/cbse_redistribution.py | 1 - qlbm/infra/runner/qiskit_runner.py | 1 - 7 files changed, 5 insertions(+), 33 deletions(-) diff --git a/qlbm/components/abe/abe.py b/qlbm/components/abe/abe.py index 827da5f..6f7c3e3 100644 --- a/qlbm/components/abe/abe.py +++ b/qlbm/components/abe/abe.py @@ -8,11 +8,10 @@ from qlbm.components.abe.reflection import ABEReflectionOperator from qlbm.components.base import LBMAlgorithm -from qlbm.lattice import CollisionlessLattice from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.lattices.abe_lattice import ABELattice from qlbm.tools.exceptions import LatticeException -from qlbm.tools.utils import flatten, get_time_series +from qlbm.tools.utils import flatten from .streaming import ABEStreamingOperator diff --git a/qlbm/components/abe/initial.py b/qlbm/components/abe/initial.py index 0505210..5a43c44 100644 --- a/qlbm/components/abe/initial.py +++ b/qlbm/components/abe/initial.py @@ -1,19 +1,12 @@ -from enum import Enum from logging import Logger, getLogger from time import perf_counter_ns -from typing import List -from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.synthesis import synth_qft_full as QFT +from qiskit import QuantumCircuit from typing_extensions import override from qlbm.components.base import LBMPrimitive -from qlbm.components.collisionless.streaming import SpeedSensitivePhaseShift from qlbm.components.common.primitives import TruncatedQFT -from qlbm.lattice import CollisionlessLattice -from qlbm.lattice.geometry.encodings.collisionless import ReflectionResetEdge from qlbm.lattice.lattices.abe_lattice import ABELattice -from qlbm.tools import flatten class ABEInitialConditions(LBMPrimitive): diff --git a/qlbm/components/abe/measurement.py b/qlbm/components/abe/measurement.py index 632375d..2c74c54 100644 --- a/qlbm/components/abe/measurement.py +++ b/qlbm/components/abe/measurement.py @@ -1,18 +1,11 @@ -from enum import Enum from logging import Logger, getLogger from time import perf_counter_ns -from typing import List from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.synthesis import synth_qft_full as QFT from typing_extensions import override from qlbm.components.base import LBMPrimitive -from qlbm.components.collisionless.streaming import SpeedSensitivePhaseShift -from qlbm.lattice import CollisionlessLattice -from qlbm.lattice.geometry.encodings.collisionless import ReflectionResetEdge from qlbm.lattice.lattices.abe_lattice import ABELattice -from qlbm.tools import flatten class ABEGridMeasurement(LBMPrimitive): diff --git a/qlbm/components/abe/reflection.py b/qlbm/components/abe/reflection.py index d8fa149..ecb8db1 100644 --- a/qlbm/components/abe/reflection.py +++ b/qlbm/components/abe/reflection.py @@ -1,25 +1,19 @@ from itertools import product from logging import Logger, getLogger -from math import pi from time import perf_counter_ns from typing import List, Tuple -import numpy as np from qiskit import QuantumCircuit -from qiskit.circuit.library import MCMTGate, MCXGate, XGate -from qiskit.synthesis import synth_qft_full as QFT +from qiskit.circuit.library import MCMTGate, XGate from typing_extensions import override from qlbm.components.abe.streaming import ABEStreamingOperator -from qlbm.components.base import CQLBMOperator, LBMOperator, LBMPrimitive +from qlbm.components.base import LBMOperator, LBMPrimitive from qlbm.components.collisionless.specular_reflection import SpecularWallComparator -from qlbm.components.collisionless.streaming import PhaseShift -from qlbm.lattice import CollisionlessLattice from qlbm.lattice.geometry.encodings.collisionless import ReflectionPoint from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.lattices.abe_lattice import ABELattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization -from qlbm.tools import CircuitException, bit_value from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import flatten, get_qubits_to_invert diff --git a/qlbm/components/abe/streaming.py b/qlbm/components/abe/streaming.py index d517334..bdc5526 100644 --- a/qlbm/components/abe/streaming.py +++ b/qlbm/components/abe/streaming.py @@ -1,20 +1,15 @@ from logging import Logger, getLogger -from math import pi from time import perf_counter_ns from typing import List -import numpy as np from qiskit import QuantumCircuit -from qiskit.circuit.library import MCXGate from qiskit.synthesis import synth_qft_full as QFT from typing_extensions import override -from qlbm.components.base import CQLBMOperator, LBMOperator, LBMPrimitive +from qlbm.components.base import LBMOperator from qlbm.components.collisionless.streaming import PhaseShift -from qlbm.lattice import CollisionlessLattice from qlbm.lattice.lattices.abe_lattice import ABELattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization -from qlbm.tools import CircuitException, bit_value from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import get_qubits_to_invert diff --git a/qlbm/components/common/cbse_collision/cbse_redistribution.py b/qlbm/components/common/cbse_collision/cbse_redistribution.py index a998878..6aeab84 100644 --- a/qlbm/components/common/cbse_collision/cbse_redistribution.py +++ b/qlbm/components/common/cbse_collision/cbse_redistribution.py @@ -6,7 +6,6 @@ import numpy as np from qiskit import QuantumCircuit -from qiskit.quantum_info import Operator from qlbm.components.base import LBMPrimitive from qlbm.components.common.primitives import TruncatedQFT diff --git a/qlbm/infra/runner/qiskit_runner.py b/qlbm/infra/runner/qiskit_runner.py index eb38d10..5064e65 100644 --- a/qlbm/infra/runner/qiskit_runner.py +++ b/qlbm/infra/runner/qiskit_runner.py @@ -3,7 +3,6 @@ from logging import Logger, getLogger from time import perf_counter_ns -import numpy as np from qiskit import QuantumCircuit as QiskitQC from qiskit import transpile from qiskit.circuit.library import Initialize From 659e1dd8bf02b0be8c5ade2f20f7c9c429ee37ec Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 19:09:26 +0100 Subject: [PATCH 31/61] Add base amplitude lattice --- qlbm/lattice/lattices/base.py | 104 ++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/qlbm/lattice/lattices/base.py b/qlbm/lattice/lattices/base.py index 1d91b8f..8b6581d 100644 --- a/qlbm/lattice/lattices/base.py +++ b/qlbm/lattice/lattices/base.py @@ -507,3 +507,107 @@ def has_multiple_geometries(self) -> bool: """ pass + +class AmplitudeLattice(Lattice, ABC): + def __init__( + self, + lattice_data, + logger=getLogger("qlbm"), + ): + super(AmplitudeLattice, self).__init__(lattice_data, logger) + + @abstractmethod + def grid_index(self, dim: int | None = None) -> List[int]: + """Get the indices of the qubits used that encode the grid values for the specified dimension. + + Parameters + ---------- + dim : int | None, optional + The dimension of the grid for which to retrieve the grid qubit indices, by default ``None``. + When ``dim`` is ``None``, the indices of all grid qubits for all dimensions are returned. + + Returns + ------- + List[int] + A list of indices of the qubits used to encode the grid values for the given dimension. + + Raises + ------ + LatticeException + If the dimension does not exist. + """ + pass + + @abstractmethod + def ancillae_comparator_index(self, index: int | None = None) -> List[int]: + """Get the indices of the qubits used as comparator ancillae for the specified index. + + Parameters + ---------- + index : int | None, optional + The index for which to retrieve the comparator qubit indices, by default ``None``. + There are `num_dims-1` available indices (i.e., 1 for 2D and 2 for 3D). + When `index` is ``None``, the indices of ancillae qubits for all dimensions are returned. + + Returns + ------- + List[int] + A list of indices of the qubits used as obstacle ancilla for the given dimension. + By convention, the 0th qubit in the returned list is used + for lower bound comparison and the 1st is used for upper bound comparisons. + + Raises + ------ + LatticeException + If the dimension does not exist. + """ + pass + + @abstractmethod + def ancillae_obstacle_index(self, index: int | None = None) -> List[int]: + """Get the indices of the qubits used as obstacle ancilla for the specified dimension. + + Parameters + ---------- + index : int | None, optional + The index of the grid for which to retrieve the obstacle qubit index, by default ``None``. + When ``index`` is ``None``, the indices of ancillae qubits for all dimensions are returned. + For 2D lattices with only bounce-back boundary-conditions, only one obstacle + qubit is required. + For all other configurations, the algorithm uses ``2d-2`` obstacle qubits. + + Returns + ------- + List[int] + A list of indices of the qubits used as obstacle ancilla for the given dimension. + + Raises + ------ + LatticeException + If the dimension does not exist. + """ + pass + + @abstractmethod + def velocity_index(self, dim: int | None = None) -> List[int]: + """Get the indices of the qubits used that encode the velocity magnitude values for the specified dimension. + + Parameters + ---------- + dim : int | None, optional + The dimension of the grid for which to retrieve the velocity qubit indices, by default ``None``. + When ``dim`` is ``None``, the indices of all velocity qubits for all dimensions are returned. + Note: ``dim`` should only only be supplied to the MSLattice. + Other amplitude lattices do not support a dimensional breakdown, and ``dim`` should not be passed as an argument. + + Returns + ------- + List[int] + A list of indices of the qubits used to encode the velocity magnitude values for the given dimension. + + Raises + ------ + LatticeException + If the dimension does not exist. + """ + pass \ No newline at end of file From bc19167af8857add96226c7c65c283b6eff698fa Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 19:10:30 +0100 Subject: [PATCH 32/61] Refactor ABELattice and CollisionlessLattice to share base AmpltiudeLattice --- .../geometry/encodings/collisionless.py | 2 +- qlbm/lattice/lattices/abe_lattice.py | 79 ++--------------- .../lattice/lattices/collisionless_lattice.py | 86 ++----------------- 3 files changed, 16 insertions(+), 151 deletions(-) diff --git a/qlbm/lattice/geometry/encodings/collisionless.py b/qlbm/lattice/geometry/encodings/collisionless.py index dcbe2e5..d232f6a 100644 --- a/qlbm/lattice/geometry/encodings/collisionless.py +++ b/qlbm/lattice/geometry/encodings/collisionless.py @@ -253,4 +253,4 @@ def __get_bounceback_wall_loose_bounds(self): if self.num_dims == 2: return [[True], [False]] else: - return [[True, True], [False, False], [False, True]] \ No newline at end of file + return [[True, True], [False, False], [False, True]] diff --git a/qlbm/lattice/lattices/abe_lattice.py b/qlbm/lattice/lattices/abe_lattice.py index 635b5c6..40aaf14 100644 --- a/qlbm/lattice/lattices/abe_lattice.py +++ b/qlbm/lattice/lattices/abe_lattice.py @@ -15,10 +15,10 @@ from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import dimension_letter, flatten, is_two_pow -from .base import Lattice +from .base import AmplitudeLattice -class ABELattice(Lattice): +class ABELattice(AmplitudeLattice): """TODO.""" discretization: LatticeDiscretization @@ -83,25 +83,8 @@ def __init__( self.registers = tuple(flatten(temporary_registers)) self.circuit = QuantumCircuit(*self.registers) + @override def grid_index(self, dim: int | None = None) -> List[int]: - """Get the indices of the qubits used that encode the grid values for the specified dimension. - - Parameters - ---------- - dim : int | None, optional - The dimension of the grid for which to retrieve the grid qubit indices, by default ``None``. - When ``dim`` is ``None``, the indices of all grid qubits for all dimensions are returned. - - Returns - ------- - List[int] - A list of indices of the qubits used to encode the grid values for the given dimension. - - Raises - ------ - LatticeException - If the dimension does not exist. - """ if dim is None: return list( range( @@ -122,14 +105,10 @@ def grid_index(self, dim: int | None = None) -> List[int]: ) ) - def velocity_index(self) -> List[int]: - """Get the indices of the qubits used that encode the discrete velocities. - - Returns - ------- - List[int] - A list of indices of the qubits that encode the velocity discretization. - """ + @override + def velocity_index(self, dim: int | None = None) -> List[int]: + if dim is not None: + raise LatticeException("ABELattice does not support a dimensional breakdown of velocities.") return list( range( self.num_grid_qubits, @@ -137,28 +116,8 @@ def velocity_index(self) -> List[int]: ) ) + @override def ancillae_comparator_index(self, index: int | None = None) -> List[int]: - """Get the indices of the qubits used as comparator ancillae for the specified index. - - Parameters - ---------- - index : int | None, optional - The index for which to retrieve the comparator qubit indices, by default ``None``. - There are `num_dims-1` available indices (i.e., 1 for 2D and 2 for 3D). - When `index` is ``None``, the indices of ancillae qubits for all dimensions are returned. - - Returns - ------- - List[int] - A list of indices of the qubits used as obstacle ancilla for the given dimension. - By convention, the 0th qubit in the returned list is used - for lower bound comparison and the 1st is used for upper bound comparisons. - - Raises - ------ - LatticeException - If the dimension does not exist. - """ if index is None: return list( range( @@ -178,28 +137,8 @@ def ancillae_comparator_index(self, index: int | None = None) -> List[int]: ) ) + @override def ancillae_obstacle_index(self, index: int | None = None) -> List[int]: - """Get the indices of the qubits used as obstacle ancilla for the specified dimension. - - Parameters - ---------- - index : int | None, optional - The index of the grid for which to retrieve the obstacle qubit index, by default ``None``. - When ``index`` is ``None``, the indices of ancillae qubits for all dimensions are returned. - For 2D lattices with only bounce-back boundary-conditions, only one obstacle - qubit is required. - For all other configurations, the algorithm uses ``2d-2`` obstacle qubits. - - Returns - ------- - List[int] - A list of indices of the qubits used as obstacle ancilla for the given dimension. - - Raises - ------ - LatticeException - If the dimension does not exist. - """ if index is None: return list( range( diff --git a/qlbm/lattice/lattices/collisionless_lattice.py b/qlbm/lattice/lattices/collisionless_lattice.py index efe3dcc..f338686 100644 --- a/qlbm/lattice/lattices/collisionless_lattice.py +++ b/qlbm/lattice/lattices/collisionless_lattice.py @@ -10,10 +10,10 @@ from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import dimension_letter, flatten, is_two_pow -from .base import Lattice +from .base import AmplitudeLattice -class CollisionlessLattice(Lattice): +class CollisionlessLattice(AmplitudeLattice): r""" Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.CQLBM` algorithm developed by :cite:t:`collisionless`. @@ -230,28 +230,8 @@ def ancillae_velocity_index(self, dim: int | None = None) -> List[int]: # The velocity ancillas are on the first register, so no offset return [dim] + @override def ancillae_obstacle_index(self, index: int | None = None) -> List[int]: - """Get the indices of the qubits used as obstacle ancilla for the specified dimension. - - Parameters - ---------- - index : int | None, optional - The index of the grid for which to retrieve the obstacle qubit index, by default ``None``. - When ``index`` is ``None``, the indices of ancillae qubits for all dimensions are returned. - For 2D lattices with only bounce-back boundary-conditions, only one obstacle - qubit is required. - For all other configurations, the algorithm uses ``2d-2`` obstacle qubits. - - Returns - ------- - List[int] - A list of indices of the qubits used as obstacle ancilla for the given dimension. - - Raises - ------ - LatticeException - If the dimension does not exist. - """ if index is None: return list(range(self.num_dims, self.num_dims + self.num_obstacle_qubits)) @@ -263,28 +243,8 @@ def ancillae_obstacle_index(self, index: int | None = None) -> List[int]: # There are `d` ancillae velocity qubits "ahead" of this register return [self.num_dims + index] + @override def ancillae_comparator_index(self, index: int | None = None) -> List[int]: - """Get the indices of the qubits used as comparator ancillae for the specified index. - - Parameters - ---------- - index : int | None, optional - The index for which to retrieve the comparator qubit indices, by default ``None``. - There are `num_dims-1` available indices (i.e., 1 for 2D and 2 for 3D). - When `index` is ``None``, the indices of ancillae qubits for all dimensions are returned. - - Returns - ------- - List[int] - A list of indices of the qubits used as obstacle ancilla for the given dimension. - By convention, the 0th qubit in the returned list is used - for lower bound comparison and the 1st is used for upper bound comparisons. - - Raises - ------ - LatticeException - If the dimension does not exist. - """ # Ahead of this register # `d` ancillae velocity qubits # `num_obstacle_qubits` ancillae obstacle qubits @@ -305,25 +265,8 @@ def ancillae_comparator_index(self, index: int | None = None) -> List[int]: previous_qubits = self.num_dims + self.num_obstacle_qubits + 2 * index return list(range(previous_qubits, previous_qubits + 2)) + @override def grid_index(self, dim: int | None = None) -> List[int]: - """Get the indices of the qubits used that encode the grid values for the specified dimension. - - Parameters - ---------- - dim : int | None, optional - The dimension of the grid for which to retrieve the grid qubit indices, by default ``None``. - When ``dim`` is ``None``, the indices of all grid qubits for all dimensions are returned. - - Returns - ------- - List[int] - A list of indices of the qubits used to encode the grid values for the given dimension. - - Raises - ------ - LatticeException - If the dimension does not exist. - """ if dim is None: return list( range( @@ -350,25 +293,8 @@ def grid_index(self, dim: int | None = None) -> List[int]: ) ) + @override def velocity_index(self, dim: int | None = None) -> List[int]: - """Get the indices of the qubits used that encode the velocity magnitude values for the specified dimension. - - Parameters - ---------- - dim : int | None, optional - The dimension of the grid for which to retrieve the velocity qubit indices, by default ``None``. - When ``dim`` is ``None``, the indices of all velocity magnitude qubits for all dimensions are returned. - - Returns - ------- - List[int] - A list of indices of the qubits used to encode the velocity magnitude values for the given dimension. - - Raises - ------ - LatticeException - If the dimension does not exist. - """ if dim is None: return list( range( From eff48772f40902f4fa3541c323a3848158632921 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 19:25:34 +0100 Subject: [PATCH 33/61] Change ABELattice to ABLattice --- qlbm/components/abe/abe.py | 6 +- qlbm/components/abe/averaged_collision.py | 57 +++++++++++++++++++ qlbm/components/abe/initial.py | 4 +- qlbm/components/abe/measurement.py | 4 +- qlbm/components/abe/reflection.py | 6 +- qlbm/components/abe/streaming.py | 6 +- qlbm/infra/result/collisionless_result.py | 6 +- qlbm/infra/runner/base.py | 6 +- qlbm/lattice/__init__.py | 4 +- qlbm/lattice/lattices/abe_lattice.py | 4 +- test/unit/abe/__init__.py | 0 .../abe/abe_reflection_permutation_test.py | 34 +++++++++++ .../lattice/abe_lattice_exception_test.py | 16 +++--- .../lattice/abe_lattice_properties_test.py | 12 ++-- test/unit/lattice/conftest.py | 18 +++--- 15 files changed, 137 insertions(+), 46 deletions(-) create mode 100644 qlbm/components/abe/averaged_collision.py create mode 100644 test/unit/abe/__init__.py create mode 100644 test/unit/abe/abe_reflection_permutation_test.py diff --git a/qlbm/components/abe/abe.py b/qlbm/components/abe/abe.py index 6f7c3e3..92b592c 100644 --- a/qlbm/components/abe/abe.py +++ b/qlbm/components/abe/abe.py @@ -9,7 +9,7 @@ from qlbm.components.abe.reflection import ABEReflectionOperator from qlbm.components.base import LBMAlgorithm from qlbm.lattice.geometry.shapes.block import Block -from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.lattices.abe_lattice import ABLattice from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import flatten @@ -21,11 +21,11 @@ class ABECQLBM(LBMAlgorithm): def __init__( self, - lattice: ABELattice, + lattice: ABLattice, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(lattice, logger) - self.lattice: ABELattice = lattice + self.lattice: ABLattice = lattice self.logger.info(f"Creating circuit {str(self)}...") circuit_creation_start_time = perf_counter_ns() diff --git a/qlbm/components/abe/averaged_collision.py b/qlbm/components/abe/averaged_collision.py new file mode 100644 index 0000000..513aa07 --- /dev/null +++ b/qlbm/components/abe/averaged_collision.py @@ -0,0 +1,57 @@ +from logging import Logger, getLogger +from time import perf_counter_ns + +from qiskit import QuantumCircuit +from typing_extensions import override + +from qlbm.components.base import LBMOperator +from qlbm.components.common.primitives import TruncatedQFT +from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.spacetime.properties_base import LatticeDiscretization +from qlbm.tools.exceptions import LatticeException + + +class ABEAveragedCollisionOperator(LBMOperator): + """TODO.""" + + lattice: ABLattice + + def __init__( + self, + lattice: ABLattice, + logger: Logger = getLogger("qlbm"), + ) -> None: + super().__init__(lattice, logger) + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self) -> QuantumCircuit: + if self.lattice.discretization == LatticeDiscretization.D1Q3: + return self.__create_circuit_d1q3() + + raise LatticeException("ABE only currently supported in D1Q3") + + def __create_circuit_d1q3(self): + circuit = self.lattice.circuit.copy() + + circuit.compose( + TruncatedQFT( + self.lattice.num_velocity_qubits, + self.lattice.num_velocities_per_point, + self.logger, + ).circuit, + qubits=self.lattice.velocity_index(), + inplace=True, + ) + + return circuit + + @override + def __str__(self) -> str: + return f"[Operator ABEAveragedCollision with lattice {self.lattice}]" diff --git a/qlbm/components/abe/initial.py b/qlbm/components/abe/initial.py index 5a43c44..9011f07 100644 --- a/qlbm/components/abe/initial.py +++ b/qlbm/components/abe/initial.py @@ -6,7 +6,7 @@ from qlbm.components.base import LBMPrimitive from qlbm.components.common.primitives import TruncatedQFT -from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.lattices.abe_lattice import ABLattice class ABEInitialConditions(LBMPrimitive): @@ -14,7 +14,7 @@ class ABEInitialConditions(LBMPrimitive): def __init__( self, - lattice: ABELattice, + lattice: ABLattice, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(logger) diff --git a/qlbm/components/abe/measurement.py b/qlbm/components/abe/measurement.py index 2c74c54..6833eab 100644 --- a/qlbm/components/abe/measurement.py +++ b/qlbm/components/abe/measurement.py @@ -5,7 +5,7 @@ from typing_extensions import override from qlbm.components.base import LBMPrimitive -from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.lattices.abe_lattice import ABLattice class ABEGridMeasurement(LBMPrimitive): @@ -13,7 +13,7 @@ class ABEGridMeasurement(LBMPrimitive): def __init__( self, - lattice: ABELattice, + lattice: ABLattice, measure_velocity_qubits: bool = False, logger: Logger = getLogger("qlbm"), ) -> None: diff --git a/qlbm/components/abe/reflection.py b/qlbm/components/abe/reflection.py index ecb8db1..db4761f 100644 --- a/qlbm/components/abe/reflection.py +++ b/qlbm/components/abe/reflection.py @@ -12,7 +12,7 @@ from qlbm.components.collisionless.specular_reflection import SpecularWallComparator from qlbm.lattice.geometry.encodings.collisionless import ReflectionPoint from qlbm.lattice.geometry.shapes.block import Block -from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.lattices.abe_lattice import ABLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import flatten, get_qubits_to_invert @@ -21,11 +21,11 @@ class ABEReflectionOperator(LBMOperator): """TODO.""" - lattice: ABELattice + lattice: ABLattice def __init__( self, - lattice: ABELattice, + lattice: ABLattice, blocks: List[Block], logger: Logger = getLogger("qlbm"), ) -> None: diff --git a/qlbm/components/abe/streaming.py b/qlbm/components/abe/streaming.py index bdc5526..8449269 100644 --- a/qlbm/components/abe/streaming.py +++ b/qlbm/components/abe/streaming.py @@ -8,7 +8,7 @@ from qlbm.components.base import LBMOperator from qlbm.components.collisionless.streaming import PhaseShift -from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.lattices.abe_lattice import ABLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import get_qubits_to_invert @@ -17,11 +17,11 @@ class ABEStreamingOperator(LBMOperator): """TODO.""" - lattice: ABELattice + lattice: ABLattice def __init__( self, - lattice: ABELattice, + lattice: ABLattice, additional_control_qubit_indices: List[int] = [], logger: Logger = getLogger("qlbm"), ) -> None: diff --git a/qlbm/infra/result/collisionless_result.py b/qlbm/infra/result/collisionless_result.py index a27769a..a4dd2b8 100644 --- a/qlbm/infra/result/collisionless_result.py +++ b/qlbm/infra/result/collisionless_result.py @@ -10,7 +10,7 @@ from vtkmodules.util import numpy_support from qlbm.lattice import CollisionlessLattice -from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.lattices.abe_lattice import ABLattice from .base import QBMResult @@ -39,12 +39,12 @@ class CollisionlessResult(QBMResult): output_file_name: str """The name of the file to output the artifacts to.""" - lattice: CollisionlessLattice | ABELattice + lattice: CollisionlessLattice | ABLattice """The lattice the result corresponds to.""" def __init__( self, - lattice: CollisionlessLattice | ABELattice, + lattice: CollisionlessLattice | ABLattice, directory: str, output_file_name: str = "step", ) -> None: diff --git a/qlbm/infra/runner/base.py b/qlbm/infra/runner/base.py index 2d2cb31..f13fa6c 100644 --- a/qlbm/infra/runner/base.py +++ b/qlbm/infra/runner/base.py @@ -21,7 +21,7 @@ SpaceTimeResult, ) from qlbm.lattice import CollisionlessLattice, Lattice -from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.lattices.abe_lattice import ABLattice from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice from qlbm.tools.exceptions import CircuitException, ResultsException @@ -125,7 +125,7 @@ def new_result(self, output_directory: str, output_file_name: str) -> QBMResult: If there is no matching result object for the runner's lattice. """ if isinstance(self.lattice, CollisionlessLattice) or isinstance( - self.lattice, ABELattice + self.lattice, ABLattice ): return CollisionlessResult( self.lattice, # type: ignore @@ -160,7 +160,7 @@ def new_reinitializer(self) -> Reinitializer: if ( isinstance(self.lattice, CollisionlessLattice) or isinstance(self.lattice, LQLGALattice) - or isinstance(self.lattice, ABELattice) + or isinstance(self.lattice, ABLattice) ): return IdentityReinitializer( self.lattice, diff --git a/qlbm/lattice/__init__.py b/qlbm/lattice/__init__.py index 45364e4..e48ab5c 100644 --- a/qlbm/lattice/__init__.py +++ b/qlbm/lattice/__init__.py @@ -13,7 +13,7 @@ Circle, ) from .lattices import CollisionlessLattice, Lattice -from .lattices.abe_lattice import ABELattice +from .lattices.abe_lattice import ABLattice from .lattices.lqlga_lattice import LQLGALattice from .lattices.spacetime_lattice import SpaceTimeLattice from .spacetime.properties_base import ( @@ -23,7 +23,7 @@ __all__ = [ "Lattice", - "ABELattice", + "ABLattice", "CollisionlessLattice", "SpaceTimeLattice", "LQLGALattice", diff --git a/qlbm/lattice/lattices/abe_lattice.py b/qlbm/lattice/lattices/abe_lattice.py index 40aaf14..256ce81 100644 --- a/qlbm/lattice/lattices/abe_lattice.py +++ b/qlbm/lattice/lattices/abe_lattice.py @@ -18,7 +18,7 @@ from .base import AmplitudeLattice -class ABELattice(AmplitudeLattice): +class ABLattice(AmplitudeLattice): """TODO.""" discretization: LatticeDiscretization @@ -108,7 +108,7 @@ def grid_index(self, dim: int | None = None) -> List[int]: @override def velocity_index(self, dim: int | None = None) -> List[int]: if dim is not None: - raise LatticeException("ABELattice does not support a dimensional breakdown of velocities.") + raise LatticeException("ABLattice does not support a dimensional breakdown of velocities.") return list( range( self.num_grid_qubits, diff --git a/test/unit/abe/__init__.py b/test/unit/abe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/unit/abe/abe_reflection_permutation_test.py b/test/unit/abe/abe_reflection_permutation_test.py new file mode 100644 index 0000000..e549806 --- /dev/null +++ b/test/unit/abe/abe_reflection_permutation_test.py @@ -0,0 +1,34 @@ +import pytest +from qiskit import QuantumCircuit, transpile +from qiskit_aer import AerSimulator + +from qlbm.components.abe.reflection import ABEReflectionPermutation +from qlbm.lattice.spacetime.properties_base import LatticeDiscretization +from qlbm.tools.utils import bit_value, get_qubits_to_invert + + +@pytest.mark.parametrize( + "permutation_outcome_pairs", + [(0, 0), (1, 3), (2, 4), (3, 1), (4, 2), (5, 7), (6, 8), (7, 5), (8, 6)] + + [(i, i) for i in range(9, 16)], +) +def test_reflectionpermutation_outcomes_d2q9(permutation_outcome_pairs): + nq = 4 + sim = AerSimulator() + + qc = QuantumCircuit(nq) + for q in range(nq): + if bit_value(permutation_outcome_pairs[0], q): + qc.x(q) + + qc.compose( + ABEReflectionPermutation(nq, LatticeDiscretization.D2Q9).circuit, inplace=True + ) + qc.measure_all() + tqc = transpile(qc, sim, optimization_level=0) + + counts = sim.run(tqc, shots=128).result().get_counts() + + assert all(int(c, 2) == permutation_outcome_pairs[1] for c in counts.keys()), ( + f"{permutation_outcome_pairs} handled incorrectly. Expected {permutation_outcome_pairs}, got {counts}." + ) diff --git a/test/unit/lattice/abe_lattice_exception_test.py b/test/unit/lattice/abe_lattice_exception_test.py index 05ed6f4..ca3032e 100644 --- a/test/unit/lattice/abe_lattice_exception_test.py +++ b/test/unit/lattice/abe_lattice_exception_test.py @@ -1,26 +1,26 @@ import pytest -from qlbm.lattice import ABELattice +from qlbm.lattice import ABLattice from qlbm.tools.exceptions import LatticeException def test_lattice_exception_empty_dict(): with pytest.raises(LatticeException) as excinfo: - ABELattice({}) + ABLattice({}) assert 'Input configuration missing "lattice" properties.' == str(excinfo.value) def test_lattice_exception_no_dims(): with pytest.raises(LatticeException) as excinfo: - ABELattice({"lattice": {}}) + ABLattice({"lattice": {}}) assert 'Lattice configuration missing "dim" properties.' == str(excinfo.value) def test_lattice_exception_no_velocities(): with pytest.raises(LatticeException) as excinfo: - ABELattice({"lattice": {"dim": {}}}) + ABLattice({"lattice": {"dim": {}}}) assert 'Lattice configuration missing "velocities" properties.' == str( excinfo.value @@ -28,14 +28,14 @@ def test_lattice_exception_no_velocities(): def test_lattice_exception_mismatched_velocities_and_dims(): with pytest.raises(LatticeException) as excinfo: - ABELattice({"lattice": {"dim": {"x": 64}, "velocities": "D2Q4"}}) + ABLattice({"lattice": {"dim": {"x": 64}, "velocities": "D2Q4"}}) assert "Velocity specification dimensions (2) do not match lattice dimensions (1)." == str(excinfo.value) def test_lattice_exception_unsupported_discretization(): with pytest.raises(LatticeException) as excinfo: - ABELattice({"lattice": {"dim": {"x": 64}, "velocities": {"x": 4}}}) + ABLattice({"lattice": {"dim": {"x": 64}, "velocities": {"x": 4}}}) assert 'Discretization LatticeDiscretization.CFLDISCRETIZATION is not supported.' == str( excinfo.value @@ -43,7 +43,7 @@ def test_lattice_exception_unsupported_discretization(): def test_lattice_exception_mismatched_bad_dimensions(): with pytest.raises(LatticeException) as excinfo: - ABELattice( + ABLattice( { "lattice": { "dim": {"x": 64, "y": 127}, @@ -59,7 +59,7 @@ def test_lattice_exception_mismatched_bad_dimensions(): def test_lattice_exception_mismatched_bad_object_dimensions(): with pytest.raises(LatticeException) as excinfo: - ABELattice( + ABLattice( { "lattice": { "dim": {"x": 64, "y": 64}, diff --git a/test/unit/lattice/abe_lattice_properties_test.py b/test/unit/lattice/abe_lattice_properties_test.py index cf8db50..278e1d1 100644 --- a/test/unit/lattice/abe_lattice_properties_test.py +++ b/test/unit/lattice/abe_lattice_properties_test.py @@ -1,10 +1,10 @@ import pytest -from qlbm.lattice import ABELattice +from qlbm.lattice import ABLattice from qlbm.tools.exceptions import LatticeException -def test_2d_abe_lattice_basic_properties(lattice_2d_16x16_1_obstacle: ABELattice): +def test_2d_abe_lattice_basic_properties(lattice_2d_16x16_1_obstacle: ABLattice): assert lattice_2d_16x16_1_obstacle.num_dims == 2 assert lattice_2d_16x16_1_obstacle.num_gridpoints == [15, 15] assert lattice_2d_16x16_1_obstacle.num_ancilla_qubits == 3 @@ -13,7 +13,7 @@ def test_2d_abe_lattice_basic_properties(lattice_2d_16x16_1_obstacle: ABELattice assert lattice_2d_16x16_1_obstacle.num_total_qubits == 13 -def test_2d_lattice_grid_register(lattice_2d_16x16_1_obstacle: ABELattice): +def test_2d_lattice_grid_register(lattice_2d_16x16_1_obstacle: ABLattice): assert lattice_2d_16x16_1_obstacle.grid_index(0) == list(range(4)) assert lattice_2d_16x16_1_obstacle.grid_index(1) == list(range(4, 8)) assert lattice_2d_16x16_1_obstacle.grid_index() == list(range(8)) @@ -27,12 +27,12 @@ def test_2d_lattice_grid_register(lattice_2d_16x16_1_obstacle: ABELattice): def test_2d_lattice_velocity_register( - lattice_2d_16x16_1_obstacle: ABELattice, + lattice_2d_16x16_1_obstacle: ABLattice, ): assert lattice_2d_16x16_1_obstacle.velocity_index() == [8, 9] def test_2d_lattice_ancilla_comparator_register( - lattice_2d_16x16_1_obstacle: ABELattice, + lattice_2d_16x16_1_obstacle: ABLattice, ): assert lattice_2d_16x16_1_obstacle.ancillae_comparator_index(0) == [10, 11] assert lattice_2d_16x16_1_obstacle.ancillae_comparator_index() == [10, 11] @@ -46,7 +46,7 @@ def test_2d_lattice_ancilla_comparator_register( def test_2d_lattice_ancilla_obstacle_register( - lattice_2d_16x16_1_obstacle: ABELattice, + lattice_2d_16x16_1_obstacle: ABLattice, ): assert lattice_2d_16x16_1_obstacle.ancillae_obstacle_index() == [8 + 2 + 2] diff --git a/test/unit/lattice/conftest.py b/test/unit/lattice/conftest.py index 1d5fa0d..66d9dbc 100644 --- a/test/unit/lattice/conftest.py +++ b/test/unit/lattice/conftest.py @@ -1,13 +1,13 @@ import pytest from qlbm.lattice.geometry.shapes.block import Block -from qlbm.lattice.lattices.abe_lattice import ABELattice +from qlbm.lattice.lattices.abe_lattice import ABLattice # 1D Lattices @pytest.fixture -def dummy_1d_lattice() -> ABELattice: - return ABELattice( +def dummy_1d_lattice() -> ABLattice: + return ABLattice( 0, { "lattice": { @@ -19,8 +19,8 @@ def dummy_1d_lattice() -> ABELattice: @pytest.fixture -def lattice_1d_16_1_obstacle() -> ABELattice: - return ABELattice( +def lattice_1d_16_1_obstacle() -> ABLattice: + return ABLattice( { "lattice": { "dim": {"x": 16}, @@ -36,8 +36,8 @@ def lattice_1d_16_1_obstacle() -> ABELattice: # 2D Lattices @pytest.fixture -def dummy_2d_lattice() -> ABELattice: - return ABELattice( +def dummy_2d_lattice() -> ABLattice: + return ABLattice( 0, { "lattice": { @@ -49,8 +49,8 @@ def dummy_2d_lattice() -> ABELattice: @pytest.fixture -def lattice_2d_16x16_1_obstacle() -> ABELattice: - return ABELattice( +def lattice_2d_16x16_1_obstacle() -> ABLattice: + return ABLattice( { "lattice": { "dim": {"x": 16, "y": 16}, From ff45d0bec72794658904e7f2059578b23317ff69 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 20:10:41 +0100 Subject: [PATCH 34/61] Refactor algorithm names into MS and AB --- qlbm/__init__.py | 12 +-- qlbm/components/__init__.py | 38 ++++----- qlbm/components/{abe => ab}/__init__.py | 2 +- qlbm/components/{abe/abe.py => ab/ab.py} | 2 +- .../{abe => ab}/averaged_collision.py | 0 qlbm/components/{abe => ab}/initial.py | 0 qlbm/components/{abe => ab}/measurement.py | 0 qlbm/components/{abe => ab}/reflection.py | 6 +- qlbm/components/{abe => ab}/streaming.py | 2 +- qlbm/components/base.py | 14 ++-- qlbm/components/common/primitives.py | 2 +- .../{collisionless => ms}/__init__.py | 12 +-- .../bounceback_reflection.py | 34 ++++---- .../components/{collisionless => ms}/cqlbm.py | 16 ++-- .../{collisionless => ms}/primitives.py | 60 ++++++------- .../specular_reflection.py | 32 +++---- .../{collisionless => ms}/streaming.py | 48 +++++------ .../spacetime/initial/volumetric.py | 2 +- .../spacetime/reflection/volumetric.py | 2 +- qlbm/infra/__init__.py | 4 +- qlbm/infra/result/__init__.py | 4 +- ...sionless_result.py => amplitude_result.py} | 13 ++- qlbm/infra/runner/base.py | 10 +-- qlbm/infra/runner/mpi_qulacs.py | 16 ++-- qlbm/infra/runner/simulation_config.py | 4 +- qlbm/lattice/__init__.py | 6 +- qlbm/lattice/geometry/encodings/__init__.py | 2 +- .../encodings/{collisionless.py => ms.py} | 0 qlbm/lattice/geometry/shapes/block.py | 2 +- qlbm/lattice/lattices/__init__.py | 4 +- ...collisionless_lattice.py => ms_lattice.py} | 6 +- test/integration/compiler_test.py | 12 +-- test/integration/execution_test.py | 10 +-- test/integration/simulation_config_test.py | 14 ++-- test/unit/{abe => ab}/__init__.py | 0 .../ab_reflection_permutation_test.py} | 2 +- test/unit/blocks_2d_test.py | 2 +- test/unit/blocks_3d_test.py | 2 +- test/unit/collisionles_lattice_test.py | 84 +++++++++---------- test/unit/cqlbm_test.py | 8 +- test/unit/incrementer_test.py | 8 +- 41 files changed, 248 insertions(+), 249 deletions(-) rename qlbm/components/{abe => ab}/__init__.py (90%) rename qlbm/components/{abe/abe.py => ab/ab.py} (97%) rename qlbm/components/{abe => ab}/averaged_collision.py (100%) rename qlbm/components/{abe => ab}/initial.py (100%) rename qlbm/components/{abe => ab}/measurement.py (100%) rename qlbm/components/{abe => ab}/reflection.py (98%) rename qlbm/components/{abe => ab}/streaming.py (98%) rename qlbm/components/{collisionless => ms}/__init__.py (80%) rename qlbm/components/{collisionless => ms}/bounceback_reflection.py (94%) rename qlbm/components/{collisionless => ms}/cqlbm.py (88%) rename qlbm/components/{collisionless => ms}/primitives.py (90%) rename qlbm/components/{collisionless => ms}/specular_reflection.py (95%) rename qlbm/components/{collisionless => ms}/streaming.py (90%) rename qlbm/infra/result/{collisionless_result.py => amplitude_result.py} (93%) rename qlbm/lattice/geometry/encodings/{collisionless.py => ms.py} (100%) rename qlbm/lattice/lattices/{collisionless_lattice.py => ms_lattice.py} (99%) rename test/unit/{abe => ab}/__init__.py (100%) rename test/unit/{abe/abe_reflection_permutation_test.py => ab/ab_reflection_permutation_test.py} (94%) diff --git a/qlbm/__init__.py b/qlbm/__init__.py index f2d7a69..39d68e7 100644 --- a/qlbm/__init__.py +++ b/qlbm/__init__.py @@ -6,23 +6,23 @@ from .components import ( CQLBM, BounceBackReflectionOperator, - CollisionlessStreamingOperator, + MSStreamingOperator, SpecularReflectionOperator, ) -from .infra import CircuitCompiler, CollisionlessResult, QiskitRunner -from .lattice import CollisionlessLattice, Lattice +from .infra import CircuitCompiler, AmplitudeResult, QiskitRunner +from .lattice import MSLattice, Lattice from .lattice.lattices.spacetime_lattice import SpaceTimeLattice __all__ = [ "Lattice", - "CollisionlessLattice", + "MSLattice", "SpaceTimeLattice", - "CollisionlessStreamingOperator", + "MSStreamingOperator", "SpecularReflectionOperator", "BounceBackReflectionOperator", "CQLBM", "CircuitCompiler", "QiskitRunner", "QulacsRunner", - "CollisionlessResult", + "AmplitudeResult", ] diff --git a/qlbm/components/__init__.py b/qlbm/components/__init__.py index 1afb9b4..fe3df3e 100644 --- a/qlbm/components/__init__.py +++ b/qlbm/components/__init__.py @@ -1,28 +1,13 @@ """Modular and extendible quantum circuits that perform parts of the QLBM algorithm.""" from .base import ( - CQLBMOperator, + MSOperator, LBMAlgorithm, LBMOperator, LBMPrimitive, QuantumComponent, SpaceTimeOperator, ) -from .collisionless import ( - CQLBM, - BounceBackReflectionOperator, - CollisionlessInitialConditions, - CollisionlessStreamingOperator, - GridMeasurement, - SpecularReflectionOperator, -) -from .collisionless.primitives import Comparator, ComparatorMode, SpeedSensitiveAdder -from .collisionless.streaming import ( - ControlledIncrementer, - PhaseShift, - SpeedSensitivePhaseShift, - StreamingAncillaPreparation, -) from .common import ( EmptyPrimitive, EQCCollisionOperator, @@ -39,13 +24,28 @@ LQLGAReflectionOperator, LQLGAStreamingOperator, ) +from .ms import ( + CQLBM, + BounceBackReflectionOperator, + MSInitialConditions, + GridMeasurement, + MSStreamingOperator, + SpecularReflectionOperator, +) +from .ms.primitives import Comparator, ComparatorMode, SpeedSensitiveAdder +from .ms.streaming import ( + ControlledIncrementer, + PhaseShift, + SpeedSensitivePhaseShift, + StreamingAncillaPreparation, +) __all__ = [ "QuantumComponent", "LBMPrimitive", "GenericLQLGACollisionOperator", "LBMOperator", - "CQLBMOperator", + "MSOperator", "SpaceTimeOperator", "LBMAlgorithm", "ComparatorMode", @@ -57,8 +57,8 @@ "StreamingAncillaPreparation", "ControlledIncrementer", "GridMeasurement", - "CollisionlessInitialConditions", - "CollisionlessStreamingOperator", + "MSInitialConditions", + "MSStreamingOperator", "SpecularReflectionOperator", "BounceBackReflectionOperator", "CQLBM", diff --git a/qlbm/components/abe/__init__.py b/qlbm/components/ab/__init__.py similarity index 90% rename from qlbm/components/abe/__init__.py rename to qlbm/components/ab/__init__.py index 3f9d50e..ab7d901 100644 --- a/qlbm/components/abe/__init__.py +++ b/qlbm/components/ab/__init__.py @@ -1,4 +1,4 @@ -from .abe import ABECQLBM +from .ab import ABECQLBM from .initial import ABEInitialConditions from .measurement import ABEGridMeasurement from .streaming import ABEStreamingOperator diff --git a/qlbm/components/abe/abe.py b/qlbm/components/ab/ab.py similarity index 97% rename from qlbm/components/abe/abe.py rename to qlbm/components/ab/ab.py index 92b592c..2040a81 100644 --- a/qlbm/components/abe/abe.py +++ b/qlbm/components/ab/ab.py @@ -6,7 +6,7 @@ from qiskit import QuantumCircuit from typing_extensions import override -from qlbm.components.abe.reflection import ABEReflectionOperator +from qlbm.components.ab.reflection import ABEReflectionOperator from qlbm.components.base import LBMAlgorithm from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.lattices.abe_lattice import ABLattice diff --git a/qlbm/components/abe/averaged_collision.py b/qlbm/components/ab/averaged_collision.py similarity index 100% rename from qlbm/components/abe/averaged_collision.py rename to qlbm/components/ab/averaged_collision.py diff --git a/qlbm/components/abe/initial.py b/qlbm/components/ab/initial.py similarity index 100% rename from qlbm/components/abe/initial.py rename to qlbm/components/ab/initial.py diff --git a/qlbm/components/abe/measurement.py b/qlbm/components/ab/measurement.py similarity index 100% rename from qlbm/components/abe/measurement.py rename to qlbm/components/ab/measurement.py diff --git a/qlbm/components/abe/reflection.py b/qlbm/components/ab/reflection.py similarity index 98% rename from qlbm/components/abe/reflection.py rename to qlbm/components/ab/reflection.py index db4761f..9713e19 100644 --- a/qlbm/components/abe/reflection.py +++ b/qlbm/components/ab/reflection.py @@ -7,10 +7,10 @@ from qiskit.circuit.library import MCMTGate, XGate from typing_extensions import override -from qlbm.components.abe.streaming import ABEStreamingOperator +from qlbm.components.ab.streaming import ABEStreamingOperator from qlbm.components.base import LBMOperator, LBMPrimitive -from qlbm.components.collisionless.specular_reflection import SpecularWallComparator -from qlbm.lattice.geometry.encodings.collisionless import ReflectionPoint +from qlbm.components.ms.specular_reflection import SpecularWallComparator +from qlbm.lattice.geometry.encodings.ms import ReflectionPoint from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.lattices.abe_lattice import ABLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization diff --git a/qlbm/components/abe/streaming.py b/qlbm/components/ab/streaming.py similarity index 98% rename from qlbm/components/abe/streaming.py rename to qlbm/components/ab/streaming.py index 8449269..938d552 100644 --- a/qlbm/components/abe/streaming.py +++ b/qlbm/components/ab/streaming.py @@ -7,7 +7,7 @@ from typing_extensions import override from qlbm.components.base import LBMOperator -from qlbm.components.collisionless.streaming import PhaseShift +from qlbm.components.ms.streaming import PhaseShift from qlbm.lattice.lattices.abe_lattice import ABLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException diff --git a/qlbm/components/base.py b/qlbm/components/base.py index fc3cf73..65178ef 100644 --- a/qlbm/components/base.py +++ b/qlbm/components/base.py @@ -9,7 +9,7 @@ from qiskit.qasm3 import dump as dump_qasm3 from typing_extensions import override -from qlbm.lattice import CollisionlessLattice, Lattice +from qlbm.lattice import MSLattice, Lattice from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice @@ -184,27 +184,27 @@ def __init__( self.lattice = lattice -class CQLBMOperator(LBMOperator): +class MSOperator(LBMOperator): """ - Specialization of the :class:`.LBMOperator` operator class for the Collisionless Quantum Transport Method algorithm by :cite:t:`collisionless`. + Specialization of the :class:`.LBMOperator` operator class for the Multi-Speed Collisionless Quantum Lattice Boltzmann Method algorithm by :cite:t:`collisionless`. Specializaitons of this class infer their properties - based on a :class:`.CollisionlessLattice`. + based on a :class:`.MSLattice`. ========================= ====================================================================== Attribute Summary ========================= ====================================================================== :attr:`circuit` The :class:`.qiskit.QuantumCircuit` of the operator. - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`logger` The performance logger, by default ``getLogger("qlbm")`` ========================= ====================================================================== """ - lattice: CollisionlessLattice + lattice: MSLattice def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(lattice, logger) diff --git a/qlbm/components/common/primitives.py b/qlbm/components/common/primitives.py index 3140f74..3a7dc5d 100644 --- a/qlbm/components/common/primitives.py +++ b/qlbm/components/common/primitives.py @@ -26,7 +26,7 @@ class EmptyPrimitive(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` or :class:`.SpaceTimeLattice` based on which the number of qubits is inferred. + :attr:`lattice` The :class:`.MSLattice` or :class:`.SpaceTimeLattice` based on which the number of qubits is inferred. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== """ diff --git a/qlbm/components/collisionless/__init__.py b/qlbm/components/ms/__init__.py similarity index 80% rename from qlbm/components/collisionless/__init__.py rename to qlbm/components/ms/__init__.py index fd04d41..b0b4a8e 100644 --- a/qlbm/components/collisionless/__init__.py +++ b/qlbm/components/ms/__init__.py @@ -6,8 +6,8 @@ ) from .cqlbm import CQLBM from .primitives import ( - CollisionlessInitialConditions, - CollisionlessInitialConditions3DSlim, + MSInitialConditions, + MSInitialConditions3DSlim, Comparator, ComparatorMode, EdgeComparator, @@ -16,8 +16,8 @@ ) from .specular_reflection import SpecularReflectionOperator, SpecularWallComparator from .streaming import ( - CollisionlessStreamingOperator, ControlledIncrementer, + MSStreamingOperator, PhaseShift, SpeedSensitivePhaseShift, StreamingAncillaPreparation, @@ -31,11 +31,11 @@ "ControlledIncrementer", "GridMeasurement", "EdgeComparator", - "CollisionlessInitialConditions", - "CollisionlessInitialConditions3DSlim", + "MSInitialConditions", + "MSInitialConditions3DSlim", "PhaseShift", "SpeedSensitivePhaseShift", - "CollisionlessStreamingOperator", + "MSStreamingOperator", "SpecularReflectionOperator", "SpecularWallComparator", "BounceBackReflectionOperator", diff --git a/qlbm/components/collisionless/bounceback_reflection.py b/qlbm/components/ms/bounceback_reflection.py similarity index 94% rename from qlbm/components/collisionless/bounceback_reflection.py rename to qlbm/components/ms/bounceback_reflection.py index 7ca3571..1ff7272 100644 --- a/qlbm/components/collisionless/bounceback_reflection.py +++ b/qlbm/components/ms/bounceback_reflection.py @@ -8,14 +8,14 @@ from qiskit.circuit.library import MCMTGate, XGate from typing_extensions import override -from qlbm.components.base import CQLBMOperator, LBMPrimitive -from qlbm.components.collisionless.primitives import ( +from qlbm.components.base import MSOperator, LBMPrimitive +from qlbm.components.ms.primitives import ( Comparator, ComparatorMode, ) -from qlbm.components.collisionless.specular_reflection import SpecularWallComparator -from qlbm.lattice import CollisionlessLattice -from qlbm.lattice.geometry.encodings.collisionless import ( +from qlbm.components.ms.specular_reflection import SpecularWallComparator +from qlbm.lattice import MSLattice +from qlbm.lattice.geometry.encodings.ms import ( ReflectionPoint, ReflectionResetEdge, ReflectionWall, @@ -38,7 +38,7 @@ class BounceBackWallComparator(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`wall` The :class:`.ReflectionWall` encoding the range spanned by the wall. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -48,11 +48,11 @@ class BounceBackWallComparator(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import BounceBackWallComparator - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import BounceBackWallComparator + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice( + lattice = MSLattice( { "lattice": {"dim": {"x": 8, "y": 8}, "velocities": {"x": 4, "y": 4}}, "geometry": [{"shape":"cuboid", "x": [5, 6], "y": [1, 2], "boundary": "bounceback"}], @@ -67,7 +67,7 @@ class BounceBackWallComparator(LBMPrimitive): def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, wall: ReflectionWall, logger: Logger = getLogger("qlbm"), ) -> None: @@ -133,7 +133,7 @@ def __str__(self) -> str: return f"[Primitive BounceBackWallComparator on wall={self.wall}]" -class BounceBackReflectionOperator(CQLBMOperator): +class BounceBackReflectionOperator(MSOperator): """ Operator implementing the 2D and 3D Bounce-Back (BB) boundary conditions as described in :cite:t:`qmem`. @@ -150,7 +150,7 @@ class BounceBackReflectionOperator(CQLBMOperator): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`blocks` A list of :class:`.Block` objects for which to generate the BB boundary condition circuits. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -160,11 +160,11 @@ class BounceBackReflectionOperator(CQLBMOperator): .. plot:: :include-source: - from qlbm.components.collisionless import BounceBackReflectionOperator - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import BounceBackReflectionOperator + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice( + lattice = MSLattice( { "lattice": {"dim": {"x": 8, "y": 8}, "velocities": {"x": 4, "y": 4}}, "geometry": [{"shape":"cuboid", "x": [5, 6], "y": [1, 2], "boundary": "bounceback"}], @@ -176,7 +176,7 @@ class BounceBackReflectionOperator(CQLBMOperator): def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, blocks: List[Block], logger: Logger = getLogger("qlbm"), ) -> None: @@ -494,7 +494,7 @@ def flip_and_stream( ): """Flips the velocity direction qubit controlled on the ancilla obstacle qubit, before performing streaming. - Unlike in the regular :class:`.CollisionlessStreamingOperator`, the :class:`.ControlledIncrementer` + Unlike in the regular :class:`.MSStreamingOperator`, the :class:`.ControlledIncrementer` phase shift circuit is additionally controlled on the ancilla obstacle qubit, which ensures that only particles whose grid position gets incremented (decremented) are those that have streamed inside the solid domain in this CFL time step. diff --git a/qlbm/components/collisionless/cqlbm.py b/qlbm/components/ms/cqlbm.py similarity index 88% rename from qlbm/components/collisionless/cqlbm.py rename to qlbm/components/ms/cqlbm.py index 81fb1cd..b868582 100644 --- a/qlbm/components/collisionless/cqlbm.py +++ b/qlbm/components/ms/cqlbm.py @@ -7,14 +7,14 @@ from typing_extensions import override from qlbm.components.base import LBMAlgorithm -from qlbm.lattice import CollisionlessLattice +from qlbm.lattice import MSLattice from qlbm.lattice.geometry.shapes.block import Block from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import get_time_series from .bounceback_reflection import BounceBackReflectionOperator from .specular_reflection import SpecularReflectionOperator -from .streaming import CollisionlessStreamingOperator, StreamingAncillaPreparation +from .streaming import MSStreamingOperator, StreamingAncillaPreparation class CQLBM(LBMAlgorithm): @@ -25,25 +25,25 @@ class CQLBM(LBMAlgorithm): The algorithm is composed of three steps that are repeated according to a CFL counter: - #. Streaming performed by the :class:`.CollisionlessStreamingOperator` increments or decrements the positions of particles on the grid. - #. :class:`.BounceBackReflectionOperator` and :class:`.SpecularReflectionOperator` reflect the particles that come in contact with :class:`.Block` obstacles encoded in the :class:`.CollisionlessLattice`. + #. Streaming performed by the :class:`.MSStreamingOperator` increments or decrements the positions of particles on the grid. + #. :class:`.BounceBackReflectionOperator` and :class:`.SpecularReflectionOperator` reflect the particles that come in contact with :class:`.Block` obstacles encoded in the :class:`.MSLattice`. #. The :class:`.StreamingAncillaPreparation` resets the state of the ancilla qubits for the next CFL counter substep. ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== """ def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(lattice, logger) - self.lattice: CollisionlessLattice = lattice + self.lattice: MSLattice = lattice self.logger.info(f"Creating circuit {str(self)}...") circuit_creation_start_time = perf_counter_ns() @@ -63,7 +63,7 @@ def create_circuit(self): for velocities_to_increment in time_series: circuit.compose( - CollisionlessStreamingOperator( + MSStreamingOperator( self.lattice, velocities_to_increment, logger=self.logger, diff --git a/qlbm/components/collisionless/primitives.py b/qlbm/components/ms/primitives.py similarity index 90% rename from qlbm/components/collisionless/primitives.py rename to qlbm/components/ms/primitives.py index 77c1400..c816004 100644 --- a/qlbm/components/collisionless/primitives.py +++ b/qlbm/components/ms/primitives.py @@ -10,9 +10,9 @@ from typing_extensions import override from qlbm.components.base import LBMPrimitive -from qlbm.components.collisionless.streaming import SpeedSensitivePhaseShift -from qlbm.lattice import CollisionlessLattice -from qlbm.lattice.geometry.encodings.collisionless import ReflectionResetEdge +from qlbm.components.ms.streaming import SpeedSensitivePhaseShift +from qlbm.lattice import MSLattice +from qlbm.lattice.geometry.encodings.ms import ReflectionResetEdge from qlbm.tools import flatten @@ -24,7 +24,7 @@ class GridMeasurement(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -33,11 +33,11 @@ class GridMeasurement(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import GridMeasurement - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import GridMeasurement + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice({ + lattice = MSLattice({ "lattice": { "dim": { "x": 8, @@ -64,7 +64,7 @@ class GridMeasurement(LBMPrimitive): def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(logger) @@ -97,8 +97,8 @@ def __str__(self) -> str: return f"[Primitive DVGridMeasurement with lattice {self.lattice}]" -class CollisionlessInitialConditions(LBMPrimitive): - """A primitive that creates the quantum circuit to prepare the flow field in its initial conditions. +class MSInitialConditions(LBMPrimitive): + """A primitive that creates the quantum circuit to prepare the flow field in its initial conditions for the :class:`.MSLattice`. The initial conditions create a quantum state spanning half the grid in the x-axis, and the entirety of the y (and z)-axes (if 3D). @@ -107,7 +107,7 @@ class CollisionlessInitialConditions(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -116,11 +116,11 @@ class CollisionlessInitialConditions(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import CollisionlessInitialConditions - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import MSInitialConditions + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice({ + lattice = MSLattice({ "lattice": { "dim": { "x": 8, @@ -142,12 +142,12 @@ class CollisionlessInitialConditions(LBMPrimitive): }) # Draw the initial conditions circuit - CollisionlessInitialConditions(lattice).draw("mpl") + MSInitialConditions(lattice).draw("mpl") """ def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(logger) @@ -183,7 +183,7 @@ def __str__(self) -> str: return f"[Primitive InitialConditions with lattice {self.lattice}]" -class CollisionlessInitialConditions3DSlim(LBMPrimitive): +class MSInitialConditions3DSlim(LBMPrimitive): r""" A primitive that creates the quantum circuit to prepare the flow field in its initial conditions for 3 dimensions. @@ -196,7 +196,7 @@ class CollisionlessInitialConditions3DSlim(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -205,11 +205,11 @@ class CollisionlessInitialConditions3DSlim(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import CollisionlessInitialConditions3DSlim - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import MSInitialConditions3DSlim + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice({ + lattice = MSLattice({ "lattice": { "dim": { "x": 8, @@ -226,12 +226,12 @@ class CollisionlessInitialConditions3DSlim(LBMPrimitive): }) # Draw the initial conditions circuit - CollisionlessInitialConditions3DSlim(lattice).draw("mpl") + MSInitialConditions3DSlim(lattice).draw("mpl") """ def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(logger) @@ -307,7 +307,7 @@ class SpeedSensitiveAdder(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import SpeedSensitiveAdder + from qlbm.components.ms import SpeedSensitiveAdder SpeedSensitiveAdder(4, 1, True).draw("mpl") """ @@ -372,7 +372,7 @@ class Comparator(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import Comparator, ComparatorMode + from qlbm.components.ms import Comparator, ComparatorMode # On a 5 qubit register, compare the number 3 Comparator(num_qubits=5, @@ -463,7 +463,7 @@ class EdgeComparator(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. :attr:`edge` The coordinates of the edge within the grid. ========================= ====================================================================== @@ -473,11 +473,11 @@ class EdgeComparator(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import EdgeComparator - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import EdgeComparator + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice( + lattice = MSLattice( { "lattice": { "dim": {"x": 8, "y": 8, "z": 8}, @@ -493,7 +493,7 @@ class EdgeComparator(LBMPrimitive): def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, edge: ReflectionResetEdge, logger: Logger = getLogger("qlbm"), ) -> None: diff --git a/qlbm/components/collisionless/specular_reflection.py b/qlbm/components/ms/specular_reflection.py similarity index 95% rename from qlbm/components/collisionless/specular_reflection.py rename to qlbm/components/ms/specular_reflection.py index b0bfb07..a5bcfbd 100644 --- a/qlbm/components/collisionless/specular_reflection.py +++ b/qlbm/components/ms/specular_reflection.py @@ -8,15 +8,15 @@ from qiskit.circuit.library import MCMTGate, XGate from typing_extensions import override -from qlbm.components.base import CQLBMOperator, LBMPrimitive -from qlbm.components.collisionless.primitives import ( +from qlbm.components.base import MSOperator, LBMPrimitive +from qlbm.components.ms.primitives import ( Comparator, ComparatorMode, ) from qlbm.lattice import ( - CollisionlessLattice, + MSLattice, ) -from qlbm.lattice.geometry.encodings.collisionless import ( +from qlbm.lattice.geometry.encodings.ms import ( ReflectionPoint, ReflectionResetEdge, ReflectionWall, @@ -39,7 +39,7 @@ class SpecularWallComparator(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`wall` The :class:`.ReflectionWall` encoding the range spanned by the wall. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -49,11 +49,11 @@ class SpecularWallComparator(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import SpecularWallComparator - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import SpecularWallComparator + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice( + lattice = MSLattice( { "lattice": {"dim": {"x": 8, "y": 8}, "velocities": {"x": 4, "y": 4}}, "geometry": [{"shape":"cuboid", "x": [5, 6], "y": [1, 2], "boundary": "specular"}], @@ -68,7 +68,7 @@ class SpecularWallComparator(LBMPrimitive): def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, wall: ReflectionWall, logger: Logger = getLogger("qlbm"), ) -> None: @@ -128,7 +128,7 @@ def __str__(self) -> str: return f"[Primitive SpecularWallComparator on wall={self.wall}]" -class SpecularReflectionOperator(CQLBMOperator): +class SpecularReflectionOperator(MSOperator): r""" Operator implementing the 2D and 3D Specular Reflection (SR) boundary conditions as described :cite:t:`collisionless`. @@ -145,7 +145,7 @@ class SpecularReflectionOperator(CQLBMOperator): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`blocks` A list of :class:`.Block` objects for which to generate the BB boundary condition circuits. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -155,11 +155,11 @@ class SpecularReflectionOperator(CQLBMOperator): .. plot:: :include-source: - from qlbm.components.collisionless import SpecularReflectionOperator - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import SpecularReflectionOperator + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice( + lattice = MSLattice( { "lattice": {"dim": {"x": 8, "y": 8}, "velocities": {"x": 4, "y": 4}}, "geometry": [{"shape":"cuboid", "x": [5, 6], "y": [1, 2], "boundary": "specular"}], @@ -171,7 +171,7 @@ class SpecularReflectionOperator(CQLBMOperator): def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, blocks: List[Block], logger: Logger = getLogger("qlbm"), ) -> None: @@ -490,7 +490,7 @@ def flip_and_stream( ): """Flips the velocity direction qubit controlled on the ancilla obstacle qubit, before performing streaming. - Unlike in the regular :class:`.CollisionlessStreamingOperator`, the :class:`.ControlledIncrementer` + Unlike in the regular :class:`.MSStreamingOperator`, the :class:`.ControlledIncrementer` phase shift circuit is additionally controlled on the ancilla obstacle qubit of the streaming dimension, which ensures that only particles whose grid position gets incremented (decremented) are those that have streamed inside the solid domain in this CFL time step. diff --git a/qlbm/components/collisionless/streaming.py b/qlbm/components/ms/streaming.py similarity index 90% rename from qlbm/components/collisionless/streaming.py rename to qlbm/components/ms/streaming.py index 8e58210..898556e 100644 --- a/qlbm/components/collisionless/streaming.py +++ b/qlbm/components/ms/streaming.py @@ -11,14 +11,14 @@ from qiskit.synthesis import synth_qft_full as QFT from typing_extensions import override -from qlbm.components.base import CQLBMOperator, LBMPrimitive -from qlbm.lattice import CollisionlessLattice +from qlbm.components.base import MSOperator, LBMPrimitive +from qlbm.lattice import MSLattice from qlbm.tools import CircuitException, bit_value class StreamingAncillaPreparation(LBMPrimitive): r""" - A primitive used in :class:`.CollisionlessStreamingOperator` that implements the preparatory step of streaming necessary for the :class:`.CQLBM` method. + A primitive used in :class:`.MSStreamingOperator` that implements the preparatory step of streaming necessary for the :class:`.CQLBM` method. This operator sets the ancilla qubits to :math:`\ket{1}` for the velocities that will be streamed in the next CFL time step. @@ -26,7 +26,7 @@ class StreamingAncillaPreparation(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`velocities` The velocities that need to be streamed within the next time step. :attr:`dim` The dimension to which the velocities correspond. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. @@ -37,11 +37,11 @@ class StreamingAncillaPreparation(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import StreamingAncillaPreparation - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import StreamingAncillaPreparation + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice( + lattice = MSLattice( { "lattice": {"dim": {"x": 8, "y": 8}, "velocities": {"x": 4, "y": 4}}, "geometry": [], @@ -54,7 +54,7 @@ class StreamingAncillaPreparation(LBMPrimitive): def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, velocities: List[int], dim: int, logger: Logger = getLogger("qlbm"), @@ -114,14 +114,14 @@ def __str__(self) -> str: class ControlledIncrementer(LBMPrimitive): r""" - A primitive used in :class:`.CollisionlessStreamingOperator` that implements the streaming operation on the states for which the ancilla qubits are in the state :math:`\ket{1}`. + A primitive used in :class:`.MSStreamingOperator` that implements the streaming operation on the states for which the ancilla qubits are in the state :math:`\ket{1}`. This primitive is applied after the primitive :class:`.StreamingAncillaPreparation` to compose the streaming operator. ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`reflection` The reflection attribute decides the type of reflection that will take place. This should be either "specular", "bounceback", or ``None``, and defaults to None. This parameter governs which qubits are used as controls for the Fourier space phase shifts. @@ -133,11 +133,11 @@ class ControlledIncrementer(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import ControlledIncrementer - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import ControlledIncrementer + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice( + lattice = MSLattice( { "lattice": {"dim": {"x": 8, "y": 8}, "velocities": {"x": 4, "y": 4}}, "geometry": [], @@ -152,7 +152,7 @@ class ControlledIncrementer(LBMPrimitive): def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, reflection: str | None = None, logger: Logger = getLogger("qlbm"), ) -> None: @@ -239,7 +239,7 @@ def __str__(self) -> str: return f"[Primitive ControlledIncrementer with reflection {self.reflection}]" -class CollisionlessStreamingOperator(CQLBMOperator): +class MSStreamingOperator(MSOperator): """An operator that performs streaming in Fourier space as part of the :class:`.CQLBM` algorithm. Streaming is broken down into the following steps: @@ -253,7 +253,7 @@ class CollisionlessStreamingOperator(CQLBMOperator): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. :attr:`velocities` A list of velocities to increment. This is computed according to CFL counter. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -263,11 +263,11 @@ class CollisionlessStreamingOperator(CQLBMOperator): .. plot:: :include-source: - from qlbm.components.collisionless import CollisionlessStreamingOperator - from qlbm.lattice import CollisionlessLattice + from qlbm.components.ms import MSStreamingOperator + from qlbm.lattice import MSLattice # Build an example lattice - lattice = CollisionlessLattice( + lattice = MSLattice( { "lattice": {"dim": {"x": 8, "y": 8}, "velocities": {"x": 4, "y": 4}}, "geometry": [], @@ -275,14 +275,14 @@ class CollisionlessStreamingOperator(CQLBMOperator): ) # Streaming the velocity with index 2 - CollisionlessStreamingOperator(lattice=lattice, velocities=[2]).draw("mpl") + MSStreamingOperator(lattice=lattice, velocities=[2]).draw("mpl") """ circuit: QuantumCircuit def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, velocities: List[int], logger: Logger = getLogger("qlbm"), ) -> None: @@ -330,7 +330,7 @@ def __str__(self) -> str: class PhaseShift(LBMPrimitive): r""" - A primitive that applies the phase-shift as part of the :class:`.ControlledIncrementer` used in the :class:`.CollisionlessStreamingOperator`. + A primitive that applies the phase-shift as part of the :class:`.ControlledIncrementer` used in the :class:`.MSStreamingOperator`. The rotation applied is :math:`\pm\frac{\pi}{2^{n_q - 1 - j}}`, with :math:`j` the position of the qubit (indexed starting with 0). For an in-depth mathematical explanation of the procedure, consult Section 4 of :cite:t:`collisionless`. @@ -350,7 +350,7 @@ class PhaseShift(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import PhaseShift + from qlbm.components.ms import PhaseShift # A phase shift of 5 qubits PhaseShift(num_qubits=5, positive=False).draw("mpl") @@ -414,7 +414,7 @@ class SpeedSensitivePhaseShift(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.collisionless import SpeedSensitivePhaseShift + from qlbm.components.ms import SpeedSensitivePhaseShift # A phase shift of 5 qubits, controlled on speed index 2 SpeedSensitivePhaseShift(num_qubits=5, speed=2, positive=True).draw("mpl") diff --git a/qlbm/components/spacetime/initial/volumetric.py b/qlbm/components/spacetime/initial/volumetric.py index 9d28bc8..b8f8868 100644 --- a/qlbm/components/spacetime/initial/volumetric.py +++ b/qlbm/components/spacetime/initial/volumetric.py @@ -9,7 +9,7 @@ from typing_extensions import override from qlbm.components.base import LBMPrimitive -from qlbm.components.collisionless.primitives import Comparator, ComparatorMode +from qlbm.components.ms.primitives import Comparator, ComparatorMode from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice from qlbm.tools.utils import flatten diff --git a/qlbm/components/spacetime/reflection/volumetric.py b/qlbm/components/spacetime/reflection/volumetric.py index 5f01686..f0bcd86 100644 --- a/qlbm/components/spacetime/reflection/volumetric.py +++ b/qlbm/components/spacetime/reflection/volumetric.py @@ -8,7 +8,7 @@ from typing_extensions import override from qlbm.components.base import SpaceTimeOperator -from qlbm.components.collisionless.primitives import Comparator, ComparatorMode +from qlbm.components.ms.primitives import Comparator, ComparatorMode from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice from qlbm.tools.exceptions import CircuitException diff --git a/qlbm/infra/__init__.py b/qlbm/infra/__init__.py index f7f9839..3c32022 100644 --- a/qlbm/infra/__init__.py +++ b/qlbm/infra/__init__.py @@ -6,14 +6,14 @@ """ from .compiler import CircuitCompiler -from .result import CollisionlessResult, SpaceTimeResult +from .result import AmplitudeResult, SpaceTimeResult from .runner import CircuitRunner, QiskitRunner, SimulationConfig __all__ = [ "CircuitCompiler", "CircuitRunner", # "MPIQulacsRunner", - "CollisionlessResult", + "AmplitudeResult", "SpaceTimeResult", "QiskitRunner", "SimulationConfig", diff --git a/qlbm/infra/result/__init__.py b/qlbm/infra/result/__init__.py index 78cfce1..2f2a3f6 100644 --- a/qlbm/infra/result/__init__.py +++ b/qlbm/infra/result/__init__.py @@ -1,8 +1,8 @@ """Result objects for processing measurement data into visualizations.""" +from .amplitude_result import AmplitudeResult from .base import QBMResult -from .collisionless_result import CollisionlessResult from .lqlga_result import LQLGAResult from .spacetime_result import SpaceTimeResult -__all__ = ["QBMResult", "CollisionlessResult", "SpaceTimeResult", "LQLGAResult"] +__all__ = ["QBMResult", "AmplitudeResult", "SpaceTimeResult", "LQLGAResult"] diff --git a/qlbm/infra/result/collisionless_result.py b/qlbm/infra/result/amplitude_result.py similarity index 93% rename from qlbm/infra/result/collisionless_result.py rename to qlbm/infra/result/amplitude_result.py index a4dd2b8..1c600a2 100644 --- a/qlbm/infra/result/collisionless_result.py +++ b/qlbm/infra/result/amplitude_result.py @@ -9,22 +9,21 @@ from typing_extensions import override from vtkmodules.util import numpy_support -from qlbm.lattice import CollisionlessLattice -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.base import AmplitudeLattice from .base import QBMResult -class CollisionlessResult(QBMResult): +class AmplitudeResult(QBMResult): """ - :class:`.CQLBM`-specific implementation of the :class:`.QBMResult`. + Implementation of the :class:`.QBMResult` for lattices inheriting from :class:`.AmplitudeLattice`. Processes counts sampled from :class:`.GridMeasurement` primitives. =========================== ====================================================================== Attribute Summary =========================== ====================================================================== - :attr:`lattice` The :class:`.CollisionlessLattice` of the simulated system. + :attr:`lattice` The :class:`.AmplitudeLattice` of the simulated system. :attr:`directory` The directory to which the results outputs data to. :attr:`output_file_name` The root name for files containing time step artifacts, by default "step". =========================== ====================================================================== @@ -39,12 +38,12 @@ class CollisionlessResult(QBMResult): output_file_name: str """The name of the file to output the artifacts to.""" - lattice: CollisionlessLattice | ABLattice + lattice: AmplitudeLattice """The lattice the result corresponds to.""" def __init__( self, - lattice: CollisionlessLattice | ABLattice, + lattice: AmplitudeLattice, directory: str, output_file_name: str = "step", ) -> None: diff --git a/qlbm/infra/runner/base.py b/qlbm/infra/runner/base.py index f13fa6c..62d39c3 100644 --- a/qlbm/infra/runner/base.py +++ b/qlbm/infra/runner/base.py @@ -15,12 +15,12 @@ ) from qlbm.infra.reinitialize.identity_reinitializer import IdentityReinitializer from qlbm.infra.result import ( - CollisionlessResult, + AmplitudeResult, LQLGAResult, QBMResult, SpaceTimeResult, ) -from qlbm.lattice import CollisionlessLattice, Lattice +from qlbm.lattice import MSLattice, Lattice from qlbm.lattice.lattices.abe_lattice import ABLattice from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice @@ -124,10 +124,10 @@ def new_result(self, output_directory: str, output_file_name: str) -> QBMResult: ResultsException If there is no matching result object for the runner's lattice. """ - if isinstance(self.lattice, CollisionlessLattice) or isinstance( + if isinstance(self.lattice, MSLattice) or isinstance( self.lattice, ABLattice ): - return CollisionlessResult( + return AmplitudeResult( self.lattice, # type: ignore output_directory, output_file_name, @@ -158,7 +158,7 @@ def new_reinitializer(self) -> Reinitializer: If the underlying algorithm does not support reinitialization. """ if ( - isinstance(self.lattice, CollisionlessLattice) + isinstance(self.lattice, MSLattice) or isinstance(self.lattice, LQLGALattice) or isinstance(self.lattice, ABLattice) ): diff --git a/qlbm/infra/runner/mpi_qulacs.py b/qlbm/infra/runner/mpi_qulacs.py index 16a814d..0f59797 100644 --- a/qlbm/infra/runner/mpi_qulacs.py +++ b/qlbm/infra/runner/mpi_qulacs.py @@ -19,8 +19,8 @@ from qlbm.components.base import LBMPrimitive from qlbm.infra.compiler import CircuitCompiler -from qlbm.infra.result import CollisionlessResult -from qlbm.lattice import CollisionlessLattice +from qlbm.infra.result import AmplitudeResult +from qlbm.lattice import MSLattice from qlbm.tools.utils import get_circuit_properties, qiskit_to_qulacs @@ -29,7 +29,7 @@ class MPIQulacsRunner: def __init__( self, - lattice: CollisionlessLattice, + lattice: MSLattice, output_directory: str, output_file_name: str = "step", sampling_backend: AerSimulator = QasmSimulator(), @@ -52,7 +52,7 @@ def run( num_shots: int, snapshot_execution: bool = False, statevector_sampling: bool = False, - ) -> CollisionlessResult: + ) -> AmplitudeResult: """Simualtes the provided algorithm configuration. Parameters @@ -76,7 +76,7 @@ def run( Returns ------- - CollisionlessResult + AmplitudeResult The result of the simulation. """ measurement = CircuitCompiler("QISKIT", "QISKIT").compile( @@ -104,7 +104,7 @@ def run_mpi_qulacs( num_shots: int, snapshot_execution: bool = False, statevector_sampling: bool = False, - ) -> CollisionlessResult: + ) -> AmplitudeResult: """ Simualtes the provided algorithm configuration. @@ -129,10 +129,10 @@ def run_mpi_qulacs( Returns ------- - CollisionlessResult + AmplitudeResult The result of the simulation. """ - simulation_result = CollisionlessResult( + simulation_result = AmplitudeResult( self.lattice, self.output_directory, self.output_file_name ) diff --git a/qlbm/infra/runner/simulation_config.py b/qlbm/infra/runner/simulation_config.py index 00d4dde..0c96262 100644 --- a/qlbm/infra/runner/simulation_config.py +++ b/qlbm/infra/runner/simulation_config.py @@ -35,7 +35,7 @@ class SimulationConfig: * - Attribute - Description * - :attr:`initial_conditions` - - The initial conditions of the simulations. For example, :class:`.CollisionlessInitialConditions` or :class:`.PointWiseSpaceTimeInitialConditions`. + - The initial conditions of the simulations. For example, :class:`.MSInitialConditions` or :class:`.PointWiseSpaceTimeInitialConditions`. * - :attr:`algorithm` - The algorithm that performs the QLBM time step computation. For example, :class:`.CQLBM` or :class:`.SpaceTimeQLBM`. * - :attr:`postprocessing` @@ -132,7 +132,7 @@ class SimulationConfig: .. code-block:: python cfg = SimulationConfig( - initial_conditions=CollisionlessInitialConditions(lattice, logger), + initial_conditions=MSInitialConditions(lattice, logger), algorithm=CQLBM(lattice, logger), postprocessing=EmptyPrimitive(lattice, logger), measurement=GridMeasurement(lattice, logger), diff --git a/qlbm/lattice/__init__.py b/qlbm/lattice/__init__.py index e48ab5c..effdc62 100644 --- a/qlbm/lattice/__init__.py +++ b/qlbm/lattice/__init__.py @@ -1,6 +1,6 @@ """Lattice and Block utilitites.""" -from .geometry.encodings.collisionless import ( +from .geometry.encodings.ms import ( DimensionalReflectionData, ReflectionPoint, ReflectionResetEdge, @@ -12,7 +12,7 @@ from .geometry.shapes.circle import ( Circle, ) -from .lattices import CollisionlessLattice, Lattice +from .lattices import MSLattice, Lattice from .lattices.abe_lattice import ABLattice from .lattices.lqlga_lattice import LQLGALattice from .lattices.spacetime_lattice import SpaceTimeLattice @@ -24,7 +24,7 @@ __all__ = [ "Lattice", "ABLattice", - "CollisionlessLattice", + "MSLattice", "SpaceTimeLattice", "LQLGALattice", "DimensionalReflectionData", diff --git a/qlbm/lattice/geometry/encodings/__init__.py b/qlbm/lattice/geometry/encodings/__init__.py index 462e32c..b0400f2 100644 --- a/qlbm/lattice/geometry/encodings/__init__.py +++ b/qlbm/lattice/geometry/encodings/__init__.py @@ -1,6 +1,6 @@ """Algorithm-specific geometrical data encodings.""" -from .collisionless import ( +from .ms import ( DimensionalReflectionData, ReflectionPoint, ReflectionResetEdge, diff --git a/qlbm/lattice/geometry/encodings/collisionless.py b/qlbm/lattice/geometry/encodings/ms.py similarity index 100% rename from qlbm/lattice/geometry/encodings/collisionless.py rename to qlbm/lattice/geometry/encodings/ms.py diff --git a/qlbm/lattice/geometry/shapes/block.py b/qlbm/lattice/geometry/shapes/block.py index c8c67ea..7782c90 100644 --- a/qlbm/lattice/geometry/shapes/block.py +++ b/qlbm/lattice/geometry/shapes/block.py @@ -8,7 +8,7 @@ import numpy as np from stl import mesh -from qlbm.lattice.geometry.encodings.collisionless import ( +from qlbm.lattice.geometry.encodings.ms import ( DimensionalReflectionData, ReflectionPoint, ReflectionResetEdge, diff --git a/qlbm/lattice/lattices/__init__.py b/qlbm/lattice/lattices/__init__.py index 7a92929..6609f5b 100644 --- a/qlbm/lattice/lattices/__init__.py +++ b/qlbm/lattice/lattices/__init__.py @@ -1,8 +1,8 @@ """Base Lattice class and algorithm-specific implementations.""" from .base import Lattice -from .collisionless_lattice import CollisionlessLattice +from .ms_lattice import MSLattice from .lqlga_lattice import LQLGALattice from .spacetime_lattice import SpaceTimeLattice -__all__ = ["Lattice", "CollisionlessLattice", "SpaceTimeLattice", "LQLGALattice"] +__all__ = ["Lattice", "MSLattice", "SpaceTimeLattice", "LQLGALattice"] diff --git a/qlbm/lattice/lattices/collisionless_lattice.py b/qlbm/lattice/lattices/ms_lattice.py similarity index 99% rename from qlbm/lattice/lattices/collisionless_lattice.py rename to qlbm/lattice/lattices/ms_lattice.py index f338686..9c0ae86 100644 --- a/qlbm/lattice/lattices/collisionless_lattice.py +++ b/qlbm/lattice/lattices/ms_lattice.py @@ -13,7 +13,7 @@ from .base import AmplitudeLattice -class CollisionlessLattice(AmplitudeLattice): +class MSLattice(AmplitudeLattice): r""" Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.CQLBM` algorithm developed by :cite:t:`collisionless`. @@ -132,9 +132,9 @@ class CollisionlessLattice(AmplitudeLattice): .. plot:: :include-source: - from qlbm.lattice import CollisionlessLattice + from qlbm.lattice import MSLattice - CollisionlessLattice( + MSLattice( { "lattice": {"dim": {"x": 8, "y": 8}, "velocities": {"x": 4, "y": 4}}, "geometry": [{"shape":"cuboid", "x": [5, 6], "y": [1, 2], "boundary": "bounceback"}], diff --git a/test/integration/compiler_test.py b/test/integration/compiler_test.py index 39ef697..be11dac 100644 --- a/test/integration/compiler_test.py +++ b/test/integration/compiler_test.py @@ -5,22 +5,22 @@ from qiskit_aer import AerSimulator from qulacs import QuantumCircuit as QulacsQC -from qlbm.components import CollisionlessStreamingOperator +from qlbm.components import MSStreamingOperator from qlbm.infra import ( CircuitCompiler, ) -from qlbm.lattice import CollisionlessLattice +from qlbm.lattice import MSLattice from qlbm.tools.utils import get_time_series @pytest.fixture def lattice_asymmetric_medium_3d(): - return CollisionlessLattice("test/resources/asymmetric_3d_no_obstacles.json") + return MSLattice("test/resources/asymmetric_3d_no_obstacles.json") @pytest.fixture def lattice_symmetric_small_2d(): - return CollisionlessLattice("test/resources/symmetric_2d_no_obstacles.json") + return MSLattice("test/resources/symmetric_2d_no_obstacles.json") @pytest.mark.parametrize( @@ -43,7 +43,7 @@ def test_qiskit_target_compilation( lattice.num_velocities[0] + 1 ) # +1 because velocities are between 0 and n_vi velocities = get_time_series(num_velocities)[velocity] - op = CollisionlessStreamingOperator(lattice, velocities) + op = MSStreamingOperator(lattice, velocities) compiler = CircuitCompiler(compiler_platform, "QISKIT") compiled_circuit = compiler.compile(op, backend, 0) @@ -69,7 +69,7 @@ def test_qiskit_target_compilation( # lattice.num_velocities[0] + 1 # ) # +1 because velocities are between 0 and n_vi # velocities = get_time_series(num_velocities)[velocity] -# op = CollisionlessStreamingOperator(lattice, velocities) +# op = MSStreamingOperator(lattice, velocities) # compiler = CircuitCompiler(compiler_platform, "QULACS") # # Qulacs backend is determined automatically diff --git a/test/integration/execution_test.py b/test/integration/execution_test.py index 23218c2..55c6f3a 100644 --- a/test/integration/execution_test.py +++ b/test/integration/execution_test.py @@ -1,9 +1,9 @@ import pytest from qiskit_aer import AerSimulator -from qlbm.components.collisionless import ( +from qlbm.components.ms import ( CQLBM, - CollisionlessInitialConditions, + MSInitialConditions, GridMeasurement, ) from qlbm.components.common import EmptyPrimitive @@ -16,7 +16,7 @@ ) from qlbm.infra.runner import QiskitRunner from qlbm.infra.runner.simulation_config import SimulationConfig -from qlbm.lattice import CollisionlessLattice +from qlbm.lattice import MSLattice from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice OUTPUT_DIR = "test/artifacts" @@ -24,10 +24,10 @@ @pytest.fixture def collisionless_circuits(): - lattice = CollisionlessLattice("test/resources/symmetric_2d_1_obstacle.json") + lattice = MSLattice("test/resources/symmetric_2d_1_obstacle.json") return { - "initial_conditions": CollisionlessInitialConditions(lattice), + "initial_conditions": MSInitialConditions(lattice), "algorithm": CQLBM(lattice), "postprocessing": EmptyPrimitive(lattice), "measurement": GridMeasurement(lattice), diff --git a/test/integration/simulation_config_test.py b/test/integration/simulation_config_test.py index e720aed..293d236 100644 --- a/test/integration/simulation_config_test.py +++ b/test/integration/simulation_config_test.py @@ -4,23 +4,23 @@ from qiskit import QuantumCircuit as QiskitQC from qiskit_aer import AerSimulator -from qlbm.components.collisionless import ( +from qlbm.components.ms import ( CQLBM, - CollisionlessInitialConditions, + MSInitialConditions, GridMeasurement, ) from qlbm.components.common import EmptyPrimitive from qlbm.infra.runner.simulation_config import SimulationConfig -from qlbm.lattice import CollisionlessLattice +from qlbm.lattice import MSLattice from qlbm.tools.exceptions import ExecutionException @pytest.fixture def symmetric_2d_no_osbtacle_circuits(): - lattice = CollisionlessLattice("test/resources/symmetric_2d_no_obstacles.json") + lattice = MSLattice("test/resources/symmetric_2d_no_obstacles.json") return { - "initial_conditions": CollisionlessInitialConditions(lattice), + "initial_conditions": MSInitialConditions(lattice), "algorithm": CQLBM(lattice), "postprocessing": EmptyPrimitive(lattice), "measurement": GridMeasurement(lattice), @@ -29,10 +29,10 @@ def symmetric_2d_no_osbtacle_circuits(): @pytest.fixture def symmetric_2d_one_osbtacle_circuits(): - lattice = CollisionlessLattice("test/resources/symmetric_2d_1_obstacle.json") + lattice = MSLattice("test/resources/symmetric_2d_1_obstacle.json") return { - "initial_conditions": CollisionlessInitialConditions(lattice), + "initial_conditions": MSInitialConditions(lattice), "algorithm": CQLBM(lattice), "postprocessing": EmptyPrimitive(lattice), "measurement": GridMeasurement(lattice), diff --git a/test/unit/abe/__init__.py b/test/unit/ab/__init__.py similarity index 100% rename from test/unit/abe/__init__.py rename to test/unit/ab/__init__.py diff --git a/test/unit/abe/abe_reflection_permutation_test.py b/test/unit/ab/ab_reflection_permutation_test.py similarity index 94% rename from test/unit/abe/abe_reflection_permutation_test.py rename to test/unit/ab/ab_reflection_permutation_test.py index e549806..4b4b1db 100644 --- a/test/unit/abe/abe_reflection_permutation_test.py +++ b/test/unit/ab/ab_reflection_permutation_test.py @@ -2,7 +2,7 @@ from qiskit import QuantumCircuit, transpile from qiskit_aer import AerSimulator -from qlbm.components.abe.reflection import ABEReflectionPermutation +from qlbm.components.ab.reflection import ABEReflectionPermutation from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.utils import bit_value, get_qubits_to_invert diff --git a/test/unit/blocks_2d_test.py b/test/unit/blocks_2d_test.py index 1888ae3..c862add 100644 --- a/test/unit/blocks_2d_test.py +++ b/test/unit/blocks_2d_test.py @@ -2,7 +2,7 @@ import pytest -from qlbm.lattice.geometry.encodings.collisionless import ReflectionPoint +from qlbm.lattice.geometry.encodings.ms import ReflectionPoint from qlbm.lattice.geometry.shapes.block import Block from qlbm.tools.utils import flatten from test.regression.blocks import Block2D as RegressionBlock2D diff --git a/test/unit/blocks_3d_test.py b/test/unit/blocks_3d_test.py index d3353eb..89adb4b 100644 --- a/test/unit/blocks_3d_test.py +++ b/test/unit/blocks_3d_test.py @@ -2,7 +2,7 @@ import pytest -from qlbm.lattice.geometry.encodings.collisionless import ReflectionWall +from qlbm.lattice.geometry.encodings.ms import ReflectionWall from qlbm.lattice.geometry.shapes.block import Block diff --git a/test/unit/collisionles_lattice_test.py b/test/unit/collisionles_lattice_test.py index f69fa66..fd4d850 100644 --- a/test/unit/collisionles_lattice_test.py +++ b/test/unit/collisionles_lattice_test.py @@ -1,12 +1,12 @@ import pytest -from qlbm.lattice import CollisionlessLattice +from qlbm.lattice import MSLattice from qlbm.tools.exceptions import LatticeException @pytest.fixture -def lattice_2d_16x16_1_obstacle() -> CollisionlessLattice: - return CollisionlessLattice( +def lattice_2d_16x16_1_obstacle() -> MSLattice: + return MSLattice( { "lattice": { "dim": {"x": 16, "y": 16}, @@ -20,8 +20,8 @@ def lattice_2d_16x16_1_obstacle() -> CollisionlessLattice: @pytest.fixture -def lattice_2d_16x16_1_obstacle_asymmetric() -> CollisionlessLattice: - return CollisionlessLattice( +def lattice_2d_16x16_1_obstacle_asymmetric() -> MSLattice: + return MSLattice( { "lattice": { "dim": {"x": 16, "y": 64}, @@ -35,8 +35,8 @@ def lattice_2d_16x16_1_obstacle_asymmetric() -> CollisionlessLattice: @pytest.fixture -def lattice_2d_16x16_1_obstacle_bounceback() -> CollisionlessLattice: - return CollisionlessLattice( +def lattice_2d_16x16_1_obstacle_bounceback() -> MSLattice: + return MSLattice( { "lattice": { "dim": {"x": 16, "y": 16}, @@ -55,8 +55,8 @@ def lattice_2d_16x16_1_obstacle_bounceback() -> CollisionlessLattice: @pytest.fixture -def lattice_2d_16x16_2_obstacle_mixed() -> CollisionlessLattice: - return CollisionlessLattice( +def lattice_2d_16x16_2_obstacle_mixed() -> MSLattice: + return MSLattice( { "lattice": { "dim": {"x": 16, "y": 16}, @@ -76,8 +76,8 @@ def lattice_2d_16x16_2_obstacle_mixed() -> CollisionlessLattice: @pytest.fixture -def lattice_3d_8x8x8_2_obstacle_mixed() -> CollisionlessLattice: - return CollisionlessLattice( +def lattice_3d_8x8x8_2_obstacle_mixed() -> MSLattice: + return MSLattice( { "lattice": { "dim": {"x": 8, "y": 8, "z": 8}, @@ -104,8 +104,8 @@ def lattice_3d_8x8x8_2_obstacle_mixed() -> CollisionlessLattice: @pytest.fixture -def lattice_3d_8x8x8_1_obstacle_bounceback() -> CollisionlessLattice: - return CollisionlessLattice( +def lattice_3d_8x8x8_1_obstacle_bounceback() -> MSLattice: + return MSLattice( { "lattice": { "dim": {"x": 8, "y": 8, "z": 8}, @@ -126,21 +126,21 @@ def lattice_3d_8x8x8_1_obstacle_bounceback() -> CollisionlessLattice: def test_lattice_exception_empty_dict(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice({}) + MSLattice({}) assert 'Input configuration missing "lattice" properties.' == str(excinfo.value) def test_lattice_exception_no_dims(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice({"lattice": {}}) + MSLattice({"lattice": {}}) assert 'Lattice configuration missing "dim" properties.' == str(excinfo.value) def test_lattice_exception_no_velocities(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice({"lattice": {"dim": {}}}) + MSLattice({"lattice": {"dim": {}}}) assert 'Lattice configuration missing "velocities" properties.' == str( excinfo.value @@ -149,14 +149,14 @@ def test_lattice_exception_no_velocities(): def test_lattice_exception_mismatched_velocities_and_dims(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice({"lattice": {"dim": {"x": 64}, "velocities": [4, 4]}}) + MSLattice({"lattice": {"dim": {"x": 64}, "velocities": [4, 4]}}) assert "Lattice configuration dimensionality is inconsistent." == str(excinfo.value) def test_lattice_exception_mismatched_too_many_dimensions(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64, "z": 128, "w": 64}, @@ -173,7 +173,7 @@ def test_lattice_exception_mismatched_too_many_dimensions(): def test_lattice_exception_mismatched_bad_dimensions(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 127}, @@ -190,7 +190,7 @@ def test_lattice_exception_mismatched_bad_dimensions(): def test_lattice_exception_mismatched_bad_velocities(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -207,7 +207,7 @@ def test_lattice_exception_mismatched_bad_velocities(): def test_lattice_exception_mismatched_bad_object_dimensions(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -232,7 +232,7 @@ def test_lattice_exception_mismatched_bad_object_dimensions(): def test_lattice_exception_mismatched_bad_object_bound_specification(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -254,7 +254,7 @@ def test_lattice_exception_mismatched_bad_object_bound_specification(): def test_lattice_exception_mismatched_bad_object_bound_decreasing(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -276,7 +276,7 @@ def test_lattice_exception_mismatched_bad_object_bound_decreasing(): def test_lattice_exception_mismatched_bad_object_out_of_bounds(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -296,7 +296,7 @@ def test_lattice_exception_mismatched_bad_object_out_of_bounds(): assert "Obstacle 1 is out of bounds in the x-dimension." == str(excinfo.value) with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -318,7 +318,7 @@ def test_lattice_exception_mismatched_bad_object_out_of_bounds(): def test_lattice_exception_bad_object_boundary_conditions(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -342,7 +342,7 @@ def test_lattice_exception_bad_object_boundary_conditions(): def test_lattice_exception_no_object_boundary_conditions(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -364,7 +364,7 @@ def test_lattice_exception_no_object_boundary_conditions(): def test_lattice_exception_missing_shape(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -381,7 +381,7 @@ def test_lattice_exception_missing_shape(): def test_lattice_exception_unsupported_shape(): with pytest.raises(LatticeException) as excinfo: - CollisionlessLattice( + MSLattice( { "lattice": { "dim": {"x": 64, "y": 64}, @@ -404,7 +404,7 @@ def test_lattice_exception_unsupported_shape(): ) -def test_2d_lattice_basic_properties(lattice_2d_16x16_1_obstacle: CollisionlessLattice): +def test_2d_lattice_basic_properties(lattice_2d_16x16_1_obstacle: MSLattice): assert lattice_2d_16x16_1_obstacle.num_dims == 2 assert lattice_2d_16x16_1_obstacle.num_gridpoints == [15, 15] assert lattice_2d_16x16_1_obstacle.num_velocities == [3, 3] @@ -415,7 +415,7 @@ def test_2d_lattice_basic_properties(lattice_2d_16x16_1_obstacle: CollisionlessL def test_2d_lattice_basic_qubit_register_size( - lattice_2d_16x16_1_obstacle: CollisionlessLattice, + lattice_2d_16x16_1_obstacle: MSLattice, ): # Ancilla velocity (1r2q) # ancilla obstacle (1r2q) @@ -428,7 +428,7 @@ def test_2d_lattice_basic_qubit_register_size( def test_2d_lattice_ancilla_velocity_register( - lattice_2d_16x16_1_obstacle: CollisionlessLattice, + lattice_2d_16x16_1_obstacle: MSLattice, ): # Ancilla velocity (1r2q) # ancilla obstacle (1r2q) @@ -450,7 +450,7 @@ def test_2d_lattice_ancilla_velocity_register( def test_2d_lattice_ancilla_obstacle_register( - lattice_2d_16x16_1_obstacle: CollisionlessLattice, + lattice_2d_16x16_1_obstacle: MSLattice, ): # Ancilla velocity (1r2q) # ancilla obstacle (1r2q) @@ -472,7 +472,7 @@ def test_2d_lattice_ancilla_obstacle_register( def test_2d_lattice_ancilla_comparator_register( - lattice_2d_16x16_1_obstacle: CollisionlessLattice, + lattice_2d_16x16_1_obstacle: MSLattice, ): assert lattice_2d_16x16_1_obstacle.ancillae_comparator_index(0) == [4, 5] assert lattice_2d_16x16_1_obstacle.ancillae_comparator_index() == [4, 5] @@ -485,7 +485,7 @@ def test_2d_lattice_ancilla_comparator_register( ) -def test_2d_lattice_grid_register(lattice_2d_16x16_1_obstacle: CollisionlessLattice): +def test_2d_lattice_grid_register(lattice_2d_16x16_1_obstacle: MSLattice): assert lattice_2d_16x16_1_obstacle.grid_index(0) == [6, 7, 8, 9] assert lattice_2d_16x16_1_obstacle.grid_index(1) == [10, 11, 12, 13] assert lattice_2d_16x16_1_obstacle.grid_index() == list(range(6, 14)) @@ -499,7 +499,7 @@ def test_2d_lattice_grid_register(lattice_2d_16x16_1_obstacle: CollisionlessLatt def test_2d_lattice_velocity_register( - lattice_2d_16x16_1_obstacle: CollisionlessLattice, + lattice_2d_16x16_1_obstacle: MSLattice, ): assert lattice_2d_16x16_1_obstacle.velocity_index(0) == [14] assert lattice_2d_16x16_1_obstacle.velocity_index(1) == [15] @@ -514,7 +514,7 @@ def test_2d_lattice_velocity_register( def test_2d_lattice_velocity_dir_register( - lattice_2d_16x16_1_obstacle: CollisionlessLattice, + lattice_2d_16x16_1_obstacle: MSLattice, ): assert lattice_2d_16x16_1_obstacle.velocity_dir_index(0) == [16] assert lattice_2d_16x16_1_obstacle.velocity_dir_index(1) == [17] @@ -529,8 +529,8 @@ def test_2d_lattice_velocity_dir_register( def test_2d_asymmetric_lattice_ancialle_registers( - lattice_2d_16x16_1_obstacle: CollisionlessLattice, - lattice_2d_16x16_1_obstacle_asymmetric: CollisionlessLattice, + lattice_2d_16x16_1_obstacle: MSLattice, + lattice_2d_16x16_1_obstacle_asymmetric: MSLattice, ): assert lattice_2d_16x16_1_obstacle_asymmetric.num_gridpoints == [15, 63] assert lattice_2d_16x16_1_obstacle_asymmetric.num_velocities == [15, 3] @@ -563,7 +563,7 @@ def test_2d_asymmetric_lattice_ancialle_registers( def test_2d_asymmetric_lattice_grid_registers( - lattice_2d_16x16_1_obstacle_asymmetric: CollisionlessLattice, + lattice_2d_16x16_1_obstacle_asymmetric: MSLattice, ): assert lattice_2d_16x16_1_obstacle_asymmetric.grid_index(0) == [6, 7, 8, 9] assert lattice_2d_16x16_1_obstacle_asymmetric.grid_index(1) == list(range(10, 16)) @@ -578,7 +578,7 @@ def test_2d_asymmetric_lattice_grid_registers( def test_2d_asymmetric_lattice_velocity_register( - lattice_2d_16x16_1_obstacle_asymmetric: CollisionlessLattice, + lattice_2d_16x16_1_obstacle_asymmetric: MSLattice, ): assert lattice_2d_16x16_1_obstacle_asymmetric.velocity_index(0) == [16, 17, 18] assert lattice_2d_16x16_1_obstacle_asymmetric.velocity_index(1) == [19] @@ -595,7 +595,7 @@ def test_2d_asymmetric_lattice_velocity_register( def test_2d_asymmetric_lattice_velocity_dir_register( - lattice_2d_16x16_1_obstacle_asymmetric: CollisionlessLattice, + lattice_2d_16x16_1_obstacle_asymmetric: MSLattice, ): assert lattice_2d_16x16_1_obstacle_asymmetric.velocity_dir_index(0) == [20] assert lattice_2d_16x16_1_obstacle_asymmetric.velocity_dir_index(1) == [21] diff --git a/test/unit/cqlbm_test.py b/test/unit/cqlbm_test.py index 81388f7..3ce9baa 100644 --- a/test/unit/cqlbm_test.py +++ b/test/unit/cqlbm_test.py @@ -1,12 +1,12 @@ import pytest from qlbm.components import CQLBM -from qlbm.lattice import CollisionlessLattice +from qlbm.lattice import MSLattice @pytest.fixture -def lattice_2d_16x16_1_object() -> CollisionlessLattice: - return CollisionlessLattice( +def lattice_2d_16x16_1_object() -> MSLattice: + return MSLattice( { "lattice": { "dim": {"x": 16, "y": 16}, @@ -19,5 +19,5 @@ def lattice_2d_16x16_1_object() -> CollisionlessLattice: ) -def test_construction(lattice_2d_16x16_1_object: CollisionlessLattice): +def test_construction(lattice_2d_16x16_1_object: MSLattice): CQLBM(lattice=lattice_2d_16x16_1_object) diff --git a/test/unit/incrementer_test.py b/test/unit/incrementer_test.py index 5d012ff..3af3abc 100644 --- a/test/unit/incrementer_test.py +++ b/test/unit/incrementer_test.py @@ -1,17 +1,17 @@ import pytest -from qlbm.components.collisionless.streaming import ControlledIncrementer -from qlbm.lattice import CollisionlessLattice +from qlbm.components.ms.streaming import ControlledIncrementer +from qlbm.lattice import MSLattice @pytest.fixture def lattice_asymmetric_medium_3d(): - return CollisionlessLattice("test/resources/asymmetric_3d_no_obstacles.json") + return MSLattice("test/resources/asymmetric_3d_no_obstacles.json") @pytest.fixture def lattice_symmetric_small_2d(): - return CollisionlessLattice("test/resources/symmetric_2d_no_obstacles.json") + return MSLattice("test/resources/symmetric_2d_no_obstacles.json") def test_3d_incrementer_size(lattice_asymmetric_medium_3d): From f48f6266e64ad5db8cae63800308bd8fffb83b03 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Tue, 4 Nov 2025 20:38:10 +0100 Subject: [PATCH 35/61] Make CQLBM common entrypoint for MSQLBM and ABQLBM --- qlbm/components/__init__.py | 10 ++-- qlbm/components/ab/__init__.py | 4 +- qlbm/components/ab/ab.py | 6 +-- qlbm/components/cqlbm.py | 60 ++++++++++++++++++++++ qlbm/components/ms/__init__.py | 10 ++-- qlbm/components/ms/{cqlbm.py => msqlbm.py} | 10 ++-- qlbm/components/ms/primitives.py | 2 +- qlbm/components/ms/streaming.py | 4 +- qlbm/components/spacetime/__init__.py | 2 +- qlbm/infra/result/amplitude_result.py | 2 +- test/integration/execution_test.py | 8 +-- test/integration/simulation_config_test.py | 10 ++-- 12 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 qlbm/components/cqlbm.py rename qlbm/components/ms/{cqlbm.py => msqlbm.py} (92%) diff --git a/qlbm/components/__init__.py b/qlbm/components/__init__.py index fe3df3e..1c12991 100644 --- a/qlbm/components/__init__.py +++ b/qlbm/components/__init__.py @@ -1,10 +1,10 @@ """Modular and extendible quantum circuits that perform parts of the QLBM algorithm.""" from .base import ( - MSOperator, LBMAlgorithm, LBMOperator, LBMPrimitive, + MSOperator, QuantumComponent, SpaceTimeOperator, ) @@ -15,6 +15,7 @@ EQCRedistribution, HammingWeightAdder, ) +from .cqlbm import CQLBM from .lqlga import ( LQLGA, GenericLQLGACollisionOperator, @@ -25,10 +26,10 @@ LQLGAStreamingOperator, ) from .ms import ( - CQLBM, + MSQLBM, BounceBackReflectionOperator, - MSInitialConditions, GridMeasurement, + MSInitialConditions, MSStreamingOperator, SpecularReflectionOperator, ) @@ -61,10 +62,11 @@ "MSStreamingOperator", "SpecularReflectionOperator", "BounceBackReflectionOperator", - "CQLBM", + "MSQLBM", "GenericLQLGACollisionOperator", "LQGLAInitialConditions", "LQLGA", + "CQLBM", "LQLGAGridVelocityMeasurement", "LQLGAMGReflectionOperator", "LQLGAReflectionOperator", diff --git a/qlbm/components/ab/__init__.py b/qlbm/components/ab/__init__.py index ab7d901..ea1cd17 100644 --- a/qlbm/components/ab/__init__.py +++ b/qlbm/components/ab/__init__.py @@ -1,10 +1,10 @@ -from .ab import ABECQLBM +from .ab import ABQLBM from .initial import ABEInitialConditions from .measurement import ABEGridMeasurement from .streaming import ABEStreamingOperator __all__ = [ - "ABECQLBM", + "ABQLBM", "ABEInitialConditions", "ABEGridMeasurement", "ABEStreamingOperator", diff --git a/qlbm/components/ab/ab.py b/qlbm/components/ab/ab.py index 2040a81..8c0abbd 100644 --- a/qlbm/components/ab/ab.py +++ b/qlbm/components/ab/ab.py @@ -16,7 +16,7 @@ from .streaming import ABEStreamingOperator -class ABECQLBM(LBMAlgorithm): +class ABQLBM(LBMAlgorithm): """TODO.""" def __init__( @@ -55,7 +55,7 @@ def create_circuit(self): for shape in self.lattice.shapes["specular"] ): raise LatticeException( - f"All shapes with the {bc} boundary condition must be cuboids for the CQLBM algorithm. " + f"All shapes with the {bc} boundary condition must be cuboids for the ABQLBM algorithm. " ) circuit.compose( @@ -71,4 +71,4 @@ def create_circuit(self): @override def __str__(self) -> str: - return f"[Algorithm ABECQLBM with lattice {self.lattice}]" + return f"[Algorithm ABQLBM with lattice {self.lattice}]" diff --git a/qlbm/components/cqlbm.py b/qlbm/components/cqlbm.py new file mode 100644 index 0000000..1ec5280 --- /dev/null +++ b/qlbm/components/cqlbm.py @@ -0,0 +1,60 @@ +"""The end-to-end algorithm of the Collisionless Quantum Lattice Boltzmann Algorithm, or Quantum Transport Method first introduced in :cite:t:`collisionless` and later extended in :cite:t:`qmem`. + +This is a common entrypoint that supports implementations based on the :class:`.MSLattice` and :class:`.ABLattice`. + +Implementations can be found in the :class:`MSQLBM` and :class:`.ABQLBM`, respectively. +""" + +from logging import Logger, getLogger +from time import perf_counter_ns +from typing import cast + +from typing_extensions import override + +from qlbm.components.ab.ab import ABQLBM +from qlbm.components.base import LBMAlgorithm +from qlbm.components.ms.msqlbm import MSQLBM +from qlbm.lattice import MSLattice +from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.base import AmplitudeLattice +from qlbm.tools.exceptions import LatticeException + + +class CQLBM(LBMAlgorithm): + """The end-to-end algorithm of the Collisionless Quantum Lattice Boltzmann Algorithm first introduced in :cite:t:`collisionless` and later extended in :cite:t:`qmem`. + + Implementations based on lattices with the DdQq discretization use the :class:`.ABQLBM`. + Implementations where the number of lattices is defined per dimension delegate to the :class:`.MSQLBM`. + + TODO add examples + """ + + def __init__( + self, + lattice: AmplitudeLattice, + logger: Logger = getLogger("qlbm"), + ) -> None: + super().__init__(lattice, logger) + self.lattice: AmplitudeLattice = lattice + + self.logger.info(f"Creating circuit {str(self)}...") + circuit_creation_start_time = perf_counter_ns() + self.circuit = self.create_circuit() + self.logger.info( + f"Creating circuit {str(self)} took {perf_counter_ns() - circuit_creation_start_time} (ns)" + ) + + @override + def create_circuit(self): + if isinstance(self.lattice, MSLattice): + return MSQLBM(cast(MSLattice, self.lattice), self.logger) + elif isinstance(self.lattice, ABLattice): + return ABQLBM(cast(ABLattice, self.lattice), self.logger) + else: + raise LatticeException( + f"CQLBM does not support lattices of type {type(self.lattice)}" + ) + + @override + def __str__(self) -> str: + return f"[Algorithm CQLBM with lattice {self.lattice}]" diff --git a/qlbm/components/ms/__init__.py b/qlbm/components/ms/__init__.py index b0b4a8e..8652182 100644 --- a/qlbm/components/ms/__init__.py +++ b/qlbm/components/ms/__init__.py @@ -1,17 +1,17 @@ -"""Modular qlbm quantum circuit components for the CQLBM algorithm :cite:p:`collisionless`.""" +"""Modular qlbm quantum circuit components for the MSQLBM algorithm :cite:p:`collisionless`.""" from .bounceback_reflection import ( BounceBackReflectionOperator, BounceBackWallComparator, ) -from .cqlbm import CQLBM +from .msqlbm import MSQLBM from .primitives import ( - MSInitialConditions, - MSInitialConditions3DSlim, Comparator, ComparatorMode, EdgeComparator, GridMeasurement, + MSInitialConditions, + MSInitialConditions3DSlim, SpeedSensitiveAdder, ) from .specular_reflection import SpecularReflectionOperator, SpecularWallComparator @@ -40,5 +40,5 @@ "SpecularWallComparator", "BounceBackReflectionOperator", "BounceBackWallComparator", - "CQLBM", + "MSQLBM", ] diff --git a/qlbm/components/ms/cqlbm.py b/qlbm/components/ms/msqlbm.py similarity index 92% rename from qlbm/components/ms/cqlbm.py rename to qlbm/components/ms/msqlbm.py index b868582..52b02b0 100644 --- a/qlbm/components/ms/cqlbm.py +++ b/qlbm/components/ms/msqlbm.py @@ -17,8 +17,8 @@ from .streaming import MSStreamingOperator, StreamingAncillaPreparation -class CQLBM(LBMAlgorithm): - """The end-to-end algorithm of the Collisionless Quantum Lattice Boltzmann Algorithm first introduced in :cite:t:`collisionless` and later extended in :cite:t:`qmem`. +class MSQLBM(LBMAlgorithm): + """The end-to-end algorithm of the Multi-Speed Collisionless Quantum Lattice Boltzmann Algorithm first introduced in :cite:t:`collisionless` and later extended in :cite:t:`qmem`. This implementation supports 2D and 3D simulations with with cuboid objects with either bounce-back or specular reflection boundary conditions. @@ -76,7 +76,7 @@ def create_circuit(self): for shape in self.lattice.shapes["specular"] ): raise LatticeException( - "All shapes with the 'specular' boundary condition must be of type Block for the CQLBM algorithm. " + "All shapes with the 'specular' boundary condition must be of type Block for the MSQLBM algorithm. " ) circuit.compose( SpecularReflectionOperator( @@ -94,7 +94,7 @@ def create_circuit(self): for shape in self.lattice.shapes["specular"] ): raise LatticeException( - f"All shapes with the {bc} boundary condition must be cuboids for the CQLBM algorithm. " + f"All shapes with the {bc} boundary condition must be cuboids for the MSQLBM algorithm. " ) circuit.compose( BounceBackReflectionOperator( @@ -119,4 +119,4 @@ def create_circuit(self): @override def __str__(self) -> str: - return f"[Algorithm CQLBM with lattice {self.lattice}]" + return f"[Algorithm MSQLBM with lattice {self.lattice}]" diff --git a/qlbm/components/ms/primitives.py b/qlbm/components/ms/primitives.py index c816004..07a3491 100644 --- a/qlbm/components/ms/primitives.py +++ b/qlbm/components/ms/primitives.py @@ -286,7 +286,7 @@ class ComparatorMode(Enum): class SpeedSensitiveAdder(LBMPrimitive): - r"""A QFT-based incrementer used to perform streaming in the CQLBM algorithm. + r"""A QFT-based incrementer used to perform streaming in the algorithms based on amplitude encodings. Incrementation and decerementation are performed as rotations on grid qubits that have been previously mapped to the Fourier basis. diff --git a/qlbm/components/ms/streaming.py b/qlbm/components/ms/streaming.py index 898556e..4888534 100644 --- a/qlbm/components/ms/streaming.py +++ b/qlbm/components/ms/streaming.py @@ -11,14 +11,14 @@ from qiskit.synthesis import synth_qft_full as QFT from typing_extensions import override -from qlbm.components.base import MSOperator, LBMPrimitive +from qlbm.components.base import LBMPrimitive, MSOperator from qlbm.lattice import MSLattice from qlbm.tools import CircuitException, bit_value class StreamingAncillaPreparation(LBMPrimitive): r""" - A primitive used in :class:`.MSStreamingOperator` that implements the preparatory step of streaming necessary for the :class:`.CQLBM` method. + A primitive used in :class:`.MSStreamingOperator` that implements the preparatory step of streaming necessary for the :class:`.MSQLBM` method. This operator sets the ancilla qubits to :math:`\ket{1}` for the velocities that will be streamed in the next CFL time step. diff --git a/qlbm/components/spacetime/__init__.py b/qlbm/components/spacetime/__init__.py index 7aca5cc..7835a9e 100644 --- a/qlbm/components/spacetime/__init__.py +++ b/qlbm/components/spacetime/__init__.py @@ -1,4 +1,4 @@ -"""Modular qlbm quantum circuit components for the CQLBM algorithm :cite:p:`spacetime`.""" +"""Modular qlbm quantum circuit components for the Space-Time QLBM algorithm :cite:p:`spacetime`.""" from .collision.d2q4_old import SpaceTimeD2Q4CollisionOperator from .initial.pointwise import PointWiseSpaceTimeInitialConditions diff --git a/qlbm/infra/result/amplitude_result.py b/qlbm/infra/result/amplitude_result.py index 1c600a2..ababae2 100644 --- a/qlbm/infra/result/amplitude_result.py +++ b/qlbm/infra/result/amplitude_result.py @@ -1,4 +1,4 @@ -""":class:`.CQLBM`-specific implementation of the :class:`.QBMResult`.""" +"""Implementation of the :class:`.QBMResult`. for :class:`AmplitudeLattice`-based algorithms.""" import re from os import listdir diff --git a/test/integration/execution_test.py b/test/integration/execution_test.py index 55c6f3a..97e05fa 100644 --- a/test/integration/execution_test.py +++ b/test/integration/execution_test.py @@ -1,12 +1,12 @@ import pytest from qiskit_aer import AerSimulator +from qlbm.components.common import EmptyPrimitive from qlbm.components.ms import ( - CQLBM, - MSInitialConditions, + MSQLBM, GridMeasurement, + MSInitialConditions, ) -from qlbm.components.common import EmptyPrimitive from qlbm.components.spacetime import ( SpaceTimeGridVelocityMeasurement, SpaceTimeQLBM, @@ -28,7 +28,7 @@ def collisionless_circuits(): return { "initial_conditions": MSInitialConditions(lattice), - "algorithm": CQLBM(lattice), + "algorithm": MSQLBM(lattice), "postprocessing": EmptyPrimitive(lattice), "measurement": GridMeasurement(lattice), "lattice": lattice, diff --git a/test/integration/simulation_config_test.py b/test/integration/simulation_config_test.py index 293d236..f7b49dd 100644 --- a/test/integration/simulation_config_test.py +++ b/test/integration/simulation_config_test.py @@ -4,12 +4,12 @@ from qiskit import QuantumCircuit as QiskitQC from qiskit_aer import AerSimulator +from qlbm.components.common import EmptyPrimitive from qlbm.components.ms import ( - CQLBM, - MSInitialConditions, + MSQLBM, GridMeasurement, + MSInitialConditions, ) -from qlbm.components.common import EmptyPrimitive from qlbm.infra.runner.simulation_config import SimulationConfig from qlbm.lattice import MSLattice from qlbm.tools.exceptions import ExecutionException @@ -21,7 +21,7 @@ def symmetric_2d_no_osbtacle_circuits(): return { "initial_conditions": MSInitialConditions(lattice), - "algorithm": CQLBM(lattice), + "algorithm": MSQLBM(lattice), "postprocessing": EmptyPrimitive(lattice), "measurement": GridMeasurement(lattice), } @@ -33,7 +33,7 @@ def symmetric_2d_one_osbtacle_circuits(): return { "initial_conditions": MSInitialConditions(lattice), - "algorithm": CQLBM(lattice), + "algorithm": MSQLBM(lattice), "postprocessing": EmptyPrimitive(lattice), "measurement": GridMeasurement(lattice), } From bcb9624bee65e7110949d2828f5bf96707f198fd Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 5 Nov 2025 09:53:40 +0100 Subject: [PATCH 36/61] Update AB nomeclature and module imports --- qlbm/components/__init__.py | 14 +++++++++ qlbm/components/ab/__init__.py | 17 +++++++---- qlbm/components/ab/ab.py | 10 +++---- qlbm/components/ab/averaged_collision.py | 2 +- qlbm/components/ab/initial.py | 4 +-- qlbm/components/ab/measurement.py | 4 +-- qlbm/components/ab/reflection.py | 12 ++++---- qlbm/components/ab/streaming.py | 6 ++-- qlbm/components/cqlbm.py | 2 +- qlbm/components/ms/streaming.py | 2 +- .../reinitialize/spacetime_reinitializer.py | 2 +- qlbm/infra/runner/base.py | 4 +-- qlbm/lattice/__init__.py | 4 +-- .../{abe_lattice.py => ab_lattice.py} | 0 qlbm/lattice/lattices/lqlga_lattice.py | 30 ++++++++++--------- test/unit/lattice/conftest.py | 2 +- 16 files changed, 68 insertions(+), 47 deletions(-) rename qlbm/lattice/lattices/{abe_lattice.py => ab_lattice.py} (100%) diff --git a/qlbm/components/__init__.py b/qlbm/components/__init__.py index 1c12991..4038095 100644 --- a/qlbm/components/__init__.py +++ b/qlbm/components/__init__.py @@ -1,5 +1,13 @@ """Modular and extendible quantum circuits that perform parts of the QLBM algorithm.""" +from .ab import ( + ABQLBM, + ABEReflectionPermutation, + ABGridMeasurement, + ABInitialConditions, + ABReflectionOperator, + ABStreamingOperator, +) from .base import ( LBMAlgorithm, LBMOperator, @@ -75,4 +83,10 @@ "EQCPermutation", "EQCRedistribution", "HammingWeightAdder", + "ABQLBM", + "ABInitialConditions", + "ABGridMeasurement", + "ABReflectionOperator", + "ABEReflectionPermutation", + "ABStreamingOperator", ] diff --git a/qlbm/components/ab/__init__.py b/qlbm/components/ab/__init__.py index ea1cd17..3f2290b 100644 --- a/qlbm/components/ab/__init__.py +++ b/qlbm/components/ab/__init__.py @@ -1,11 +1,16 @@ +"""Primitives and operators for the Amplitude Based QLBM.""" + from .ab import ABQLBM -from .initial import ABEInitialConditions -from .measurement import ABEGridMeasurement -from .streaming import ABEStreamingOperator +from .initial import ABInitialConditions +from .measurement import ABGridMeasurement +from .reflection import ABEReflectionPermutation, ABReflectionOperator +from .streaming import ABStreamingOperator __all__ = [ "ABQLBM", - "ABEInitialConditions", - "ABEGridMeasurement", - "ABEStreamingOperator", + "ABInitialConditions", + "ABGridMeasurement", + "ABReflectionOperator", + "ABEReflectionPermutation", + "ABStreamingOperator", ] diff --git a/qlbm/components/ab/ab.py b/qlbm/components/ab/ab.py index 8c0abbd..ad5cdeb 100644 --- a/qlbm/components/ab/ab.py +++ b/qlbm/components/ab/ab.py @@ -6,14 +6,14 @@ from qiskit import QuantumCircuit from typing_extensions import override -from qlbm.components.ab.reflection import ABEReflectionOperator +from qlbm.components.ab.reflection import ABReflectionOperator from qlbm.components.base import LBMAlgorithm from qlbm.lattice.geometry.shapes.block import Block -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import flatten -from .streaming import ABEStreamingOperator +from .streaming import ABStreamingOperator class ABQLBM(LBMAlgorithm): @@ -41,7 +41,7 @@ def create_circuit(self): ) circuit.compose( - ABEStreamingOperator( + ABStreamingOperator( self.lattice, logger=self.logger, ).circuit, @@ -59,7 +59,7 @@ def create_circuit(self): ) circuit.compose( - ABEReflectionOperator( + ABReflectionOperator( self.lattice, flatten(list(self.lattice.shapes.values())), # type: ignore logger=self.logger, diff --git a/qlbm/components/ab/averaged_collision.py b/qlbm/components/ab/averaged_collision.py index 513aa07..d78a3b8 100644 --- a/qlbm/components/ab/averaged_collision.py +++ b/qlbm/components/ab/averaged_collision.py @@ -6,7 +6,7 @@ from qlbm.components.base import LBMOperator from qlbm.components.common.primitives import TruncatedQFT -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException diff --git a/qlbm/components/ab/initial.py b/qlbm/components/ab/initial.py index 9011f07..36534f4 100644 --- a/qlbm/components/ab/initial.py +++ b/qlbm/components/ab/initial.py @@ -6,10 +6,10 @@ from qlbm.components.base import LBMPrimitive from qlbm.components.common.primitives import TruncatedQFT -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice -class ABEInitialConditions(LBMPrimitive): +class ABInitialConditions(LBMPrimitive): """TODO.""" def __init__( diff --git a/qlbm/components/ab/measurement.py b/qlbm/components/ab/measurement.py index 6833eab..2551f51 100644 --- a/qlbm/components/ab/measurement.py +++ b/qlbm/components/ab/measurement.py @@ -5,10 +5,10 @@ from typing_extensions import override from qlbm.components.base import LBMPrimitive -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice -class ABEGridMeasurement(LBMPrimitive): +class ABGridMeasurement(LBMPrimitive): """TODO.""" def __init__( diff --git a/qlbm/components/ab/reflection.py b/qlbm/components/ab/reflection.py index 9713e19..a7bed62 100644 --- a/qlbm/components/ab/reflection.py +++ b/qlbm/components/ab/reflection.py @@ -7,18 +7,18 @@ from qiskit.circuit.library import MCMTGate, XGate from typing_extensions import override -from qlbm.components.ab.streaming import ABEStreamingOperator +from qlbm.components.ab.streaming import ABStreamingOperator from qlbm.components.base import LBMOperator, LBMPrimitive from qlbm.components.ms.specular_reflection import SpecularWallComparator from qlbm.lattice.geometry.encodings.ms import ReflectionPoint from qlbm.lattice.geometry.shapes.block import Block -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import flatten, get_qubits_to_invert -class ABEReflectionOperator(LBMOperator): +class ABReflectionOperator(LBMOperator): """TODO.""" lattice: ABLattice @@ -45,7 +45,7 @@ def create_circuit(self) -> QuantumCircuit: if self.lattice.discretization == LatticeDiscretization.D2Q9: return self.__create_circuit_d2q9() - raise LatticeException("ABE reflection only currently supported in D2Q9") + raise LatticeException("AB reflection only currently supported in D2Q9") def __create_circuit_d2q9(self): circuit = self.lattice.circuit.copy() @@ -291,7 +291,7 @@ def permute_and_stream(self) -> QuantumCircuit: ) circuit.compose( - ABEStreamingOperator( + ABStreamingOperator( self.lattice, self.lattice.ancillae_obstacle_index(), self.logger ).circuit, inplace=True, @@ -301,7 +301,7 @@ def permute_and_stream(self) -> QuantumCircuit: @override def __str__(self) -> str: - return f"[Operator ABEStreaming with lattice {self.lattice}]" + return f"[Operator ABStreaming with lattice {self.lattice}]" class ABEReflectionPermutation(LBMPrimitive): diff --git a/qlbm/components/ab/streaming.py b/qlbm/components/ab/streaming.py index 938d552..b8932a9 100644 --- a/qlbm/components/ab/streaming.py +++ b/qlbm/components/ab/streaming.py @@ -8,13 +8,13 @@ from qlbm.components.base import LBMOperator from qlbm.components.ms.streaming import PhaseShift -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import get_qubits_to_invert -class ABEStreamingOperator(LBMOperator): +class ABStreamingOperator(LBMOperator): """TODO.""" lattice: ABLattice @@ -164,4 +164,4 @@ def __create_circuit_d2q9(self): @override def __str__(self) -> str: - return f"[Operator ABEStreaming with lattice {self.lattice}]" + return f"[Operator ABStreaming with lattice {self.lattice}]" diff --git a/qlbm/components/cqlbm.py b/qlbm/components/cqlbm.py index 1ec5280..c043c96 100644 --- a/qlbm/components/cqlbm.py +++ b/qlbm/components/cqlbm.py @@ -15,7 +15,7 @@ from qlbm.components.base import LBMAlgorithm from qlbm.components.ms.msqlbm import MSQLBM from qlbm.lattice import MSLattice -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice from qlbm.lattice.lattices.base import AmplitudeLattice from qlbm.tools.exceptions import LatticeException diff --git a/qlbm/components/ms/streaming.py b/qlbm/components/ms/streaming.py index 4888534..984a87c 100644 --- a/qlbm/components/ms/streaming.py +++ b/qlbm/components/ms/streaming.py @@ -240,7 +240,7 @@ def __str__(self) -> str: class MSStreamingOperator(MSOperator): - """An operator that performs streaming in Fourier space as part of the :class:`.CQLBM` algorithm. + """An operator that performs streaming in Fourier space as part of the :class:`.MSQLBM` algorithm. Streaming is broken down into the following steps: diff --git a/qlbm/infra/reinitialize/spacetime_reinitializer.py b/qlbm/infra/reinitialize/spacetime_reinitializer.py index 20199e6..866487b 100644 --- a/qlbm/infra/reinitialize/spacetime_reinitializer.py +++ b/qlbm/infra/reinitialize/spacetime_reinitializer.py @@ -25,7 +25,7 @@ class SpaceTimeReinitializer(Reinitializer): :class:`.SpaceTimeQLBM`-specific implementation of the :class:`.Reinitializer`. Compatible with both :class:`.QiskitRunner`\ s and :class:`.QulacsRunner`\ s. - To generate a new set of initial conditions for the CQLBM algorithm, + To generate a new set of initial conditions for the Space-Time encoding, the reinitializer simply returns the quantum state computed at the end of the previous simulation. This allows the reuse of a single quantum circuit for the simulation diff --git a/qlbm/infra/runner/base.py b/qlbm/infra/runner/base.py index 62d39c3..1263312 100644 --- a/qlbm/infra/runner/base.py +++ b/qlbm/infra/runner/base.py @@ -20,8 +20,8 @@ QBMResult, SpaceTimeResult, ) -from qlbm.lattice import MSLattice, Lattice -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice import Lattice, MSLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice from qlbm.tools.exceptions import CircuitException, ResultsException diff --git a/qlbm/lattice/__init__.py b/qlbm/lattice/__init__.py index effdc62..15f4b58 100644 --- a/qlbm/lattice/__init__.py +++ b/qlbm/lattice/__init__.py @@ -12,8 +12,8 @@ from .geometry.shapes.circle import ( Circle, ) -from .lattices import MSLattice, Lattice -from .lattices.abe_lattice import ABLattice +from .lattices import Lattice, MSLattice +from .lattices.ab_lattice import ABLattice from .lattices.lqlga_lattice import LQLGALattice from .lattices.spacetime_lattice import SpaceTimeLattice from .spacetime.properties_base import ( diff --git a/qlbm/lattice/lattices/abe_lattice.py b/qlbm/lattice/lattices/ab_lattice.py similarity index 100% rename from qlbm/lattice/lattices/abe_lattice.py rename to qlbm/lattice/lattices/ab_lattice.py diff --git a/qlbm/lattice/lattices/lqlga_lattice.py b/qlbm/lattice/lattices/lqlga_lattice.py index 2415e5b..a8ba755 100644 --- a/qlbm/lattice/lattices/lqlga_lattice.py +++ b/qlbm/lattice/lattices/lqlga_lattice.py @@ -22,19 +22,19 @@ class LQLGALattice(Lattice): r""" Lattice class for the :class:`.LQLGA` algorithm. - ================================= ======================================================================================== - Attribute Summary - ================================= ======================================================================================== - :attr:`num_gridpoints` The number of gridpoints in each dimension of the lattice. - :attr:`num_velocities` The number of discrete velocities in each dimension of the lattice. - :attr:`num_dims` The number of dimensions of the lattice. - :attr:`discretization` The discretization of the lattice. - :attr:`num_velocities_per_point` The number of discrete velocities per gridpoint. - :attr:`num_base_qubits` The number of qubits required to represent the lattice without velocities. - :attr:`num_total_qubits` The total number of qubits required to represent the lattice, including velocities. - :attr:`registers` The list of quantum registers for the lattice, one for each gridpoint. - :attr:`circuit` The quantum circuit representing the lattice, initialized with the registers. - ================================ ======================================================================================== + ==================================== ======================================================================================== + Attribute Summary + ==================================== ======================================================================================== + :attr:`num_gridpoints` The number of gridpoints in each dimension of the lattice. + :attr:`num_velocities` The number of discrete velocities in each dimension of the lattice. + :attr:`num_dims` The number of dimensions of the lattice. + :attr:`discretization` The discretization of the lattice. + :attr:`num_velocities_per_point` The number of discrete velocities per gridpoint. + :attr:`num_base_qubits` The number of qubits required to represent the lattice without velocities. + :attr:`num_total_qubits` The total number of qubits required to represent the lattice, including velocities. + :attr:`registers` The list of quantum registers for the lattice, one for each gridpoint. + :attr:`circuit` The quantum circuit representing the lattice, initialized with the registers. + ==================================== ======================================================================================== The registers encoded in the lattice and their accessors are given below. For the size of each register, @@ -347,7 +347,9 @@ def set_geometries(self, geometries): For a given lattice (set number of gridpoints and velocity discretization), set multiple geometry configurations to simulate simultaneously. - .. code-block:: python + .. plot:: + :include-source: + from qlbm.lattice import LQLGALattice lattice = LQLGALattice( diff --git a/test/unit/lattice/conftest.py b/test/unit/lattice/conftest.py index 66d9dbc..1c58d5b 100644 --- a/test/unit/lattice/conftest.py +++ b/test/unit/lattice/conftest.py @@ -1,7 +1,7 @@ import pytest from qlbm.lattice.geometry.shapes.block import Block -from qlbm.lattice.lattices.abe_lattice import ABLattice +from qlbm.lattice.lattices.ab_lattice import ABLattice # 1D Lattices From db5f413b41a592f2440471eb458e92309f29dce1 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 5 Nov 2025 13:42:48 +0100 Subject: [PATCH 37/61] Add AB component documentation --- qlbm/components/ab/__init__.py | 4 +- qlbm/components/ab/ab.py | 27 ++++- qlbm/components/ab/averaged_collision.py | 4 +- qlbm/components/ab/initial.py | 43 +++++++- qlbm/components/ab/measurement.py | 24 ++++- qlbm/components/ab/reflection.py | 124 +++++++++++++++++++++-- qlbm/components/ab/streaming.py | 39 ++++++- 7 files changed, 250 insertions(+), 15 deletions(-) diff --git a/qlbm/components/ab/__init__.py b/qlbm/components/ab/__init__.py index 3f2290b..e6d57d3 100644 --- a/qlbm/components/ab/__init__.py +++ b/qlbm/components/ab/__init__.py @@ -3,7 +3,7 @@ from .ab import ABQLBM from .initial import ABInitialConditions from .measurement import ABGridMeasurement -from .reflection import ABEReflectionPermutation, ABReflectionOperator +from .reflection import ABReflectionOperator, ABReflectionPermutation from .streaming import ABStreamingOperator __all__ = [ @@ -11,6 +11,6 @@ "ABInitialConditions", "ABGridMeasurement", "ABReflectionOperator", - "ABEReflectionPermutation", + "ABReflectionPermutation", "ABStreamingOperator", ] diff --git a/qlbm/components/ab/ab.py b/qlbm/components/ab/ab.py index ad5cdeb..a66b081 100644 --- a/qlbm/components/ab/ab.py +++ b/qlbm/components/ab/ab.py @@ -17,7 +17,32 @@ class ABQLBM(LBMAlgorithm): - """TODO.""" + """ + Implementation of the **A** mplitude **B** ased QLBM (ABQLBM). + + The algorithm consists of interleaving steps of streaming and boundary conditions. + Note that there is **no** collision in this algorithm as of yet. + Details of the general framework can be found in :cite:`collisionless`. + The ABQLBM works with :math:`D_dQ_q` discretizations only. + For multi-speed alternatives, see :class:`.MSQLBM`. + + Eample usage: + + .. code-block:: python + + from qlbm.components.ab import ABQLBM + from qlbm.lattice import ABLattice + + # Example with streaming only for simplicity. + lattice = ABLattice( + { + "lattice": {"dim": {"x": 16, "y": 8}, "velocities": "d2q9"}, + "geometry": [], + } + ) + + ABQLBM(lattice).draw("mpl") + """ def __init__( self, diff --git a/qlbm/components/ab/averaged_collision.py b/qlbm/components/ab/averaged_collision.py index d78a3b8..d817c0a 100644 --- a/qlbm/components/ab/averaged_collision.py +++ b/qlbm/components/ab/averaged_collision.py @@ -1,3 +1,5 @@ +"""WIP.""" + from logging import Logger, getLogger from time import perf_counter_ns @@ -12,7 +14,7 @@ class ABEAveragedCollisionOperator(LBMOperator): - """TODO.""" + """WIP.""" lattice: ABLattice diff --git a/qlbm/components/ab/initial.py b/qlbm/components/ab/initial.py index 36534f4..0f26dbb 100644 --- a/qlbm/components/ab/initial.py +++ b/qlbm/components/ab/initial.py @@ -1,3 +1,5 @@ +"""Quantum circuits used for setting the initial state in the :class:`ABQLBM` algorithm.""" + from logging import Logger, getLogger from time import perf_counter_ns @@ -10,7 +12,46 @@ class ABInitialConditions(LBMPrimitive): - """TODO.""" + """ + Initial conditions for the :class:`ABQLBM` algorithm. + + This component creates an equal magnitude superposition of all velocity + basis states at position ``(0, 0)`` using the :class:`TruncatedQFT`. + + Example usage: + + .. plot:: + :include-source: + + from qlbm.components.ab import ABInitialConditions + from qlbm.lattice import ABLattice + + lattice = ABLattice( + { + "lattice": {"dim": {"x": 16, "y": 8}, "velocities": "d2q9"}, + "geometry": [], + } + ) + + ABInitialConditions(lattice).draw("mpl") + + You can also get the low-level decomposition of the circuit as: + + .. plot:: + :include-source: + + from qlbm.components.ab import ABInitialConditions + from qlbm.lattice import ABLattice + + lattice = ABLattice( + { + "lattice": {"dim": {"x": 4, "y": 4}, "velocities": "d2q9"}, + "geometry": [], + } + ) + + ABInitialConditions(lattice).circuit.decompose(reps=2).draw("mpl") + """ def __init__( self, diff --git a/qlbm/components/ab/measurement.py b/qlbm/components/ab/measurement.py index 2551f51..7c6f58d 100644 --- a/qlbm/components/ab/measurement.py +++ b/qlbm/components/ab/measurement.py @@ -1,3 +1,5 @@ +"""Quantum circuits used for measurement in the :class:`ABQLBM` algorithm.""" + from logging import Logger, getLogger from time import perf_counter_ns @@ -9,7 +11,27 @@ class ABGridMeasurement(LBMPrimitive): - """TODO.""" + """ + Grid measurement for the :class:`ABQLBM` algorithm. + + Example usage: + + .. plot:: + :include-source: + + from qlbm.components.ab import ABGridMeasurement + from qlbm.lattice import ABLattice + + lattice = ABLattice( + { + "lattice": {"dim": {"x": 32, "y": 8}, "velocities": "d2q9"}, + "geometry": [], + } + ) + + ABGridMeasurement(lattice).draw("mpl") + + """ def __init__( self, diff --git a/qlbm/components/ab/reflection.py b/qlbm/components/ab/reflection.py index a7bed62..9cb5b43 100644 --- a/qlbm/components/ab/reflection.py +++ b/qlbm/components/ab/reflection.py @@ -1,3 +1,5 @@ +"""Quantum circuits used for reflection in the :class:`ABQLBM` algorithm.""" + from itertools import product from logging import Logger, getLogger from time import perf_counter_ns @@ -19,7 +21,29 @@ class ABReflectionOperator(LBMOperator): - """TODO.""" + """ + Implements bounceback reflection in the amplitude-based encoding of :class:`.ABQLBM` for :math:`D_dQ_q` discretizations. + + Example usage: + + .. code-block:: python + + from qlbm.components.ab import ABReflectionOperator + from qlbm.lattice import ABLattice + + lattice = ABLattice( + { + "lattice": {"dim": {"x": 4, "y": 4}, "velocities": "d2q9"}, + "geometry": [ + {"shape": "cuboid", "x": [1, 3], "y": [1, 3], "boundary": "bounceback"} + ], + } + ) + + # Drawing the circuit might take a while + ABReflectionOperator(lattice, blocks=lattice.shapes["bounceback"]) + + """ lattice: ABLattice @@ -52,7 +76,7 @@ def __create_circuit_d2q9(self): # Mark populations inside the object for block in self.blocks: - circuit.compose(self.set_wall_ancilla_state(block), inplace=True) + circuit.compose(self.set_inside_wall_ancilla_state(block), inplace=True) circuit.compose( self.reset_ancilla_of_point_state( @@ -107,7 +131,24 @@ def __create_circuit_d2q9(self): return circuit - def set_wall_ancilla_state(self, block: Block): + def set_inside_wall_ancilla_state(self, block: Block) -> QuantumCircuit: + """ + Sets the state of the ancilla qubit for all the gridpoints lying inside the walls of the block. + + This is done using the :class:`.SpecularWallComparator` originally designed + for the :class:`MSQLBM` algorithm. + The inside corner points are not addressed. + + Parameters + ---------- + block : Block + The solid object to address. + + Returns + ------- + QuantumCircuit + The circuit that sets the appropriate state on the object ancilla qubit. + """ circuit = self.lattice.circuit.copy() for dim in range(self.lattice.num_dims): @@ -150,7 +191,26 @@ def set_wall_ancilla_state(self, block: Block): return circuit - def reset_outside_wall_ancilla_state(self, block: Block): + def reset_outside_wall_ancilla_state(self, block: Block) -> QuantumCircuit: + """ + Resets the state of the obstacle ancilla qubit for all the gridpoints that are directly adjacent to the object, but in the fluid domain. + + This is done using the :class:`.SpecularWallComparator` originally designed + for the :class:`MSQLBM` algorithm. The state of obstacle ancilla of the + the near-corner gridpoints will be incorrect following the application of this primitive + and needs to be corrected. + The outside corner points are not addressed. + + Parameters + ---------- + block : Block + The solid object to address. + + Returns + ------- + QuantumCircuit + The circuit that sets the appropriate state on the object ancilla qubit. + """ circuit = self.lattice.circuit.copy() for dim in range(self.lattice.num_dims): @@ -215,7 +275,22 @@ def reset_ancilla_of_point_state( self, points_data: List[Tuple[ReflectionPoint, List[int]]], ignore_velocity_data: bool, - ): + ) -> QuantumCircuit: + """ + Sets the state of the obstacle ancilla qubit of a given gridpoint, conditioned on the velocity profile. + + Parameters + ---------- + points_data : List[Tuple[ReflectionPoint, List[int]]] + The tuple of gridpoint and velocity profile to set the ancilla state for. + ignore_velocity_data : bool + Whether to ignore the velocity data. Setting this to ``True`` will flip the state of the ancilla qubit based on position alone. + + Returns + ------- + QuantumCircuit + The circuit that sets the appropriate state on the object ancilla qubit. + """ circuit = self.lattice.circuit.copy() for point, velocities in points_data: @@ -274,11 +349,19 @@ def reset_ancilla_of_point_state( return circuit def permute_and_stream(self) -> QuantumCircuit: + """ + Performs the permutation of basis states that implements bounceback reflection in the amplitude-based encoding. + + Returns + ------- + QuantumCircuit + The permutation acting on only the velocity register. + """ circuit = self.lattice.circuit.copy() # Permute the velocities according to reflection rules circuit.compose( - ABEReflectionPermutation( + ABReflectionPermutation( self.lattice.num_velocity_qubits, self.lattice.discretization, self.logger, @@ -304,7 +387,32 @@ def __str__(self) -> str: return f"[Operator ABStreaming with lattice {self.lattice}]" -class ABEReflectionPermutation(LBMPrimitive): +class ABReflectionPermutation(LBMPrimitive): + """ + Permutes velocity state to implement reflection in the amplitude-based encoding for :math:`D_dQ_q` discretizations. + + Example usage: + + .. plot:: + :include-source: + + from qlbm.components.ab import ABReflectionPermutation + from qlbm.lattice import LatticeDiscretization + + ABReflectionPermutation(4, LatticeDiscretization.D2Q9).draw("mpl") + + """ + + num_qubits: int + """ + The number of qubits that encode the velocity state. + """ + + discretization: LatticeDiscretization + """ + The lattice discretization the permutation adheres to. + """ + def __init__( self, num_qubits: int, @@ -363,4 +471,4 @@ def __create_circuit_d2q9(self): @override def __str__(self) -> str: - return f"[Primitive ABEReflectionPermutation with {self.num_qubits} qubits on {self.discretization}]" + return f"[Primitive ABReflectionPermutation with {self.num_qubits} qubits on {self.discretization}]" diff --git a/qlbm/components/ab/streaming.py b/qlbm/components/ab/streaming.py index b8932a9..c8aa19c 100644 --- a/qlbm/components/ab/streaming.py +++ b/qlbm/components/ab/streaming.py @@ -1,3 +1,5 @@ +"""Quantum circuits used for streaming in the :class:`ABQLBM` algorithm.""" + from logging import Logger, getLogger from time import perf_counter_ns from typing import List @@ -15,9 +17,44 @@ class ABStreamingOperator(LBMOperator): - """TODO.""" + """ + Streaming operator for the :class:`ABQLBM` algorithm. + + Uses a variant of the Draper adder described in :cite:`collisionless`. + The operator works by applying QFTs in parallel to each dimension of the grid, + followed by phase gates that perform incrementation in the Fourier basis, and, finally, + by applying an inverse QFT mapping the qubits back to the computational basis. + + Populations are streamed one after the other in Fourier space + by controlling phase gates on the state of the velocity qubits. + Additional controls qubits can be specified to restrict this operation. + + Example usage: + + .. plot:: + :include-source: + + from qlbm.components.ab import ABStreamingOperator + from qlbm.lattice import ABLattice + + lattice = ABLattice( + { + "lattice": {"dim": {"x": 4, "y": 8}, "velocities": "d2q9"}, + "geometry": [], + } + ) + + ABStreamingOperator(lattice).draw("mpl") + + """ lattice: ABLattice + """The lattice to construct the component for.""" + + additional_control_qubit_indices: List[int] + """The qubits (if any) that streaming should be controlled over. + This makes the operator useful for the application of boundary conditions. + Controls need only be applied to the phase gates and not the QFT blocks.""" def __init__( self, From f4fb3b96d68a09c6cb716e2baec27a04bd8abb29 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 5 Nov 2025 13:46:04 +0100 Subject: [PATCH 38/61] Update lattice documentation --- qlbm/lattice/geometry/encodings/__init__.py | 2 +- qlbm/lattice/geometry/shapes/block.py | 66 ++++++++++++++++- qlbm/lattice/lattices/__init__.py | 2 +- qlbm/lattice/lattices/ab_lattice.py | 82 ++++++++++++++++++++- qlbm/lattice/lattices/base.py | 15 +++- qlbm/lattice/lattices/ms_lattice.py | 5 +- 6 files changed, 164 insertions(+), 8 deletions(-) diff --git a/qlbm/lattice/geometry/encodings/__init__.py b/qlbm/lattice/geometry/encodings/__init__.py index b0400f2..aff8bdb 100644 --- a/qlbm/lattice/geometry/encodings/__init__.py +++ b/qlbm/lattice/geometry/encodings/__init__.py @@ -1,12 +1,12 @@ """Algorithm-specific geometrical data encodings.""" +from .lqlga import LQLGAPointwiseReflectionData, LQLGAReflectionData from .ms import ( DimensionalReflectionData, ReflectionPoint, ReflectionResetEdge, ReflectionWall, ) -from .lqlga import LQLGAPointwiseReflectionData, LQLGAReflectionData from .spacetime import ( SpaceTimeDiagonalReflectionData, SpaceTimePWReflectionData, diff --git a/qlbm/lattice/geometry/shapes/block.py b/qlbm/lattice/geometry/shapes/block.py index 7782c90..c3a0f62 100644 --- a/qlbm/lattice/geometry/shapes/block.py +++ b/qlbm/lattice/geometry/shapes/block.py @@ -760,6 +760,28 @@ def get_d2q4_surfaces(self) -> List[List[List[Tuple[int, ...]]]]: def get_lbm_wall_velocity_indices_to_reflect( self, discretization: LatticeDiscretization, dim: int, bound: bool ) -> List[int]: + """ + Get the indices of the velocities that need to be reflected at gridpoints outside the wall of the object. + + Parameters + ---------- + discretization : LatticeDiscretization + The discretization of the lattice. + dim : int + The dimension that the wall reflects. + bounds : Tuple[bool, ...] + The bounds of the corner to address. + + Returns + ------- + List[int] + The list of velocity indices to reflect. + + Raises + ------ + LatticeException + If the discretization is not supported or if the dimension or bounds are inconsistent. + """ if discretization not in self.ab_wall_indices_to_reset: raise LatticeException( f"Discretization {discretization} not supported. Supported discretizations are: {self.ab_wall_indices_to_reset.keys()}" @@ -775,6 +797,28 @@ def get_lbm_wall_velocity_indices_to_reflect( def get_lbm_near_corner_velocity_indices_to_reflect( self, discretization: LatticeDiscretization, dim: int, bounds: Tuple[bool, ...] ) -> List[int]: + """ + Get the indices of the velocities that need to be reflected at gridpoints near the corners of the object. + + Parameters + ---------- + discretization : LatticeDiscretization + The discretization of the lattice. + dim : int + The dimension outside of obstacle bounds. + bounds : Tuple[bool, ...] + The bounds of the corner to address. + + Returns + ------- + List[int] + The list of velocity indices to reflect. + + Raises + ------ + LatticeException + If the discretization is not supported or if the dimension or bounds are inconsistent. + """ if discretization not in self.ab_near_corner_indices_to_reset: raise LatticeException( f"Discretization {discretization} not supported. Supported discretizations are: {self.ab_wall_indices_to_reset.keys()}" @@ -794,7 +838,27 @@ def get_lbm_near_corner_velocity_indices_to_reflect( def get_lbm_outside_corner_indices_to_reflect( self, discretization: LatticeDiscretization, bounds: Tuple[bool, ...] - ): + ) -> List[int]: + """ + Get the indices of the velocities that need to be reflected at the outside corners of the object. + + Parameters + ---------- + discretization : LatticeDiscretization + The discretization of the lattice. + bounds : Tuple[bool, ...] + The bounds of the corner to address. + + Returns + ------- + List[int] + The list of velocity indices to reflect. + + Raises + ------ + LatticeException + If the discretization is not supported or if the bounds are inconsistent. + """ if discretization not in self.ab_corner_indices_to_reset: raise LatticeException( f"Discretization {discretization} not supported. Supported discretizations are: {self.ab_wall_indices_to_reset.keys()}" diff --git a/qlbm/lattice/lattices/__init__.py b/qlbm/lattice/lattices/__init__.py index 6609f5b..5cc7f25 100644 --- a/qlbm/lattice/lattices/__init__.py +++ b/qlbm/lattice/lattices/__init__.py @@ -1,8 +1,8 @@ """Base Lattice class and algorithm-specific implementations.""" from .base import Lattice -from .ms_lattice import MSLattice from .lqlga_lattice import LQLGALattice +from .ms_lattice import MSLattice from .spacetime_lattice import SpaceTimeLattice __all__ = ["Lattice", "MSLattice", "SpaceTimeLattice", "LQLGALattice"] diff --git a/qlbm/lattice/lattices/ab_lattice.py b/qlbm/lattice/lattices/ab_lattice.py index 256ce81..6129b00 100644 --- a/qlbm/lattice/lattices/ab_lattice.py +++ b/qlbm/lattice/lattices/ab_lattice.py @@ -19,7 +19,82 @@ class ABLattice(AmplitudeLattice): - """TODO.""" + r""" + Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.ABQLBM` algorithm developed in :cite:t:`collisionless`. + + This lattice is only built from :math:`D_dQ_q` specifications. + For multi-speed implementations, see :class:`.MSQLBM`. + + The registers encoded in the lattice and their accessors are given below. + For the size of each register, + :math:`N_{g_j}` is the number of grid points of dimension :math:`j` (i.e., 64, 128), + :math:`q` is the number of discrete velocities, for instance, 9. + + .. list-table:: Register allocation + :widths: 25 25 25 50 + :header-rows: 1 + + * - Register + - Size + - Access Method + - Description + * - :attr:`grid_registers` + - :math:`\Sigma_{1\leq j \leq d} \left \lceil{\log N_{g_j}} \right \rceil` + - :meth:`grid_index` + - The qubits encoding the physical grid. + * - :attr:`velocity_registers` + - :math:`\lceil\log_2 q \rceil` + - :meth:`velocity_index` + - The qubits encoding the :math:`q` discrete velocities. + * - :attr:`ancilla_obstacle_register` + - :math:`1` + - :meth:`ancillae_obstacle_index` + - The qubits used to detect whether particles have streamed into obstacles. Used for reflection. + * - :attr:`ancilla_comparator_register` + - :math:`2(d-1)` + - :meth:`ancillae_comparator_index` + - The qubits used to for :class:`.Comparator`\ s. Used for reflection. + + A lattice can be constructed from from either an input file or a Python dictionary: + + .. code-block:: json + + { + "lattice": { + "dim": { + "x": 16, + "y": 16 + }, + "velocities": "d2q9" + }, + "geometry": [ + { + "x": [9, 12], + "y": [3, 6], + "boundary": "bounceback" + }, + { + "x": [9, 12], + "y": [9, 12], + "boundary": "bounceback" + } + ] + } + + The register setup can be visualized by constructing a lattice object: + + .. plot:: + :include-source: + + from qlbm.lattice import ABLattice + + ABLattice( + { + "lattice": {"dim": {"x": 8, "y": 8}, "velocities": "D2Q9"}, + "geometry": [], + } + ).circuit.draw("mpl") + """ discretization: LatticeDiscretization """The discretization of the lattice, one of :class:`.LatticeDiscretization`.""" @@ -38,6 +113,7 @@ class ABLattice(AmplitudeLattice): """The number of qubits required to represent the lattice.""" registers: Tuple[QuantumRegister, ...] + """The registers of the lattice.""" def __init__( self, @@ -108,7 +184,9 @@ def grid_index(self, dim: int | None = None) -> List[int]: @override def velocity_index(self, dim: int | None = None) -> List[int]: if dim is not None: - raise LatticeException("ABLattice does not support a dimensional breakdown of velocities.") + raise LatticeException( + "ABLattice does not support a dimensional breakdown of velocities." + ) return list( range( self.num_grid_qubits, diff --git a/qlbm/lattice/lattices/base.py b/qlbm/lattice/lattices/base.py index 8b6581d..977bd94 100644 --- a/qlbm/lattice/lattices/base.py +++ b/qlbm/lattice/lattices/base.py @@ -509,6 +509,17 @@ def has_multiple_geometries(self) -> bool: class AmplitudeLattice(Lattice, ABC): + r""" + Abstract Lattice class for QLBM algorithms that use the ampltiude-based encoding. + + The amplitude-based encoding generally maps LBM populations :math:`f_i` onto basis states as :math:`\sqrt{f_i}\ket{x}\ket{v}`, + with :math:`x` the position and :math:`v` the velocity. + Amplitude-based encdoings generally compress both the grid register and the velocity register into logarithmically + many qubits. + + ``qlbm`` currently has 2 amplitude-based lattices: the :class:`.MSLattice` and :class:`.ABLattice` used in the :class:`.MSQLBM` and :class:`.ABQLBM`, respectively. + """ + def __init__( self, lattice_data, @@ -587,7 +598,7 @@ def ancillae_obstacle_index(self, index: int | None = None) -> List[int]: If the dimension does not exist. """ pass - + @abstractmethod def velocity_index(self, dim: int | None = None) -> List[int]: """Get the indices of the qubits used that encode the velocity magnitude values for the specified dimension. @@ -610,4 +621,4 @@ def velocity_index(self, dim: int | None = None) -> List[int]: LatticeException If the dimension does not exist. """ - pass \ No newline at end of file + pass diff --git a/qlbm/lattice/lattices/ms_lattice.py b/qlbm/lattice/lattices/ms_lattice.py index 9c0ae86..9b21636 100644 --- a/qlbm/lattice/lattices/ms_lattice.py +++ b/qlbm/lattice/lattices/ms_lattice.py @@ -15,7 +15,10 @@ class MSLattice(AmplitudeLattice): r""" - Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.CQLBM` algorithm developed by :cite:t:`collisionless`. + Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.MSQLBM` algorithm developed by :cite:t:`collisionless`. + + This lattice is only built from multi-speed specifications. + For :math:`D_dQ_q` implementations, see :class:`.ABQLBM`. =========================== ====================================================================== Attribute Summary From 174309db1fd12e4caec5ddd0a62a9d9f25622480 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 5 Nov 2025 13:46:38 +0100 Subject: [PATCH 39/61] Update CQLBM documentation --- qlbm/components/cqlbm.py | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/qlbm/components/cqlbm.py b/qlbm/components/cqlbm.py index c043c96..6030778 100644 --- a/qlbm/components/cqlbm.py +++ b/qlbm/components/cqlbm.py @@ -23,10 +23,40 @@ class CQLBM(LBMAlgorithm): """The end-to-end algorithm of the Collisionless Quantum Lattice Boltzmann Algorithm first introduced in :cite:t:`collisionless` and later extended in :cite:t:`qmem`. - Implementations based on lattices with the DdQq discretization use the :class:`.ABQLBM`. - Implementations where the number of lattices is defined per dimension delegate to the :class:`.MSQLBM`. + Implementations based on lattices with the DdQq discretization use the :class:`.ABQLBM`: + + .. plot:: + :include-source: + + from qlbm.components import CQLBM + from qlbm.lattice import ABLattice + + lattice = ABLattice( + { + "lattice": {"dim": {"x": 4, "y": 4}, "velocities": "d2q9"}, + "geometry": [], + } + ) + + CQLBM(lattice).draw("mpl") + + Implementations where the number of velocities is defined per dimension delegate to the :class:`.MSQLBM`. + + .. plot:: + :include-source: + + from qlbm.components import CQLBM + from qlbm.lattice import MSLattice + + lattice = MSLattice( + { + "lattice": {"dim": {"x": 4, "y": 4}, "velocities": {"x": 4, "y": 4}}, + "geometry": [], + } + ) + + CQLBM(lattice).draw("mpl") - TODO add examples """ def __init__( From 251826fdfead7b157d00099a3f9b1cca40d1589a Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 5 Nov 2025 13:47:07 +0100 Subject: [PATCH 40/61] Update result documentation --- qlbm/infra/result/base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/qlbm/infra/result/base.py b/qlbm/infra/result/base.py index 8b9c647..fce9140 100644 --- a/qlbm/infra/result/base.py +++ b/qlbm/infra/result/base.py @@ -171,6 +171,18 @@ def visualize_all_numpy_data(self): pass def save_statevector(self, statevector: Statevector, step: int): + """ + Save a given statevector to disk. + + The statevector is saved as a numpy array in the ``statevector`` subdirectory of the result's root directory. + + Parameters + ---------- + statevector : Statevector + The statevector to save. + step : int + The step this statevector corresponds to, for naming purposes. + """ statevector_dir = f"{self.directory}/statevectors" if not isdir(statevector_dir): create_directory_and_parents(statevector_dir) From 597aa62a66554bdd76b29d4ad23a1abae62588ad Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Wed, 5 Nov 2025 13:48:36 +0100 Subject: [PATCH 41/61] Update component documentation and modules --- qlbm/__init__.py | 7 ++-- qlbm/components/__init__.py | 4 +-- qlbm/components/base.py | 2 +- qlbm/components/common/primitives.py | 28 ++++++++++++++-- qlbm/components/lqlga/initial.py | 32 +++++++++++++++++-- qlbm/components/ms/bounceback_reflection.py | 2 +- qlbm/components/ms/specular_reflection.py | 2 +- .../unit/ab/ab_reflection_permutation_test.py | 4 +-- 8 files changed, 67 insertions(+), 14 deletions(-) diff --git a/qlbm/__init__.py b/qlbm/__init__.py index 39d68e7..7e98639 100644 --- a/qlbm/__init__.py +++ b/qlbm/__init__.py @@ -9,11 +9,12 @@ MSStreamingOperator, SpecularReflectionOperator, ) -from .infra import CircuitCompiler, AmplitudeResult, QiskitRunner -from .lattice import MSLattice, Lattice -from .lattice.lattices.spacetime_lattice import SpaceTimeLattice +from .infra import AmplitudeResult, CircuitCompiler, QiskitRunner +from .lattice import ABLattice, Lattice, LQLGALattice, MSLattice, SpaceTimeLattice __all__ = [ + "ABLattice", + "LQLGALattice", "Lattice", "MSLattice", "SpaceTimeLattice", diff --git a/qlbm/components/__init__.py b/qlbm/components/__init__.py index 4038095..f969bbc 100644 --- a/qlbm/components/__init__.py +++ b/qlbm/components/__init__.py @@ -2,10 +2,10 @@ from .ab import ( ABQLBM, - ABEReflectionPermutation, ABGridMeasurement, ABInitialConditions, ABReflectionOperator, + ABReflectionPermutation, ABStreamingOperator, ) from .base import ( @@ -87,6 +87,6 @@ "ABInitialConditions", "ABGridMeasurement", "ABReflectionOperator", - "ABEReflectionPermutation", + "ABReflectionPermutation", "ABStreamingOperator", ] diff --git a/qlbm/components/base.py b/qlbm/components/base.py index 65178ef..ca34b48 100644 --- a/qlbm/components/base.py +++ b/qlbm/components/base.py @@ -9,7 +9,7 @@ from qiskit.qasm3 import dump as dump_qasm3 from typing_extensions import override -from qlbm.lattice import MSLattice, Lattice +from qlbm.lattice import Lattice, MSLattice from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice from qlbm.lattice.lattices.spacetime_lattice import SpaceTimeLattice diff --git a/qlbm/components/common/primitives.py b/qlbm/components/common/primitives.py index 3a7dc5d..cb2a4cf 100644 --- a/qlbm/components/common/primitives.py +++ b/qlbm/components/common/primitives.py @@ -183,7 +183,31 @@ def __str__(self): class TruncatedQFT(LBMPrimitive): - """TODO.""" + r"""Truncated Quantum Fourier Transform primitive used to create an equal magnitude superposition. + + For a superposition of the first :math:`k` basis states encoded in :math:`n` qubits, + the operator consists of discrete fourier transform block of size :math:`k\times k`, + padded with :math:`2^n - k` :math:`1`s on the main diagonal. + The rationale and properties of this operator are described in :cite:`spacetime2`. + This primitive is used in both amplitude-based and computational basis state encodings. + In the :class:`ABInitialConditions`, it creates an equal magnitude superposition over the velocity space. + In the :class:`EQCRedistribution`, the superposition is over all basis states with an equivalent mass and momenta. + + Example usage: + + .. plot:: + :include-source: + + from qlbm.components.common import TruncatedQFT + + TruncatedQFT(4, 7).decompose(reps=2).draw("mpl") + """ + + num_qubits: int + """The number of qubits the operator acts on.""" + + dft_size: int + """The size of the discrete Fourier transform block.""" def __init__( self, @@ -224,7 +248,7 @@ def create_circuit(self): circuit.append(op, list(range(self.num_qubits))) return circuit - + @override def __str__(self): return f"[Primitive TuncatedQFT({self.num_qubits}, {self.dft_size})]" diff --git a/qlbm/components/lqlga/initial.py b/qlbm/components/lqlga/initial.py index 743c607..835e47d 100644 --- a/qlbm/components/lqlga/initial.py +++ b/qlbm/components/lqlga/initial.py @@ -94,10 +94,38 @@ def __str__(self): class LQGLAAveragedInitialConditions(LBMPrimitive): - """TODO.""" + """ + Primitive for setting initial conditions in the :class:`.LQLGA` algorithm. + + This operator creates an equal magnitude superposition over a set of gridpoints. + This is equivalent to starting the QLGA algorithm in all possible configurations + over the given set of gridpoints. + + + Example usage: + + .. plot:: + :include-source: + + from qlbm.lattice import LQLGALattice + from qlbm.components.lqlga import LQGLAAveragedInitialConditions + + lattice = LQLGALattice( + { + "lattice": { + "dim": {"x": 5}, + "velocities": "D1Q3", + }, + "geometry": [], + }, + ) + initial_conditions = LQGLAAveragedInitialConditions(lattice, [0, 2, 3]) + initial_conditions.draw("mpl") + + """ gridpoints: List[int] - """TODO.""" + """The gridpoints to create the uniform superposition over.""" def __init__( self, diff --git a/qlbm/components/ms/bounceback_reflection.py b/qlbm/components/ms/bounceback_reflection.py index 1ff7272..5acc32b 100644 --- a/qlbm/components/ms/bounceback_reflection.py +++ b/qlbm/components/ms/bounceback_reflection.py @@ -8,7 +8,7 @@ from qiskit.circuit.library import MCMTGate, XGate from typing_extensions import override -from qlbm.components.base import MSOperator, LBMPrimitive +from qlbm.components.base import LBMPrimitive, MSOperator from qlbm.components.ms.primitives import ( Comparator, ComparatorMode, diff --git a/qlbm/components/ms/specular_reflection.py b/qlbm/components/ms/specular_reflection.py index a5bcfbd..fa68a87 100644 --- a/qlbm/components/ms/specular_reflection.py +++ b/qlbm/components/ms/specular_reflection.py @@ -8,7 +8,7 @@ from qiskit.circuit.library import MCMTGate, XGate from typing_extensions import override -from qlbm.components.base import MSOperator, LBMPrimitive +from qlbm.components.base import LBMPrimitive, MSOperator from qlbm.components.ms.primitives import ( Comparator, ComparatorMode, diff --git a/test/unit/ab/ab_reflection_permutation_test.py b/test/unit/ab/ab_reflection_permutation_test.py index 4b4b1db..05ea770 100644 --- a/test/unit/ab/ab_reflection_permutation_test.py +++ b/test/unit/ab/ab_reflection_permutation_test.py @@ -2,7 +2,7 @@ from qiskit import QuantumCircuit, transpile from qiskit_aer import AerSimulator -from qlbm.components.ab.reflection import ABEReflectionPermutation +from qlbm.components.ab.reflection import ABReflectionPermutation from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.utils import bit_value, get_qubits_to_invert @@ -22,7 +22,7 @@ def test_reflectionpermutation_outcomes_d2q9(permutation_outcome_pairs): qc.x(q) qc.compose( - ABEReflectionPermutation(nq, LatticeDiscretization.D2Q9).circuit, inplace=True + ABReflectionPermutation(nq, LatticeDiscretization.D2Q9).circuit, inplace=True ) qc.measure_all() tqc = transpile(qc, sim, optimization_level=0) From 19f4ae0838254e17e5eeef16a77b1eddf76f58dc Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Thu, 13 Nov 2025 12:20:54 +0100 Subject: [PATCH 42/61] Add documentation for MSQLBM components and lattice --- docs/source/code/comps_base.rst | 2 +- docs/source/code/comps_cqlbm.rst | 102 +++++++++++++++++++++++-------- docs/source/code/comps_stqbm.rst | 4 +- docs/source/code/index.rst | 4 +- docs/source/code/infra.rst | 2 +- docs/source/code/lattice.rst | 11 +++- 6 files changed, 93 insertions(+), 32 deletions(-) diff --git a/docs/source/code/comps_base.rst b/docs/source/code/comps_base.rst index 370c4f5..e830f81 100644 --- a/docs/source/code/comps_base.rst +++ b/docs/source/code/comps_base.rst @@ -35,7 +35,7 @@ Components Base .. autoclass:: qlbm.components.base.LBMOperator -.. autoclass:: qlbm.components.base.CQLBMOperator +.. autoclass:: qlbm.components.base.MSOperator .. autoclass:: qlbm.components.base.SpaceTimeOperator diff --git a/docs/source/code/comps_cqlbm.rst b/docs/source/code/comps_cqlbm.rst index 4a2e80f..de179ef 100644 --- a/docs/source/code/comps_cqlbm.rst +++ b/docs/source/code/comps_cqlbm.rst @@ -1,7 +1,7 @@ -.. _cqlbm_components: +.. _amplitude_components: ==================================== -Collisionless Circuits +Amplitude-Based Circuits ==================================== .. testcode:: @@ -9,12 +9,13 @@ Collisionless Circuits from qlbm.components import ( CQLBM, - CollisionlessStreamingOperator, + MSQLBM, + MSStreamingOperator, ControlledIncrementer, SpecularReflectionOperator, SpeedSensitivePhaseShift, ) - from qlbm.lattice import CollisionlessLattice + from qlbm.lattice import MSLattice print("ok") .. testoutput:: @@ -23,9 +24,21 @@ Collisionless Circuits ok -This page contains documentation about the quantum circuits that make up the -**C**\ ollisionless **Q**\ uantum **L**\ attice **B**\ oltzmann **M**\ ethod (CQLBM) -first described in :cite:p:`collisionless` and later expanded in :cite:p:`qmem`. +This page documents the components that are used in algorithms +that use the **A** mplitude **B** ased (AB) Encoding. +At the moment, this includes two algorihtms: + +#. The "regular" Amplitude-Based Collisionless QLBM: ABQLBM, +#. The Multi-Speed (MS) Collisionless QLBM: MSQLBM. + +Both algorithms are instances of the Collisionless QLBM (CQLBM), also known as the +Quantum Transport Method (QTM). +Both algorithms compress the grid and the vnumber of discrete velocities +into :math:`N_g\cdot N_v \mapsto \lceil \log_2 N_g \rceil + \lceil \log_2 N_v \rceil` qubits. +The amplitude of each basis state is directly related to the populations in the classical LBM discretization. +The MSQLBM is a generalization of the ABQLBM. +The implementation of the algorithms was first described in :cite:p:`collisionless` and later expanded in :cite:p:`qmem`. + At its core, the CQLBM algorithm manipulates the particle probability distribution in an amplitude-based encoding of the quantum state. This happens in several distinct steps: @@ -44,53 +57,92 @@ before being broken down into their constituent parts. End-to-end algorithms ---------------------------------- -.. autoclass:: qlbm.components.collisionless.cqlbm.CQLBM +.. autoclass:: qlbm.components.CQLBM + +.. autoclass:: qlbm.components.ms.MSQLBM + +.. autoclass:: qlbm.components.ab.ABQLBM .. _cqlbm_streaming: Streaming ---------------------------------- -.. autoclass:: qlbm.components.collisionless.streaming.CollisionlessStreamingOperator +MSQLBM +********************************** -.. autoclass:: qlbm.components.collisionless.streaming.StreamingAncillaPreparation +.. autoclass:: qlbm.components.ms.streaming.MSStreamingOperator -.. autoclass:: qlbm.components.collisionless.streaming.ControlledIncrementer +.. autoclass:: qlbm.components.ms.streaming.StreamingAncillaPreparation -.. autoclass:: qlbm.components.collisionless.primitives.SpeedSensitiveAdder +.. autoclass:: qlbm.components.ms.streaming.ControlledIncrementer -.. autoclass:: qlbm.components.collisionless.streaming.SpeedSensitivePhaseShift +.. autoclass:: qlbm.components.ms.primitives.SpeedSensitiveAdder -.. autoclass:: qlbm.components.collisionless.streaming.PhaseShift +.. autoclass:: qlbm.components.ms.streaming.SpeedSensitivePhaseShift + +.. autoclass:: qlbm.components.ms.streaming.PhaseShift + +ABQLBM +********************************** + +.. autoclass:: qlbm.components.ab.streaming.ABStreamingOperator .. _cqlbm_reflection: Reflection ---------------------------------- -.. autoclass:: qlbm.components.collisionless.bounceback_reflection.BounceBackReflectionOperator +MSQLBM Reflection +********************************** + +.. autoclass:: qlbm.components.ms.bounceback_reflection.BounceBackReflectionOperator :members: -.. autoclass:: qlbm.components.collisionless.specular_reflection.SpecularReflectionOperator +.. autoclass:: qlbm.components.ms.specular_reflection.SpecularReflectionOperator :members: -.. autoclass:: qlbm.components.collisionless.bounceback_reflection.BounceBackWallComparator +.. autoclass:: qlbm.components.ms.bounceback_reflection.BounceBackWallComparator + +.. autoclass:: qlbm.components.ms.specular_reflection.SpecularWallComparator -.. autoclass:: qlbm.components.collisionless.specular_reflection.SpecularWallComparator +.. autoclass:: qlbm.components.ms.primitives.EdgeComparator -.. autoclass:: qlbm.components.collisionless.primitives.EdgeComparator +.. autoclass:: qlbm.components.ms.primitives.Comparator -.. autoclass:: qlbm.components.collisionless.primitives.Comparator +.. autoclass:: qlbm.components.ms.primitives.ComparatorMode -.. autoclass:: qlbm.components.collisionless.primitives.ComparatorMode +ABQLBM Reflection +********************************** + +.. autoclass:: qlbm.components.ab.reflection.ABReflectionOperator .. _cqlbm_others: -Others +Initial Conditions ----------------------------------- -.. autoclass:: qlbm.components.collisionless.primitives.CollisionlessInitialConditions +MSQLBM Initial Conditions +********************************** + +.. autoclass:: qlbm.components.ms.primitives.MSInitialConditions + +.. autoclass:: qlbm.components.ms.primitives.MSInitialConditions3DSlim + +ABQLBM Initial Conditions +********************************** + +.. autoclass:: qlbm.components.ab.initial.ABInitialConditions + +Measurement +----------------------------------- + +MSQLBM Measurement +********************************** + +.. autoclass:: qlbm.components.ms.primitives.GridMeasurement -.. autoclass:: qlbm.components.collisionless.primitives.CollisionlessInitialConditions3DSlim +ABQLBM Measurement +********************************** -.. autoclass:: qlbm.components.collisionless.primitives.GridMeasurement \ No newline at end of file +.. autoclass:: qlbm.components.ab.measurement.ABGridMeasurement \ No newline at end of file diff --git a/docs/source/code/comps_stqbm.rst b/docs/source/code/comps_stqbm.rst index a37faa3..a0c0a92 100644 --- a/docs/source/code/comps_stqbm.rst +++ b/docs/source/code/comps_stqbm.rst @@ -9,12 +9,12 @@ Space-Time Circuits from qlbm.components import ( CQLBM, - CollisionlessStreamingOperator, + MSStreamingOperator, ControlledIncrementer, SpecularReflectionOperator, SpeedSensitivePhaseShift, ) - from qlbm.lattice import CollisionlessLattice + from qlbm.lattice import MSLattice print("ok") .. testoutput:: diff --git a/docs/source/code/index.rst b/docs/source/code/index.rst index f869ba8..658b16c 100644 --- a/docs/source/code/index.rst +++ b/docs/source/code/index.rst @@ -4,7 +4,7 @@ Internal Documentation ================================ ``qlbm`` is made up of 4 main modules. -Together, the :ref:`base_components`, :ref:`cqlbm_components`, and :ref:`stqlbm_components` +Together, the :ref:`base_components`, :ref:`amplitude_components`, and :ref:`stqlbm_components` module handle the parameterized creation of quantum circuits that compose QBMs. The :ref:`lattice` module parses external information into quantum registers and provides uniform interfaces for underlying algorithms. @@ -14,12 +14,12 @@ The :ref:`tools` module contains miscellaneous utilities. .. toctree:: + lattice comps_base comps_cqlbm comps_collision comps_stqbm comps_lqlga - lattice infra tools diff --git a/docs/source/code/infra.rst b/docs/source/code/infra.rst index a25f96f..760ec0a 100644 --- a/docs/source/code/infra.rst +++ b/docs/source/code/infra.rst @@ -62,7 +62,7 @@ Results .. autoclass:: qlbm.infra.result.base.QBMResult :members: -.. autoclass:: qlbm.infra.result.collisionless_result.CollisionlessResult +.. autoclass:: qlbm.infra.result.amplitude_result.AmplitudeResult :members: .. autoclass:: qlbm.infra.result.spacetime_result.SpaceTimeResult diff --git a/docs/source/code/lattice.rst b/docs/source/code/lattice.rst index bf5fbbb..5d74337 100644 --- a/docs/source/code/lattice.rst +++ b/docs/source/code/lattice.rst @@ -24,12 +24,21 @@ Concretely, each :class:`.Lattice` fulfills the following functionality: #. Provide convenient indexing methods methods to access individual (or groups of) qubits based on their purpose. #. Encode additional information required for the automatic assembly of large quantum circuits. -.. autoclass:: qlbm.lattice.lattices.collisionless_lattice.CollisionlessLattice +.. autoclass:: qlbm.lattice.lattices.ms_lattice.AmplitudeLattice + :members: + +.. autoclass:: qlbm.lattice.lattices.ms_lattice.MSLattice + :members: + +.. autoclass:: qlbm.lattice.lattices.ab_lattice.ABLattice :members: .. autoclass:: qlbm.lattice.lattices.spacetime_lattice.SpaceTimeLattice :members: +.. autoclass:: qlbm.lattice.lattices.lqlga_lattice.LQLGALattice + :members: + .. _geometry: Geometry From 8619a377b501701ad3cf051786d35b9e0a5aca1c Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Thu, 13 Nov 2025 12:21:15 +0100 Subject: [PATCH 43/61] Update entry page to documentation website --- docs/source/index.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 5d0dfa1..a67284c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,7 +9,7 @@ On this website, you can find the :ref:`internal_docs` of the source code compon A paper describing `qlbm` in detail is available on `here `_ :cite:p:`qlbm`. ``qlbm`` is made up of 4 main modules. -Together, the :ref:`base_components`, :ref:`cqlbm_components`, :ref:`stqlbm_components`, and :ref:`lqlga_components` +Together, the :ref:`base_components`, :ref:`amplitude_components`, :ref:`stqlbm_components`, and :ref:`lqlga_components` modules handle the parameterized creation of quantum circuits that compose QBMs. The :ref:`lattice` module parses external information into quantum registers and provides uniform interfaces for underlying algorithms. @@ -25,6 +25,12 @@ The :ref:`tools` module contains miscellaneous utilities. #. **L**\ inear \ **Q**\ uantum **L**\ attice **G**\ as **A**\ utomata (LQLGA) described in :cite:p:`spacetime2`, :cite:p:`lqlga1`, and :cite:p:`lqlga2`. +.. card:: Intro to QLBM + :link: internal_docs + :link-type: ref + + :fas:`lightbulb;sd-text-primary` Check out the basics. + .. card:: Internal Documentation :link: internal_docs :link-type: ref From 0a007b2852f87da78b45e563f4a8f32a7fdfd555 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Thu, 13 Nov 2025 12:21:57 +0100 Subject: [PATCH 44/61] Fix bug in CQLBM delegation --- qlbm/components/cqlbm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qlbm/components/cqlbm.py b/qlbm/components/cqlbm.py index 6030778..4ad4290 100644 --- a/qlbm/components/cqlbm.py +++ b/qlbm/components/cqlbm.py @@ -77,9 +77,9 @@ def __init__( @override def create_circuit(self): if isinstance(self.lattice, MSLattice): - return MSQLBM(cast(MSLattice, self.lattice), self.logger) + return MSQLBM(cast(MSLattice, self.lattice), self.logger).circuit elif isinstance(self.lattice, ABLattice): - return ABQLBM(cast(ABLattice, self.lattice), self.logger) + return ABQLBM(cast(ABLattice, self.lattice), self.logger).circuit else: raise LatticeException( f"CQLBM does not support lattices of type {type(self.lattice)}" From 894fcae38ecfed8a0e154cb28a2daac4fc312b4a Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 12:06:21 +0100 Subject: [PATCH 45/61] Add support for different encodings under ABLattice --- qlbm/lattice/lattices/ab_lattice.py | 19 +++++++++++++------ qlbm/lattice/lattices/base.py | 13 +++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/qlbm/lattice/lattices/ab_lattice.py b/qlbm/lattice/lattices/ab_lattice.py index 6129b00..db8e601 100644 --- a/qlbm/lattice/lattices/ab_lattice.py +++ b/qlbm/lattice/lattices/ab_lattice.py @@ -1,4 +1,4 @@ -"""Implementation of the Amplitude-Based Encoding (ABE) lattice for generic DdQq discretizations.""" +"""Implementation of the Amplitude-Based (AB) encoding lattice for generic DdQq discretizations.""" from logging import getLogger from typing import Dict, List, Tuple @@ -7,6 +7,7 @@ from qiskit import QuantumCircuit, QuantumRegister from typing_extensions import override +from qlbm.components.ab.encodings import ABEncodingType from qlbm.lattice.geometry.shapes.base import Shape from qlbm.lattice.spacetime.properties_base import ( LatticeDiscretization, @@ -255,9 +256,11 @@ def __num_obstacle_qubits(self) -> int: def get_registers(self) -> Tuple[List[QuantumRegister], ...]: """Generates the encoding-specific register required for the streaming step. - For this encoding, different registers encode (i) the velocity direction, - (ii) the velocity discretization, (iii) the velocity ancillae, - and (iv) the grid encoding. + For this encoding, different registers encode + (i) the logarithmically compressed grid, + (ii) the logarithmically compressed discrete velocities, + (iii) the comparator qubits, + (iv) the object qubits. Returns ------- @@ -297,8 +300,12 @@ def logger_name(self) -> str: gp_string += f"{gp + 1}" if c < len(self.num_gridpoints) - 1: gp_string += "x" - return f"abelattice-{self.num_dims}d-{gp_string}-{len(flatten(list(self.shapes.values())))}-obstacle" + return f"ablattice-{self.num_dims}d-{gp_string}-{len(flatten(list(self.shapes.values())))}-obstacle" @override def has_multiple_geometries(self): - return False # multiple geometries unsupported for CQBM + return False # multiple geometries unsupported for ABQLBM right now + + @override + def get_encoding(self) -> ABEncodingType: + return ABEncodingType.AB diff --git a/qlbm/lattice/lattices/base.py b/qlbm/lattice/lattices/base.py index 977bd94..edda4a1 100644 --- a/qlbm/lattice/lattices/base.py +++ b/qlbm/lattice/lattices/base.py @@ -7,6 +7,7 @@ from qiskit import QuantumCircuit, QuantumRegister +from qlbm.components.ab.encodings import ABEncodingType from qlbm.lattice.geometry.shapes.base import Shape from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.geometry.shapes.circle import Circle @@ -622,3 +623,15 @@ def velocity_index(self, dim: int | None = None) -> List[int]: If the dimension does not exist. """ pass + + @abstractmethod + def get_encoding(self) -> ABEncodingType: + """ + Get the type of encoding this lattice implements. + + Returns + ------- + ABEncodingType + The encoding of this lattice. + """ + pass From 952c352f9f5fd69b01e2569097d98f19824dfabf Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 12:08:11 +0100 Subject: [PATCH 46/61] Add encodings enumerator for AB subtypes --- qlbm/components/ab/encodings.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 qlbm/components/ab/encodings.py diff --git a/qlbm/components/ab/encodings.py b/qlbm/components/ab/encodings.py new file mode 100644 index 0000000..5f6b538 --- /dev/null +++ b/qlbm/components/ab/encodings.py @@ -0,0 +1,14 @@ +from matplotlib.pylab import Enum + + +class ABEncodingType(Enum): + r"""Enumerator for the modes of quantum comparator circuits. + + The modes are as follows: + + * (1, ``ABEncodingType.AB``, The regular AB encoding.); + * (2, ``ABEncodingType.OH``, The one-hot encoding.). + """ + + AB = (1, ) + OH = (2, ) From d33cca8ce43a060c1279b0f17465118816434109 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 12:08:31 +0100 Subject: [PATCH 47/61] Add OH lattice --- qlbm/lattice/lattices/oh_lattice.py | 160 ++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 qlbm/lattice/lattices/oh_lattice.py diff --git a/qlbm/lattice/lattices/oh_lattice.py b/qlbm/lattice/lattices/oh_lattice.py new file mode 100644 index 0000000..29b1208 --- /dev/null +++ b/qlbm/lattice/lattices/oh_lattice.py @@ -0,0 +1,160 @@ +"""Implementation of the One-Hot (OH) encoding lattice for generic DdQq discretizations.""" + +from logging import getLogger +from typing import Dict, List, Tuple + +from numpy import ceil, log2 +from qiskit import QuantumCircuit, QuantumRegister +from typing_extensions import override + +from qlbm.components.ab.encodings import ABEncodingType +from qlbm.lattice.geometry.shapes.base import Shape +from qlbm.lattice.lattices.base import AmplitudeLattice +from qlbm.lattice.spacetime.properties_base import ( + LatticeDiscretization, + LatticeDiscretizationProperties, +) +from qlbm.tools.exceptions import LatticeException +from qlbm.tools.utils import dimension_letter, flatten, is_two_pow + +from .ab_lattice import ABLattice + + +class OHLattice(ABLattice): + discretization: LatticeDiscretization + """The discretization of the lattice, one of :class:`.LatticeDiscretization`.""" + + num_gridpoints: List[int] + """The number of gridpoints in each dimension of the lattice. + **Important** : for easier compatibility with binary arithmetic, the number of gridpoints + specified in the input dictionary is one larger than the one held in the ``Lattice``.""" + + shapes: Dict[str, List[Shape]] + """The shapes of the lattice, which are used to define the geometry of the lattice. + The key consists of the type of the shape and the name of the shape, e.g. "bounceback" or "specular". + """ + + num_base_qubits: int + """The number of qubits required to represent the lattice.""" + + registers: Tuple[QuantumRegister, ...] + """The registers of the lattice.""" + + def __init__( + self, + lattice_data, + logger=getLogger("qlbm"), + ): + super().__init__(lattice_data, logger) + self.num_gridpoints, self.num_velocities, self.shapes, self.discretization = ( + self.parse_input_data(lattice_data) + ) # type: ignore + self.geometries: List[Dict[str, List[Shape]]] = [self.shapes] + self.num_dims = len(self.num_gridpoints) + self.num_velocities_per_point = ( + LatticeDiscretizationProperties.get_num_velocities(self.discretization) + ) + + for dim in range(self.num_dims): + if not is_two_pow(self.num_gridpoints[dim] + 1): # type: ignore + raise LatticeException( + f"Lattice has a number of grid points that is not divisible by 2 in dimension {dimension_letter(dim)}." + ) + + self.num_grid_qubits = int( + sum(map(lambda x: ceil(log2(x)), self.num_gridpoints)) + ) + self.num_velocity_qubits = self.num_velocities_per_point + self.num_base_qubits = self.num_grid_qubits + self.num_velocity_qubits + + self.num_obstacle_qubits = self.__num_obstacle_qubits() + self.num_comparator_qubits = 2 * (self.num_dims - 1) + self.num_ancilla_qubits = self.num_comparator_qubits + self.num_obstacle_qubits + + self.num_total_qubits = self.num_base_qubits + self.num_ancilla_qubits + + temporary_registers = self.get_registers() + ( + self.grid_registers, + self.velocity_registers, + self.ancilla_comparator_register, + self.ancilla_object_register, + ) = temporary_registers + + self.registers = tuple(flatten(temporary_registers)) + self.circuit = QuantumCircuit(*self.registers) + + @override + def get_registers(self) -> Tuple[List[QuantumRegister], ...]: + """Generates the encoding-specific register required for the streaming step. + + For this encoding, different registers encode + (i) the logarithmically compressed grid, + (ii) the uncompressed discrete velocities, + (iii) the comparator qubits, + (iv) the object qubits. + + Returns + ------- + List[int] + Tuple[QuantumRegister]: The 4-tuple of qubit registers encoding the streaming step. + """ + # d ancilla qubits used to conditionally reflect velocities + ancilla_object_register = [ + QuantumRegister(self.num_obstacle_qubits, name="a_o") + ] + + # 2(d-1) ancilla qubits + ancilla_comparator_register = [ + QuantumRegister(self.num_comparator_qubits, name="a_c") + ] + + # Velocity qubits + velocity_registers = [QuantumRegister(self.num_velocity_qubits, name="v")] + + # Grid qubits + grid_registers = [ + QuantumRegister(gp.bit_length(), name=f"g_{dimension_letter(c)}") + for c, gp in enumerate(self.num_gridpoints) + ] + + return ( + grid_registers, + velocity_registers, + ancilla_comparator_register, + ancilla_object_register, + ) + + @override + def logger_name(self) -> str: + gp_string = "" + for c, gp in enumerate(self.num_gridpoints): + gp_string += f"{gp + 1}" + if c < len(self.num_gridpoints) - 1: + gp_string += "x" + return f"ohlattice-{self.num_dims}d-{gp_string}-{len(flatten(list(self.shapes.values())))}-obstacle" + + @override + def has_multiple_geometries(self): + return False # multiple geometries unsupported for ABQLBM right now + + @override + def get_encoding(self) -> ABEncodingType: + return ABEncodingType.OH + + def __num_obstacle_qubits(self) -> int: + all_obstacle_bounceback: bool = len( + [ + b + for b in flatten(list(self.shapes.values())) + if b.boundary_condition == "bounceback" + ] + ) == len(flatten(list(self.shapes.values()))) + if all_obstacle_bounceback: + # A single qubit suffices to determine + # Whether particles have streamed inside the object + return 1 + # If there is at least one object with specular reflection + # 2 ancilla qubits are required for velocity inversion + else: + return self.num_dims From c78591aa7aa62f3f8fc92cd5bf5a57086148dac3 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 12:09:10 +0100 Subject: [PATCH 48/61] Adapt ABQLBM components to work with OH lattice --- qlbm/components/ab/__init__.py | 2 + qlbm/components/ab/initial.py | 78 +++++++- qlbm/components/ab/reflection.py | 300 ++++++++++++++++++++----------- qlbm/components/ab/streaming.py | 85 +++++---- 4 files changed, 321 insertions(+), 144 deletions(-) diff --git a/qlbm/components/ab/__init__.py b/qlbm/components/ab/__init__.py index e6d57d3..c1f69b3 100644 --- a/qlbm/components/ab/__init__.py +++ b/qlbm/components/ab/__init__.py @@ -1,6 +1,7 @@ """Primitives and operators for the Amplitude Based QLBM.""" from .ab import ABQLBM +from .encodings import ABEncodingType from .initial import ABInitialConditions from .measurement import ABGridMeasurement from .reflection import ABReflectionOperator, ABReflectionPermutation @@ -13,4 +14,5 @@ "ABReflectionOperator", "ABReflectionPermutation", "ABStreamingOperator", + "ABEncodingType", ] diff --git a/qlbm/components/ab/initial.py b/qlbm/components/ab/initial.py index 0f26dbb..bf294bb 100644 --- a/qlbm/components/ab/initial.py +++ b/qlbm/components/ab/initial.py @@ -3,12 +3,16 @@ from logging import Logger, getLogger from time import perf_counter_ns +import numpy as np from qiskit import QuantumCircuit +from qiskit.quantum_info import Operator from typing_extensions import override +from qlbm.components.ab.encodings import ABEncodingType from qlbm.components.base import LBMPrimitive from qlbm.components.common.primitives import TruncatedQFT from qlbm.lattice.lattices.ab_lattice import ABLattice +from qlbm.tools.exceptions import LatticeException class ABInitialConditions(LBMPrimitive): @@ -72,15 +76,71 @@ def __init__( def create_circuit(self) -> QuantumCircuit: circuit = QuantumCircuit(*self.lattice.registers) - circuit.compose( - TruncatedQFT( - self.lattice.num_velocity_qubits, - self.lattice.num_velocities_per_point, - self.logger, - ).circuit, - qubits=self.lattice.velocity_index(), - inplace=True, - ) + match self.lattice.get_encoding(): + case ABEncodingType.AB: + circuit.compose( + TruncatedQFT( + self.lattice.num_velocity_qubits, + self.lattice.num_velocities_per_point, + self.logger, + ).circuit, + qubits=self.lattice.velocity_index(), + inplace=True, + ) + case ABEncodingType.OH: + nq = int(np.ceil(np.log2(self.lattice.num_velocity_qubits))) + circuit.compose( + TruncatedQFT( + nq, + self.lattice.num_velocity_qubits, + self.logger, + ).circuit, + qubits=self.lattice.velocity_index()[:nq], + inplace=True, + ) + + circuit.compose( + self.__oh_permutation(), + qubits=self.lattice.velocity_index(), + inplace=True, + ) + case _: + raise LatticeException( + f"Encoding {self.lattice.get_encoding()} not supported." + ) + + return circuit + + def __oh_permutation(self) -> QuantumCircuit: + circuit = QuantumCircuit(*self.lattice.registers) + + n = self.lattice.num_velocity_qubits + dim = 2**n + + perm = [-1] * dim + used_rows = set() + + for j in range(9): + row = 1 << j # 2^j + perm[j] = row + used_rows.add(row) + + # Fill in the rest of the permutation arbitrarily but bijectively. + remaining_rows = [r for r in range(dim) if r not in used_rows] + k = 0 + for col in range(9, dim): + perm[col] = remaining_rows[k] + k += 1 + + U = np.zeros((dim, dim), dtype=complex) + for col in range(dim): + row = perm[col] + U[row, col] = 1.0 + + op = Operator(U) + + circuit = QuantumCircuit(n) + circuit.unitary(op, range(n), label="binary_to_onehot") return circuit diff --git a/qlbm/components/ab/reflection.py b/qlbm/components/ab/reflection.py index 9cb5b43..c29e1e3 100644 --- a/qlbm/components/ab/reflection.py +++ b/qlbm/components/ab/reflection.py @@ -9,12 +9,15 @@ from qiskit.circuit.library import MCMTGate, XGate from typing_extensions import override +from qlbm.components.ab.encodings import ABEncodingType from qlbm.components.ab.streaming import ABStreamingOperator from qlbm.components.base import LBMOperator, LBMPrimitive from qlbm.components.ms.specular_reflection import SpecularWallComparator from qlbm.lattice.geometry.encodings.ms import ReflectionPoint from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.lattices.ab_lattice import ABLattice +from qlbm.lattice.lattices.base import AmplitudeLattice +from qlbm.lattice.lattices.oh_lattice import OHLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import flatten, get_qubits_to_invert @@ -35,7 +38,12 @@ class ABReflectionOperator(LBMOperator): { "lattice": {"dim": {"x": 4, "y": 4}, "velocities": "d2q9"}, "geometry": [ - {"shape": "cuboid", "x": [1, 3], "y": [1, 3], "boundary": "bounceback"} + { + "shape": "cuboid", + "x": [1, 3], + "y": [1, 3], + "boundary": "bounceback", + } ], } ) @@ -45,7 +53,7 @@ class ABReflectionOperator(LBMOperator): """ - lattice: ABLattice + lattice: AmplitudeLattice def __init__( self, @@ -79,7 +87,7 @@ def __create_circuit_d2q9(self): circuit.compose(self.set_inside_wall_ancilla_state(block), inplace=True) circuit.compose( - self.reset_ancilla_of_point_state( + self.set_ancilla_of_point_state( flatten( [[(p, None) for p in block.corners_inside] for block in self.blocks] ), @@ -125,7 +133,7 @@ def __create_circuit_d2q9(self): # Re-reset the ancilla state of the populations that # Shouldn't have been flipped in the previous step circuit.compose( - self.reset_ancilla_of_point_state(point_data, ignore_velocity_data=False), + self.set_ancilla_of_point_state(point_data, ignore_velocity_data=False), inplace=True, ) @@ -233,36 +241,65 @@ def reset_outside_wall_ancilla_state(self, block: Block) -> QuantumCircuit: for v in block.get_lbm_wall_velocity_indices_to_reflect( self.lattice.discretization, dim, bool(bound) ): - qs = [ - self.lattice.velocity_index()[0] + q - for q in get_qubits_to_invert( - v, self.lattice.num_velocity_qubits - ) - ] - - if qs: - circuit.x(qs) - - control_qubits = ( - self.lattice.grid_index(wall.dim) - + self.lattice.ancillae_comparator_index() - + self.lattice.velocity_index() # The reset step is additionally controlled on the velocity register - ) - - target_qubits = self.lattice.ancillae_obstacle_index(0) - - circuit.compose( - MCMTGate( - XGate(), - len(control_qubits), - len(target_qubits), - ), - qubits=control_qubits + target_qubits, - inplace=True, - ) - - if qs: - circuit.x(qs) + match self.lattice.get_encoding(): + case ABEncodingType.AB: + qs = [ + self.lattice.velocity_index()[0] + q + for q in get_qubits_to_invert( + v, self.lattice.num_velocity_qubits + ) + ] + + if qs: + circuit.x(qs) + + control_qubits = ( + self.lattice.grid_index(wall.dim) + + self.lattice.ancillae_comparator_index() + + self.lattice.velocity_index() # The reset step is additionally controlled on the velocity register + ) + + target_qubits = self.lattice.ancillae_obstacle_index(0) + + circuit.compose( + MCMTGate( + XGate(), + len(control_qubits), + len(target_qubits), + ), + qubits=control_qubits + target_qubits, + inplace=True, + ) + + if qs: + circuit.x(qs) + case ABEncodingType.OH: + control_qubits = ( + self.lattice.grid_index(wall.dim) + + self.lattice.ancillae_comparator_index() + + [ + self.lattice.velocity_index()[ + v + ] # Only one velocity index required here + ] + ) + + target_qubits = self.lattice.ancillae_obstacle_index(0) + + circuit.compose( + MCMTGate( + XGate(), + len(control_qubits), + len(target_qubits), + ), + qubits=control_qubits + target_qubits, + inplace=True, + ) + + case _: + raise LatticeException( + f"Unsupported lattice encoding: {self.lattice.get_encoding()}" + ) if grid_qubit_indices_to_invert: circuit.x(grid_qubit_indices_to_invert) @@ -271,7 +308,7 @@ def reset_outside_wall_ancilla_state(self, block: Block) -> QuantumCircuit: return circuit - def reset_ancilla_of_point_state( + def set_ancilla_of_point_state( self, points_data: List[Tuple[ReflectionPoint, List[int]]], ignore_velocity_data: bool, @@ -298,51 +335,88 @@ def reset_ancilla_of_point_state( self.lattice.grid_index(0)[0] + qubit for qubit in point.qubits_to_invert ] - velocity_data = ( - [ - [ - self.lattice.velocity_index()[0] + qubit - for qubit in get_qubits_to_invert( - velocity_index, self.lattice.num_velocity_qubits - ) - ] - for velocity_index in velocities - ] - if not ignore_velocity_data - else [[]] - ) if grid_qubit_indices_to_invert: circuit.x(grid_qubit_indices_to_invert) - # Reset the state for each velocity we care about: - for velocity_qubit_indices_to_invert in velocity_data: - if velocity_qubit_indices_to_invert: - circuit.x(velocity_qubit_indices_to_invert) - - control_qubits = ( - self.lattice.grid_index() - + ( - self.lattice.velocity_index() + match self.lattice.get_encoding(): + case ABEncodingType.AB: + velocity_data = ( + [ + [ + self.lattice.velocity_index()[0] + qubit + for qubit in get_qubits_to_invert( + velocity_index, self.lattice.num_velocity_qubits + ) + ] + for velocity_index in velocities + ] if not ignore_velocity_data - else [] - ) # The reset step is additionally controlled on the velocity register - ) - - target_qubits = self.lattice.ancillae_obstacle_index(0) + else [[]] + ) - circuit.compose( - MCMTGate( - XGate(), - len(control_qubits), - len(target_qubits), - ), - qubits=control_qubits + target_qubits, - inplace=True, - ) + # Reset the state for each velocity we care about: + for velocity_qubit_indices_to_invert in velocity_data: + if velocity_qubit_indices_to_invert: + circuit.x(velocity_qubit_indices_to_invert) + + control_qubits = ( + self.lattice.grid_index() + + ( + self.lattice.velocity_index() + if not ignore_velocity_data + else [] + ) # The reset step is additionally controlled on the velocity register + ) - if velocity_qubit_indices_to_invert: - circuit.x(velocity_qubit_indices_to_invert) + target_qubits = self.lattice.ancillae_obstacle_index(0) + circuit.compose( + MCMTGate( + XGate(), + len(control_qubits), + len(target_qubits), + ), + qubits=control_qubits + target_qubits, + inplace=True, + ) + if velocity_qubit_indices_to_invert: + circuit.x(velocity_qubit_indices_to_invert) + case ABEncodingType.OH: + if ignore_velocity_data: + circuit.compose( + MCMTGate( + XGate(), + len(self.lattice.grid_index()), + len(self.lattice.ancillae_obstacle_index(0)), + ), + qubits=self.lattice.grid_index() + + self.lattice.ancillae_obstacle_index(0), + inplace=True, + ) + else: + for v in velocities: + control_qubits = ( + self.lattice.grid_index() + + ( + [self.lattice.velocity_index()[v]] + ) # Only one velocity control + ) + + target_qubits = self.lattice.ancillae_obstacle_index(0) + + circuit.compose( + MCMTGate( + XGate(), + len(control_qubits), + len(target_qubits), + ), + qubits=control_qubits + target_qubits, + inplace=True, + ) + case _: + raise LatticeException( + f"Unsupported lattice encoding: {self.encoding}" + ) if grid_qubit_indices_to_invert: circuit.x(grid_qubit_indices_to_invert) @@ -364,6 +438,7 @@ def permute_and_stream(self) -> QuantumCircuit: ABReflectionPermutation( self.lattice.num_velocity_qubits, self.lattice.discretization, + self.lattice.get_encoding(), self.logger, ) .circuit.control(1) @@ -396,10 +471,10 @@ class ABReflectionPermutation(LBMPrimitive): .. plot:: :include-source: - from qlbm.components.ab import ABReflectionPermutation + from qlbm.components.ab import ABEncodingType, ABReflectionPermutation from qlbm.lattice import LatticeDiscretization - ABReflectionPermutation(4, LatticeDiscretization.D2Q9).draw("mpl") + ABReflectionPermutation(4, LatticeDiscretization.D2Q9, ABEncodingType.AB).draw("mpl") """ @@ -413,16 +488,23 @@ class ABReflectionPermutation(LBMPrimitive): The lattice discretization the permutation adheres to. """ + encoding: ABEncodingType + """ + The type of encoding to permute for. + """ + def __init__( self, num_qubits: int, discretization: LatticeDiscretization, + encoding: ABEncodingType, logger: Logger = getLogger("qlbm"), ) -> None: super().__init__(logger) self.num_qubits = num_qubits self.discretization = discretization + self.encoding = encoding self.logger.info(f"Creating circuit {str(self)}...") circuit_creation_start_time = perf_counter_ns() @@ -436,38 +518,48 @@ def create_circuit(self) -> QuantumCircuit: if self.discretization == LatticeDiscretization.D2Q9: return self.__create_circuit_d2q9() - raise LatticeException("ABE reflection only currently supported in D2Q9") + raise LatticeException("AB reflection only currently supported in D2Q9") def __create_circuit_d2q9(self): circuit = QuantumCircuit(self.num_qubits) - - # 1 <-> 3 - circuit.x([0, 1]) - circuit.mcx([0, 1, 3], 2) - circuit.x([0, 1]) - - # 2 <-> 4 - circuit.x([0, 3]) - circuit.cx(1, 2) - circuit.mcx([0, 2, 3], 1) - circuit.cx(1, 2) - circuit.x([0, 3]) - - # 5 <-> 7 - circuit.x(0) - circuit.mcx([0, 1, 3], 2) - circuit.x(0) - - # 6 <-> 8 - circuit.cx(0, 1) - circuit.cx(0, 2) - circuit.x(3) - circuit.mcx([1, 2, 3], 0) - circuit.cx(0, 2) - circuit.cx(0, 1) - circuit.x(3) - - return circuit.reverse_bits() + match self.encoding: + case ABEncodingType.OH: + circuit.swap(1, 3) + circuit.swap(2, 4) + circuit.swap(5, 7) + circuit.swap(6, 8) + + case ABEncodingType.AB: + # 1 <-> 3 + circuit.x([0, 1]) + circuit.mcx([0, 1, 3], 2) + circuit.x([0, 1]) + + # 2 <-> 4 + circuit.x([0, 3]) + circuit.cx(1, 2) + circuit.mcx([0, 2, 3], 1) + circuit.cx(1, 2) + circuit.x([0, 3]) + + # 5 <-> 7 + circuit.x(0) + circuit.mcx([0, 1, 3], 2) + circuit.x(0) + + # 6 <-> 8 + circuit.cx(0, 1) + circuit.cx(0, 2) + circuit.x(3) + circuit.mcx([1, 2, 3], 0) + circuit.cx(0, 2) + circuit.cx(0, 1) + circuit.x(3) + + case _: + raise LatticeException(f"Unsupported lattice encoding: {self.encoding}") + + return circuit.reverse_bits() if self.encoding == ABEncodingType.AB else circuit @override def __str__(self) -> str: diff --git a/qlbm/components/ab/streaming.py b/qlbm/components/ab/streaming.py index c8aa19c..745606e 100644 --- a/qlbm/components/ab/streaming.py +++ b/qlbm/components/ab/streaming.py @@ -8,9 +8,10 @@ from qiskit.synthesis import synth_qft_full as QFT from typing_extensions import override +from qlbm.components.ab.encodings import ABEncodingType from qlbm.components.base import LBMOperator from qlbm.components.ms.streaming import PhaseShift -from qlbm.lattice.lattices.ab_lattice import ABLattice +from qlbm.lattice.lattices.base import AmplitudeLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import get_qubits_to_invert @@ -48,7 +49,7 @@ class ABStreamingOperator(LBMOperator): """ - lattice: ABLattice + lattice: AmplitudeLattice """The lattice to construct the component for.""" additional_control_qubit_indices: List[int] @@ -58,7 +59,7 @@ class ABStreamingOperator(LBMOperator): def __init__( self, - lattice: ABLattice, + lattice: AmplitudeLattice, additional_control_qubit_indices: List[int] = [], logger: Logger = getLogger("qlbm"), ) -> None: @@ -162,34 +163,56 @@ def __create_circuit_d2q9(self): positive = bool(1 - direction) for index in indices: - velocity_inversion_qubits = [ - self.lattice.num_grid_qubits + q - for q in get_qubits_to_invert( - index, self.lattice.num_velocity_qubits - ) - ] - if velocity_inversion_qubits: - circuit.x(velocity_inversion_qubits) - - circuit.compose( - PhaseShift( - num_qubits=len(self.lattice.grid_index(dim)), - positive=positive, - logger=self.logger, - ) - .circuit.control( - self.lattice.num_velocity_qubits - + len(self.additional_control_qubit_indices) - ) - .decompose(), - qubits=self.additional_control_qubit_indices - + self.lattice.velocity_index() - + self.lattice.grid_index(dim), - inplace=True, - ) - - if velocity_inversion_qubits: - circuit.x(velocity_inversion_qubits) + match self.lattice.get_encoding(): + case ABEncodingType.OH: + circuit.compose( + PhaseShift( + num_qubits=len(self.lattice.grid_index(dim)), + positive=positive, + logger=self.logger, + ) + .circuit.control( + 1 + len(self.additional_control_qubit_indices) + ) + .decompose(), + qubits=self.additional_control_qubit_indices + + [self.lattice.velocity_index()[index]] + + self.lattice.grid_index(dim), + inplace=True, + ) + case ABEncodingType.AB: + velocity_inversion_qubits = [ + self.lattice.num_grid_qubits + q + for q in get_qubits_to_invert( + index, self.lattice.num_velocity_qubits + ) + ] + if velocity_inversion_qubits: + circuit.x(velocity_inversion_qubits) + + circuit.compose( + PhaseShift( + num_qubits=len(self.lattice.grid_index(dim)), + positive=positive, + logger=self.logger, + ) + .circuit.control( + self.lattice.num_velocity_qubits + + len(self.additional_control_qubit_indices) + ) + .decompose(), + qubits=self.additional_control_qubit_indices + + self.lattice.velocity_index() + + self.lattice.grid_index(dim), + inplace=True, + ) + + if velocity_inversion_qubits: + circuit.x(velocity_inversion_qubits) + case _: + raise LatticeException( + f"Unsupported lattice encoding: {self.lattice.get_encoding()}" + ) circuit.compose( QFT(len(self.lattice.grid_index(dim)), inverse=True), From 183d93ace3c3e9dd582a23af6d9dfe62776c8943 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 14:17:54 +0100 Subject: [PATCH 49/61] Add MS encoding type to MSLattice --- qlbm/components/ab/encodings.py | 8 ++++++-- qlbm/lattice/lattices/ms_lattice.py | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/qlbm/components/ab/encodings.py b/qlbm/components/ab/encodings.py index 5f6b538..0b0901b 100644 --- a/qlbm/components/ab/encodings.py +++ b/qlbm/components/ab/encodings.py @@ -1,14 +1,18 @@ +"""Encoding utilities for amplitude-based lattices and components.""" + from matplotlib.pylab import Enum class ABEncodingType(Enum): - r"""Enumerator for the modes of quantum comparator circuits. + r"""Enumerator for the kinds of encodings under the Amplitude-Based encoding umbrella. The modes are as follows: * (1, ``ABEncodingType.AB``, The regular AB encoding.); - * (2, ``ABEncodingType.OH``, The one-hot encoding.). + * (2, ``ABEncodingType.OH``, The one-hot encoding.); + * (3, ``ABEncodingType.MS``, The multi-speed encoding.). """ AB = (1, ) OH = (2, ) + MS = (3, ) diff --git a/qlbm/lattice/lattices/ms_lattice.py b/qlbm/lattice/lattices/ms_lattice.py index 9b21636..93530f0 100644 --- a/qlbm/lattice/lattices/ms_lattice.py +++ b/qlbm/lattice/lattices/ms_lattice.py @@ -6,6 +6,7 @@ from qiskit import QuantumCircuit, QuantumRegister from typing_extensions import override +from qlbm.components.ab.encodings import ABEncodingType from qlbm.lattice.geometry.shapes.base import Shape from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import dimension_letter, flatten, is_two_pow @@ -460,7 +461,11 @@ def logger_name(self) -> str: if c < len(self.num_gridpoints) - 1: gp_string += "x" return f"{self.num_dims}d-{gp_string}-{len(self.shape_list)}-obstacle" - + @override def has_multiple_geometries(self): - return False # multiple geometries unsupported for CQBM + return False # multiple geometries unsupported for MS + + @override + def get_encoding(self): + return ABEncodingType.MS From dccca3f98c150101dca0b7fcaccb16ec5409412c Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 14:18:33 +0100 Subject: [PATCH 50/61] Add documentation to OHLattice and module --- qlbm/lattice/__init__.py | 2 + qlbm/lattice/lattices/__init__.py | 3 +- qlbm/lattice/lattices/oh_lattice.py | 87 ++++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/qlbm/lattice/__init__.py b/qlbm/lattice/__init__.py index 15f4b58..5d5620e 100644 --- a/qlbm/lattice/__init__.py +++ b/qlbm/lattice/__init__.py @@ -15,6 +15,7 @@ from .lattices import Lattice, MSLattice from .lattices.ab_lattice import ABLattice from .lattices.lqlga_lattice import LQLGALattice +from .lattices.oh_lattice import OHLattice from .lattices.spacetime_lattice import SpaceTimeLattice from .spacetime.properties_base import ( LatticeDiscretization, @@ -25,6 +26,7 @@ "Lattice", "ABLattice", "MSLattice", + "OHLattice", "SpaceTimeLattice", "LQLGALattice", "DimensionalReflectionData", diff --git a/qlbm/lattice/lattices/__init__.py b/qlbm/lattice/lattices/__init__.py index 5cc7f25..11f49b3 100644 --- a/qlbm/lattice/lattices/__init__.py +++ b/qlbm/lattice/lattices/__init__.py @@ -3,6 +3,7 @@ from .base import Lattice from .lqlga_lattice import LQLGALattice from .ms_lattice import MSLattice +from .oh_lattice import OHLattice from .spacetime_lattice import SpaceTimeLattice -__all__ = ["Lattice", "MSLattice", "SpaceTimeLattice", "LQLGALattice"] +__all__ = ["Lattice", "MSLattice", "SpaceTimeLattice", "LQLGALattice", "OHLattice"] diff --git a/qlbm/lattice/lattices/oh_lattice.py b/qlbm/lattice/lattices/oh_lattice.py index 29b1208..06e1864 100644 --- a/qlbm/lattice/lattices/oh_lattice.py +++ b/qlbm/lattice/lattices/oh_lattice.py @@ -9,7 +9,6 @@ from qlbm.components.ab.encodings import ABEncodingType from qlbm.lattice.geometry.shapes.base import Shape -from qlbm.lattice.lattices.base import AmplitudeLattice from qlbm.lattice.spacetime.properties_base import ( LatticeDiscretization, LatticeDiscretizationProperties, @@ -21,6 +20,92 @@ class OHLattice(ABLattice): + r""" + Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.ABQLBM` for the **O** ne- **H** ot encoding. + + In the OH encoding, the grid is compressed into logarithmically many qubits, + while the the velocity register is not. + For a :math:`1024 \times 1024` lattice with a :math:`D_2Q_9` discretization, the + OH encoding requires `2\log 1024 + 9 = 29` qubits. + + Each of the discrete velocities is assigned a vector :math:`\ket{\mathbf{e}_j}`, + with entry :math:`1` at index :math:`j` and :math:`0` everywhere else. + + This lattice is only built from :math:`D_dQ_q` specifications. + For multi-speed implementations, see :class:`.MSQLBM` and :class:`.MSLattice`. + For the fully compressed velocity register counterpart, see :class:`.ABLattice`. + + The registers encoded in the lattice and their accessors are given below. + For the size of each register, + :math:`N_{g_j}` is the number of grid points of dimension :math:`j` (i.e., 64, 128), + :math:`q` is the number of discrete velocities, for instance, 9. + + .. list-table:: Register allocation + :widths: 25 25 25 50 + :header-rows: 1 + + * - Register + - Size + - Access Method + - Description + * - :attr:`grid_registers` + - :math:`\Sigma_{1\leq j \leq d} \left \lceil{\log N_{g_j}} \right \rceil` + - :meth:`grid_index` + - The qubits encoding the physical grid. + * - :attr:`velocity_registers` + - :math:`q` + - :meth:`velocity_index` + - The qubits encoding the :math:`q` discrete velocities. + * - :attr:`ancilla_obstacle_register` + - :math:`1` + - :meth:`ancillae_obstacle_index` + - The qubits used to detect whether particles have streamed into obstacles. Used for reflection. + * - :attr:`ancilla_comparator_register` + - :math:`2(d-1)` + - :meth:`ancillae_comparator_index` + - The qubits used to for :class:`.Comparator`\ s. Used for reflection. + + A lattice can be constructed from from either an input file or a Python dictionary: + + .. code-block:: json + + { + "lattice": { + "dim": { + "x": 16, + "y": 16 + }, + "velocities": "d2q9" + }, + "geometry": [ + { + "x": [9, 12], + "y": [3, 6], + "boundary": "bounceback" + }, + { + "x": [9, 12], + "y": [9, 12], + "boundary": "bounceback" + } + ] + } + + The register setup can be visualized by constructing a lattice object: + + .. plot:: + :include-source: + + from qlbm.lattice import OHLattice + + OHLattice( + { + "lattice": {"dim": {"x": 8, "y": 8}, "velocities": "D2Q9"}, + "geometry": [], + } + ).circuit.draw("mpl") + """ + discretization: LatticeDiscretization """The discretization of the lattice, one of :class:`.LatticeDiscretization`.""" From bf9dcaa5957ac1536b861c357ee39bbbd2c1aaa1 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 14:19:32 +0100 Subject: [PATCH 51/61] Fix test and import --- qlbm/components/ab/reflection.py | 1 - test/unit/ab/ab_reflection_permutation_test.py | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/qlbm/components/ab/reflection.py b/qlbm/components/ab/reflection.py index c29e1e3..35265b4 100644 --- a/qlbm/components/ab/reflection.py +++ b/qlbm/components/ab/reflection.py @@ -17,7 +17,6 @@ from qlbm.lattice.geometry.shapes.block import Block from qlbm.lattice.lattices.ab_lattice import ABLattice from qlbm.lattice.lattices.base import AmplitudeLattice -from qlbm.lattice.lattices.oh_lattice import OHLattice from qlbm.lattice.spacetime.properties_base import LatticeDiscretization from qlbm.tools.exceptions import LatticeException from qlbm.tools.utils import flatten, get_qubits_to_invert diff --git a/test/unit/ab/ab_reflection_permutation_test.py b/test/unit/ab/ab_reflection_permutation_test.py index 05ea770..26dc143 100644 --- a/test/unit/ab/ab_reflection_permutation_test.py +++ b/test/unit/ab/ab_reflection_permutation_test.py @@ -2,9 +2,10 @@ from qiskit import QuantumCircuit, transpile from qiskit_aer import AerSimulator +from qlbm.components.ab.encodings import ABEncodingType from qlbm.components.ab.reflection import ABReflectionPermutation from qlbm.lattice.spacetime.properties_base import LatticeDiscretization -from qlbm.tools.utils import bit_value, get_qubits_to_invert +from qlbm.tools.utils import bit_value @pytest.mark.parametrize( @@ -22,7 +23,10 @@ def test_reflectionpermutation_outcomes_d2q9(permutation_outcome_pairs): qc.x(q) qc.compose( - ABReflectionPermutation(nq, LatticeDiscretization.D2Q9).circuit, inplace=True + ABReflectionPermutation( + nq, LatticeDiscretization.D2Q9, ABEncodingType.AB + ).circuit, + inplace=True, ) qc.measure_all() tqc = transpile(qc, sim, optimization_level=0) From 28a552df4f1d3a0570c2b68157a0fa2426eadad4 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 17:36:32 +0100 Subject: [PATCH 52/61] Fix documentation typo --- qlbm/lattice/lattices/oh_lattice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qlbm/lattice/lattices/oh_lattice.py b/qlbm/lattice/lattices/oh_lattice.py index 06e1864..1583b65 100644 --- a/qlbm/lattice/lattices/oh_lattice.py +++ b/qlbm/lattice/lattices/oh_lattice.py @@ -21,12 +21,12 @@ class OHLattice(ABLattice): r""" - Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.ABQLBM` for the **O** ne- **H** ot encoding. + Implementation of the :class:`.Lattice` base specific to the 2D and 3D :class:`.ABQLBM` for the One-Hot (OH) encoding. In the OH encoding, the grid is compressed into logarithmically many qubits, while the the velocity register is not. For a :math:`1024 \times 1024` lattice with a :math:`D_2Q_9` discretization, the - OH encoding requires `2\log 1024 + 9 = 29` qubits. + OH encoding requires :math:`2\log_2 1024 + 9 = 29` qubits. Each of the discrete velocities is assigned a vector :math:`\ket{\mathbf{e}_j}`, with entry :math:`1` at index :math:`j` and :math:`0` everywhere else. From a3dab11ef5467c3a94897098e47df2458b4f33b5 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 17:36:51 +0100 Subject: [PATCH 53/61] Update documentation website structure --- docs/source/code/comps_base.rst | 3 + docs/source/code/comps_collision.rst | 65 ------------ docs/source/code/comps_cqlbm.rst | 59 ++++------- docs/source/code/comps_lga.rst | 144 +++++++++++++++++++++++++++ docs/source/code/comps_lqlga.rst | 84 ---------------- docs/source/code/comps_stqbm.rst | 83 --------------- docs/source/code/index.rst | 6 +- docs/source/code/lattice.rst | 17 +++- docs/source/index.rst | 2 +- 9 files changed, 184 insertions(+), 279 deletions(-) delete mode 100644 docs/source/code/comps_collision.rst create mode 100644 docs/source/code/comps_lga.rst delete mode 100644 docs/source/code/comps_lqlga.rst delete mode 100644 docs/source/code/comps_stqbm.rst diff --git a/docs/source/code/comps_base.rst b/docs/source/code/comps_base.rst index e830f81..98949e4 100644 --- a/docs/source/code/comps_base.rst +++ b/docs/source/code/comps_base.rst @@ -20,6 +20,9 @@ Lattice Base .. autoclass:: qlbm.lattice.lattices.base.Lattice :members: +.. autoclass:: qlbm.lattice.lattices.base.AmplitudeLattice + :members: + .. autoclass:: qlbm.lattice.geometry.shapes.base.Shape :members: diff --git a/docs/source/code/comps_collision.rst b/docs/source/code/comps_collision.rst deleted file mode 100644 index 1eb7a13..0000000 --- a/docs/source/code/comps_collision.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. _comps_collision: - -==================================== -Collision Logic Classes -==================================== - -.. testcode:: - :hide: - - from qlbm.components import ( - EQCPermutation, - EQCRedistribution, - ) - from qlbm.lattice.spacetime.properties_base import ( - LatticeDiscretization, - LatticeDiscretizationProperties, - ) - - from qlbm.lattice.eqc import ( - EquivalenceClass, - EquivalenceClassGenerator, - ) - print("ok") - -.. testoutput:: - :hide: - - ok - - -This page contain collision-related components shared -between the :ref:`stqlbm_components` and :ref:`lqlga_components` algorithms. -Collision in these algorithms is based on the concept of equivalence classes -described in Section 4 of :cite:p:`spacetime2`, and follows a PRP (permute-redistribute-unpermute) approach. -All components of this module may be used for different variations of the Computational Basis State Encoding (CBSE) -of the velocity register. -The components of this module consist of: - -#. :ref:`collision_logic` classes that provide information about the lattice discretization and equivalence classes. -#. :ref:`collision_components` that implement the small parts of the collision operator. - -.. _collision_logic: - -Collision Logic Classes ------------------------------------ - -.. autoclass:: qlbm.lattice.spacetime.properties_base.LatticeDiscretization - -.. autoclass:: qlbm.lattice.spacetime.properties_base.LatticeDiscretizationProperties - -.. autoclass:: qlbm.lattice.eqc.EquivalenceClass - -.. autoclass:: qlbm.lattice.eqc.EquivalenceClassGenerator - -.. _collision_components: - -Collision Components ------------------------------------ - -.. autoclass:: qlbm.components.common.EQCPermutation - -.. autoclass:: qlbm.components.common.EQCRedistribution - -.. autoclass:: qlbm.components.common.EQCCollisionOperator - diff --git a/docs/source/code/comps_cqlbm.rst b/docs/source/code/comps_cqlbm.rst index de179ef..ac97ebe 100644 --- a/docs/source/code/comps_cqlbm.rst +++ b/docs/source/code/comps_cqlbm.rst @@ -31,7 +31,10 @@ At the moment, this includes two algorihtms: #. The "regular" Amplitude-Based Collisionless QLBM: ABQLBM, #. The Multi-Speed (MS) Collisionless QLBM: MSQLBM. -Both algorithms are instances of the Collisionless QLBM (CQLBM), also known as the +:class:`.ABQLBM` uses :class:`.ABLattice`\ s and :class:`.OHLattice`\ s, while :class:`.MSQLBM` is built from :class:`.MSLattice`\ s. +The general interface of :class:`.CQLBM` is built from all 3 lattice instances and delegeates to the appropriate implementation automatically. + +Both algorithms are instances of the Collisionless QLBM (:class:`.CQLBM`), also known as the Quantum Transport Method (QTM). Both algorithms compress the grid and the vnumber of discrete velocities into :math:`N_g\cdot N_v \mapsto \lceil \log_2 N_g \rceil + \lceil \log_2 N_v \rceil` qubits. @@ -41,18 +44,18 @@ The implementation of the algorithms was first described in :cite:p:`collisionle At its core, the CQLBM algorithm manipulates the particle probability distribution in an amplitude-based encoding of the quantum state. -This happens in several distinct steps: +The :ref:`cqlbm_e2e` can be broken down into several distinct steps: -#. Initial conditions prepare the starting state of the probability distribution function. +#. :ref:`cqlbm_initial` conditions prepare the starting state of the probability distribution function. #. :ref:`cqlbm_streaming` circuits increment or decrement the position of particles in physical space through QFT-based streaming. #. :ref:`cqlbm_reflection` circuits apply boundary conditions that affect particles that come in contact with solid obstacles. Reflection places those particles back in the appropriate position of the fluid domain. -#. Measurement operations extract information out of the quantum state, which can later be post-processed classically. +#. :ref:`cqlbm_measurement` operations extract information out of the quantum state, which can later be post-processed classically. This page documents the individual components that make up the CQLBM algorithm. Subsections follow a top-down approach, where end-to-end operators are introduced first, before being broken down into their constituent parts. -.. _e2e: +.. _cqlbm_e2e: End-to-end algorithms ---------------------------------- @@ -63,14 +66,22 @@ End-to-end algorithms .. autoclass:: qlbm.components.ab.ABQLBM +.. _cqlbm_initial: + +Initial Conditions +----------------------------------- + +.. autoclass:: qlbm.components.ms.primitives.MSInitialConditions + +.. autoclass:: qlbm.components.ms.primitives.MSInitialConditions3DSlim + +.. autoclass:: qlbm.components.ab.initial.ABInitialConditions + .. _cqlbm_streaming: Streaming ---------------------------------- -MSQLBM -********************************** - .. autoclass:: qlbm.components.ms.streaming.MSStreamingOperator .. autoclass:: qlbm.components.ms.streaming.StreamingAncillaPreparation @@ -83,9 +94,6 @@ MSQLBM .. autoclass:: qlbm.components.ms.streaming.PhaseShift -ABQLBM -********************************** - .. autoclass:: qlbm.components.ab.streaming.ABStreamingOperator .. _cqlbm_reflection: @@ -93,9 +101,6 @@ ABQLBM Reflection ---------------------------------- -MSQLBM Reflection -********************************** - .. autoclass:: qlbm.components.ms.bounceback_reflection.BounceBackReflectionOperator :members: @@ -112,37 +117,13 @@ MSQLBM Reflection .. autoclass:: qlbm.components.ms.primitives.ComparatorMode -ABQLBM Reflection -********************************** - .. autoclass:: qlbm.components.ab.reflection.ABReflectionOperator -.. _cqlbm_others: - -Initial Conditions ------------------------------------ - -MSQLBM Initial Conditions -********************************** - -.. autoclass:: qlbm.components.ms.primitives.MSInitialConditions - -.. autoclass:: qlbm.components.ms.primitives.MSInitialConditions3DSlim - -ABQLBM Initial Conditions -********************************** - -.. autoclass:: qlbm.components.ab.initial.ABInitialConditions +.. _cqlbm_measurement: Measurement ----------------------------------- -MSQLBM Measurement -********************************** - .. autoclass:: qlbm.components.ms.primitives.GridMeasurement -ABQLBM Measurement -********************************** - .. autoclass:: qlbm.components.ab.measurement.ABGridMeasurement \ No newline at end of file diff --git a/docs/source/code/comps_lga.rst b/docs/source/code/comps_lga.rst new file mode 100644 index 0000000..71a16a2 --- /dev/null +++ b/docs/source/code/comps_lga.rst @@ -0,0 +1,144 @@ +.. _qlga_components: + +==================================== +QLGA Circuits +==================================== + +.. testcode:: + :hide: + + from qlbm.components import ( + LQLGA, + LQGLAInitialConditions, + LQLGAGridVelocityMeasurement, + LQLGAReflectionOperator, + LQLGAStreamingOperator, + ) + from qlbm.components import ( + CQLBM, + MSStreamingOperator, + ControlledIncrementer, + SpecularReflectionOperator, + SpeedSensitivePhaseShift, + ) + from qlbm.lattice import MSLattice, LQLGALattice + print("ok") + +.. testoutput:: + :hide: + + ok + + +This page contains documentation about the quantum circuits that make up the +**L**\ attice **G**\ as **A**\ utomata (LGA) algorithms of ``qlbm``. +This includes two aglorithms: + +#. **S**\ pace-\ **T**\ ime **Q**\ uantum **L**\ attice **B**\ oltzmann **M**\ ethod (STQLBM), described in :cite:p:`spacetime` and extended in :cite:p:`spacetime2`. +#. **L**\ inear **Q**\ uantum **L**\ attice **G**\ as **A**\ utomata (LQLGA), :cite:p:`lqlga1`, :cite:p:`lqlga2`. + +At its core, the Space-Time QLBM uses an extended computational basis state +encoding that that circumvents the non-locality of the streaming +step by including additional information from neighboring grid points. +This happens in several distinct steps: +The LQGLA encodes a lattice of :math:`N_g` gridpoints with :math:`q` discrete velocities +each into :math:`N_g \cdot q` qubits. +LQLGA can be seen as the "limit" of the extended computational basis state encoding that is the Space-Time encoding. +For both algorithms, time-evolution of the system consists of the following steps: + +#. :ref:`lqlga_initial` prepare the starting state of the flow field. +#. :ref:`lqlga_streaming` move particles across gridpoints according to the velocity discretization. +#. :ref:`lqlga_reflection` circuits apply boundary conditions that affect particles that come in contact with solid obstacles. Reflection places those particles back in the appropriate position of the fluid domain. +#. :ref:`lqlga_collision` operators create superposed local configurations of velocity profiles. +#. :ref:`lqlga_measurement` operations extract information out of the quantum state, which can later be post-processed classically. + +This page documents the individual components that make up the CQLBM algorithm. +Subsections follow a top-down approach, where end-to-end operators are introduced first, +before being broken down into their constituent parts. + +.. warning:: + STQLBM and LQLGA are a based on typical :math:`D_dQ_q` discretizations. + The current implementation only supports :math:`D_1Q_2`, :math:`D_1Q_3`, and :math:`D_2Q_4` for one time step + with inexact restarts through ``qlbm``\ 's reinitialization mechanism. + LQLGA only supports :math:`D_1Q_3`. + +.. note:: + Need to work with a different discretization or want to work together? Reach out at ``qcfd-ewi@tudelft.nl``. + +.. _lqlga_e2e: + +End-to-end algorithms +---------------------------------- + +.. autoclass:: qlbm.components.spacetime.spacetime.SpaceTimeQLBM + +.. autoclass:: qlbm.components.LQLGA + +.. _lqlga_initial: + +Initial Conditions +---------------------------------- + +.. autoclass:: qlbm.components.spacetime.initial.PointWiseSpaceTimeInitialConditions + +.. autoclass:: qlbm.components.LQGLAInitialConditions + +.. _lqlga_streaming: + +Streaming +---------------------------------- + +.. autoclass:: qlbm.components.spacetime.streaming.SpaceTimeStreamingOperator + +.. autoclass:: qlbm.components.LQLGAStreamingOperator + +.. _lqlga_reflection: + +Reflection +---------------------------------- + +.. autoclass:: qlbm.components.LQLGAReflectionOperator + +.. _lqlga_collision: + +Collision +----------------------------------- + +The collision module contains collision operators and adjacent logic classes. +The former implements the circuits that perform collision in computational basis state encodings, +while the latter contains useful abstractions that circuits build on top of. +Collision in LGA algorithms is based on the concept of equivalence classes +described in Section 4 of :cite:p:`spacetime2`, and follows a permute-redistribute-unpermute (PRP) approach. +All components of this module may be used for different variations of the Computational Basis State Encoding (CBSE) +of the velocity register. +The components of this module consist of: + +.. autoclass:: qlbm.components.spacetime.collision.GenericSpaceTimeCollisionOperator + +.. autoclass:: qlbm.components.spacetime.collision.SpaceTimeD2Q4CollisionOperator + +.. autoclass:: qlbm.components.GenericLQLGACollisionOperator + +.. autoclass:: qlbm.components.common.EQCPermutation + +.. autoclass:: qlbm.components.common.EQCRedistribution + +.. autoclass:: qlbm.components.common.EQCCollisionOperator + +.. autoclass:: qlbm.lattice.spacetime.properties_base.LatticeDiscretization + +.. autoclass:: qlbm.lattice.spacetime.properties_base.LatticeDiscretizationProperties + +.. autoclass:: qlbm.lattice.eqc.EquivalenceClass + +.. autoclass:: qlbm.lattice.eqc.EquivalenceClassGenerator + + +.. _lqlga_measurement: + +Measurement +---------------------------------- + +.. autoclass:: qlbm.components.spacetime.measurement.SpaceTimeGridVelocityMeasurement + +.. autoclass:: qlbm.components.LQLGAGridVelocityMeasurement \ No newline at end of file diff --git a/docs/source/code/comps_lqlga.rst b/docs/source/code/comps_lqlga.rst deleted file mode 100644 index da2f7b4..0000000 --- a/docs/source/code/comps_lqlga.rst +++ /dev/null @@ -1,84 +0,0 @@ -.. _lqlga_components: - -==================================== -LQLGA Circuits -==================================== - -.. testcode:: - :hide: - - from qlbm.components import ( - LQLGA, - LQGLAInitialConditions, - LQLGAGridVelocityMeasurement, - LQLGAReflectionOperator, - LQLGAStreamingOperator, - ) - from qlbm.lattice import LQLGALattice - print("ok") - -.. testoutput:: - :hide: - - ok - - -This page contains documentation about the quantum circuits that make up the -**L**\ inear **Q**\ uantum **L**\ attice **G**\ as **A**\ utomata (LQLGA). -For a more in-depth depth description of the LQLGA algorithm, -we suggest :cite:p:`lqlga1`, :cite:p:`lqlga2`, and :cite:p:`spacetime2`. -The LQGLA encodes a lattice of :math:`N_g` gridpoints with :math:`q` discrete velocities -each into :math:`N_g \cdot q` qubits. -The time-evolution of the system consists of the following steps: - -#. :ref:`lqlga_initial` prepare the starting state of the flow field. -#. :ref:`lqlga_streaming` move particles across gridpoints according to the velocity discretization. -#. :ref:`lqlga_reflection` circuits apply boundary conditions that affect particles that come in contact with solid obstacles. Reflection places those particles back in the appropriate position of the fluid domain. -#. :ref:`lqlga_collision` operators create superposed local configurations of velocity profiles. -#. :ref:`lqlga_measurement` operations extract information out of the quantum state, which can later be post-processed classically. - -This page documents the individual components that make up the CQLBM algorithm. -Subsections follow a top-down approach, where end-to-end operators are introduced first, -before being broken down into their constituent parts. - -.. _lqlga_e2e: - -End-to-end algorithms ----------------------------------- - -.. autoclass:: qlbm.components.LQLGA - -.. _lqlga_initial: - -Initial Conditions ----------------------------------- - -.. autoclass:: qlbm.components.LQGLAInitialConditions - -.. _lqlga_streaming: - -Streaming ----------------------------------- - -.. autoclass:: qlbm.components.LQLGAStreamingOperator - -.. _lqlga_reflection: - -Reflection ----------------------------------- - -.. autoclass:: qlbm.components.LQLGAReflectionOperator - -.. _lqlga_collision: - -Collision ------------------------------------ - -.. autoclass:: qlbm.components.GenericLQLGACollisionOperator - -.. _lqlga_measurement: - -Measurement ----------------------------------- - -.. autoclass:: qlbm.components.LQLGAGridVelocityMeasurement \ No newline at end of file diff --git a/docs/source/code/comps_stqbm.rst b/docs/source/code/comps_stqbm.rst deleted file mode 100644 index a0c0a92..0000000 --- a/docs/source/code/comps_stqbm.rst +++ /dev/null @@ -1,83 +0,0 @@ -.. _stqlbm_components: - -==================================== -Space-Time Circuits -==================================== - -.. testcode:: - :hide: - - from qlbm.components import ( - CQLBM, - MSStreamingOperator, - ControlledIncrementer, - SpecularReflectionOperator, - SpeedSensitivePhaseShift, - ) - from qlbm.lattice import MSLattice - print("ok") - -.. testoutput:: - :hide: - - ok - - -This page contains documentation about the quantum circuits that make up the -**S**\ pace-\ **T**\ ime **Q**\ uantum **L**\ attice **B**\ oltzmann **M**\ ethod (STQLBM) -described in :cite:p:`spacetime`. -At its core, the Space-Time QLBM uses an extended computational basis state -encoding that that circumvents the non-locality of the streaming -step by including additional information from neighboring grid points. -This happens in several distinct steps: - -#. Initial conditions prepare the starting state of the probability distribution function. -#. :ref:`stqlbm_streaming` moves the position of particles to neighboring points according to their velocity. -#. :ref:`stqlbm_collision` locally changes the velocity profile of particles positioned at the same position in space. -#. Measurement operations extract information out of the quantum state, which can later be post-processed classically. - -This page documents the individual components that make up the STQLBM algorithm. -Subsections follow a top-down approach, where end-to-end operators are introduced first, -before being broken down into their constituent parts. - -.. warning:: - The STQBLM algorithm is a based on typical :math:`D_dQ_q` discretizations. - The current implementation only supports :math:`D_2Q_4` for one time step. - This is work in progress. - Multiple steps are possible through ``qlbm``\ 's reinitialization mechanism. - -.. _stqlbm_e2e: - -End-to-end algorithms ----------------------------------- - -.. autoclass:: qlbm.components.spacetime.spacetime.SpaceTimeQLBM - -.. _stqlbm_streaming: - -Streaming ----------------------------------- - -.. autoclass:: qlbm.components.spacetime.streaming.SpaceTimeStreamingOperator - -.. _stqlbm_collision: - -Collision ----------------------------------- - -The collision module contains collision operators and adjacent logic classes. -The former implements the circuits that perform collision in computational basis state encodings, -while the latter contains useful abstractions that circuits build on top of. - -.. autoclass:: qlbm.components.spacetime.collision.GenericSpaceTimeCollisionOperator - -.. autoclass:: qlbm.components.spacetime.collision.SpaceTimeD2Q4CollisionOperator - -.. _stqlbm_others: - -Others ------------------------------------ - -.. autoclass:: qlbm.components.spacetime.initial.PointWiseSpaceTimeInitialConditions - -.. autoclass:: qlbm.components.spacetime.measurement.SpaceTimeGridVelocityMeasurement \ No newline at end of file diff --git a/docs/source/code/index.rst b/docs/source/code/index.rst index 658b16c..5174384 100644 --- a/docs/source/code/index.rst +++ b/docs/source/code/index.rst @@ -4,7 +4,7 @@ Internal Documentation ================================ ``qlbm`` is made up of 4 main modules. -Together, the :ref:`base_components`, :ref:`amplitude_components`, and :ref:`stqlbm_components` +Together, the :ref:`base_components`, :ref:`amplitude_components`, and :ref:`qlga_components` module handle the parameterized creation of quantum circuits that compose QBMs. The :ref:`lattice` module parses external information into quantum registers and provides uniform interfaces for underlying algorithms. @@ -17,9 +17,7 @@ The :ref:`tools` module contains miscellaneous utilities. lattice comps_base comps_cqlbm - comps_collision - comps_stqbm - comps_lqlga + comps_lga infra tools diff --git a/docs/source/code/lattice.rst b/docs/source/code/lattice.rst index 5d74337..b9bb399 100644 --- a/docs/source/code/lattice.rst +++ b/docs/source/code/lattice.rst @@ -7,6 +7,17 @@ This page contains documentation about :ref:`lattices` and :ref:`geometry` class Lattices and geometry go hand-in-hand in that they do not themselves contain quantum components, but instead provide a convenient interface for accessing the information that determines the structure and composition of quantum components. +``qlbm`` supports the following kinds of lattices: + +#. Amplitude-Based (AB) lattices. These are the most common encdoings in QLBM literature. All AB lattices compress the grid into logarithmically many qubits. + #. :class:`.AmpltiudeLattice` is the abstract base class for all amplitude-based lattices. + #. :class:`.ABLattice` is the "standard" amplitude-based lattice, where both the grid and the velocities are logarithmically compressed. It supports only :math:`D_dQ_q` discretization. + #. :class:`.MSLattice` is the multi-speed lattice for the algorithm described in :cite:t:`collisionless`. It is the same as the :class:`.ABLattice`, except it supports different velocity discretizations. + #. :class:`.OHLattice` is the amplitude-based lattice where the grid is logarithmically compressed, but the :math:`D_dQ_q` velocities are not. It assigns one basis state per discrete velocity. + +#. LGA lattices. These rely on the computational basis state encoding (CBSE) and are used for QLGA algorithms. + #. :class:`.SpaceTimeLattice` is the realization of the space-time data encoding described in :cite:`spacetime` and :cite:`spacetime2`. It uses an expanded CBSE to accomodate multiple time steps. + #. :class:`.LQLGALattice` is the entirely uncompressed CBSE, encoding all velocity channels in the system. .. _lattices: @@ -24,15 +35,15 @@ Concretely, each :class:`.Lattice` fulfills the following functionality: #. Provide convenient indexing methods methods to access individual (or groups of) qubits based on their purpose. #. Encode additional information required for the automatic assembly of large quantum circuits. -.. autoclass:: qlbm.lattice.lattices.ms_lattice.AmplitudeLattice - :members: - .. autoclass:: qlbm.lattice.lattices.ms_lattice.MSLattice :members: .. autoclass:: qlbm.lattice.lattices.ab_lattice.ABLattice :members: +.. autoclass:: qlbm.lattice.lattices.oh_lattice.OHLattice + :members: + .. autoclass:: qlbm.lattice.lattices.spacetime_lattice.SpaceTimeLattice :members: diff --git a/docs/source/index.rst b/docs/source/index.rst index a67284c..865c11b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,7 +9,7 @@ On this website, you can find the :ref:`internal_docs` of the source code compon A paper describing `qlbm` in detail is available on `here `_ :cite:p:`qlbm`. ``qlbm`` is made up of 4 main modules. -Together, the :ref:`base_components`, :ref:`amplitude_components`, :ref:`stqlbm_components`, and :ref:`lqlga_components` +Together, the :ref:`base_components`, :ref:`amplitude_components`, and :ref:`qlga_components` modules handle the parameterized creation of quantum circuits that compose QBMs. The :ref:`lattice` module parses external information into quantum registers and provides uniform interfaces for underlying algorithms. From d9875ea460844c7320a7df7350bbc93876e4b5c4 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Fri, 14 Nov 2025 17:41:58 +0100 Subject: [PATCH 54/61] Fix type inference errors --- qlbm/components/ab/reflection.py | 2 +- qlbm/components/ms/specular_reflection.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qlbm/components/ab/reflection.py b/qlbm/components/ab/reflection.py index 35265b4..92aa411 100644 --- a/qlbm/components/ab/reflection.py +++ b/qlbm/components/ab/reflection.py @@ -414,7 +414,7 @@ def set_ancilla_of_point_state( ) case _: raise LatticeException( - f"Unsupported lattice encoding: {self.encoding}" + f"Unsupported lattice encoding: {self.lattice.get_encoding()}" ) if grid_qubit_indices_to_invert: circuit.x(grid_qubit_indices_to_invert) diff --git a/qlbm/components/ms/specular_reflection.py b/qlbm/components/ms/specular_reflection.py index fa68a87..1930ad3 100644 --- a/qlbm/components/ms/specular_reflection.py +++ b/qlbm/components/ms/specular_reflection.py @@ -22,6 +22,7 @@ ReflectionWall, ) from qlbm.lattice.geometry.shapes.block import Block +from qlbm.lattice.lattices.base import AmplitudeLattice from qlbm.tools.exceptions import CircuitException from qlbm.tools.utils import flatten @@ -39,7 +40,7 @@ class SpecularWallComparator(LBMPrimitive): ========================= ====================================================================== Attribute Summary ========================= ====================================================================== - :attr:`lattice` The :class:`.MSLattice` based on which the properties of the operator are inferred. + :attr:`lattice` The :class:`.AmplitudeLattice` based on which the properties of the operator are inferred. :attr:`wall` The :class:`.ReflectionWall` encoding the range spanned by the wall. :attr:`logger` The performance logger, by default ``getLogger("qlbm")``. ========================= ====================================================================== @@ -68,7 +69,7 @@ class SpecularWallComparator(LBMPrimitive): def __init__( self, - lattice: MSLattice, + lattice: AmplitudeLattice, wall: ReflectionWall, logger: Logger = getLogger("qlbm"), ) -> None: From bdbe308a0a5bb8f95c0243598d3ee04c857bb5d7 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 24 Nov 2025 15:53:17 +0100 Subject: [PATCH 55/61] Update LQLGA simulation notebook --- demos/simulation/lqlga_simulation.ipynb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/demos/simulation/lqlga_simulation.ipynb b/demos/simulation/lqlga_simulation.ipynb index 2ac16f8..91f62bc 100644 --- a/demos/simulation/lqlga_simulation.ipynb +++ b/demos/simulation/lqlga_simulation.ipynb @@ -10,12 +10,15 @@ "from qiskit_aer import AerSimulator\n", "\n", "from qlbm.components import EmptyPrimitive\n", - "from qlbm.components.lqlga.initial import LQGLAInitialConditions\n", - "from qlbm.components.lqlga.lqlga import LQLGA\n", - "from qlbm.components.lqlga.measurement import LQLGAGridVelocityMeasurement\n", + "from qlbm.components.lqlga import (\n", + " LQLGA,\n", + " LQGLAInitialConditions,\n", + " LQLGAGridVelocityMeasurement,\n", + ")\n", "from qlbm.infra import QiskitRunner, SimulationConfig\n", - "from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice\n", - "from qlbm.tools.utils import create_directory_and_parents\n" + "from qlbm.lattice import LQLGALattice\n", + "from qlbm.lattice import LQLGALattice\n", + "from qlbm.tools.utils import create_directory_and_parents" ] }, { @@ -25,8 +28,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qlbm.components.lqlga import LQGLAInitialConditions\n", - "from qlbm.lattice import LQLGALattice\n", + "\n", "\n", "lattice = LQLGALattice(\n", " {\n", From 764637e3b0bde8dadf76bb07f90c147f42960b95 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 24 Nov 2025 16:20:26 +0100 Subject: [PATCH 56/61] Update simulation noteooks --- ...s_simulation.ipynb => ab_simulation.ipynb} | 82 +++++++------- demos/simulation/lqlga_simulation.ipynb | 37 +++++-- demos/simulation/ms_simulation.ipynb | 101 ++++++++++++++++++ demos/simulation/spacetime_simulation.ipynb | 25 ++++- 4 files changed, 190 insertions(+), 55 deletions(-) rename demos/simulation/{collisionless_simulation.ipynb => ab_simulation.ipynb} (64%) create mode 100644 demos/simulation/ms_simulation.ipynb diff --git a/demos/simulation/collisionless_simulation.ipynb b/demos/simulation/ab_simulation.ipynb similarity index 64% rename from demos/simulation/collisionless_simulation.ipynb rename to demos/simulation/ab_simulation.ipynb index 64490ad..e8f839b 100644 --- a/demos/simulation/collisionless_simulation.ipynb +++ b/demos/simulation/ab_simulation.ipynb @@ -1,9 +1,17 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "348de144", + "metadata": {}, + "source": [ + "# Simulating the Amplitude-Based Collisionless QLBM" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "9b1814b3", + "id": "181af34e", "metadata": {}, "outputs": [], "source": [ @@ -11,77 +19,61 @@ "\n", "from qlbm.components import (\n", " CQLBM,\n", - " CollisionlessInitialConditions,\n", + " ABGridMeasurement,\n", + " ABInitialConditions,\n", " EmptyPrimitive,\n", - " GridMeasurement,\n", ")\n", "from qlbm.infra import QiskitRunner, SimulationConfig\n", - "from qlbm.lattice import CollisionlessLattice\n", - "from qlbm.tools.utils import create_directory_and_parents" + "from qlbm.lattice import ABLattice\n", + "from qlbm.tools.utils import create_directory_and_parents\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "e8e77d2f-a778-441c-b436-a61e1f3154cf", + "id": "05e4915d", "metadata": {}, "outputs": [], "source": [ - "# Load example with mixed boundary conditions and create output directory\n", - "lattice = CollisionlessLattice(\n", + "lattice = ABLattice(\n", " {\n", - " \"lattice\": {\"dim\": {\"x\": 16, \"y\": 16}, \"velocities\": {\"x\": 4, \"y\": 4}},\n", + " \"lattice\": {\"dim\": {\"x\": 64, \"y\": 32}, \"velocities\": \"d2q9\"},\n", " \"geometry\": [\n", - " {\"shape\": \"cuboid\", \"x\": [9, 12], \"y\": [3, 6], \"boundary\": \"specular\"},\n", - " {\"shape\": \"cuboid\", \"x\": [9, 12], \"y\": [9, 12], \"boundary\": \"bounceback\"},\n", + " {\"shape\": \"cuboid\", \"x\": [10, 13], \"y\": [14, 17], \"boundary\": \"bounceback\"},\n", " ],\n", " }\n", ")\n", "\n", "\n", - "output_dir = \"qlbm-output/collisionless-2d-16x16-2-obstacle-mixed-qiskit\"\n", + "output_dir = \"qlbm-output/ab-d2q9-64x32-1-obstacle-qiskit\"\n", "create_directory_and_parents(output_dir)" ] }, { "cell_type": "code", "execution_count": null, - "id": "b4df878f", + "id": "deb472a0", "metadata": {}, "outputs": [], "source": [ - "# Number of shots to simulate for each timestep when running the circuit\n", - "NUM_SHOTS = 2**12\n", - "# Number of timesteps to simulate\n", - "NUM_STEPS = 20" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "26ce07ff-55f9-4828-a9b3-4e9c6dc6a856", - "metadata": {}, - "outputs": [], - "source": [ - "# In the simulation configuration function the user can determine the specifications of the run\n", "cfg = SimulationConfig(\n", - " initial_conditions=CollisionlessInitialConditions(lattice),\n", + " initial_conditions=ABInitialConditions(lattice),\n", " algorithm=CQLBM(lattice),\n", " postprocessing=EmptyPrimitive(lattice),\n", - " measurement=GridMeasurement(lattice),\n", + " measurement=ABGridMeasurement(lattice),\n", " target_platform=\"QISKIT\",\n", " compiler_platform=\"QISKIT\",\n", " optimization_level=0,\n", " statevector_sampling=True,\n", " execution_backend=AerSimulator(method=\"statevector\"),\n", " sampling_backend=AerSimulator(method=\"statevector\"),\n", - ")" + ")\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "b733d245-c344-46db-88bb-864e4cf07474", + "id": "57240678", "metadata": {}, "outputs": [], "source": [ @@ -91,16 +83,30 @@ { "cell_type": "code", "execution_count": null, - "id": "f1c02c70-8244-4a86-8357-9f8c80d2d632", + "id": "1da18eb8", + "metadata": {}, + "outputs": [], + "source": [ + "# Number of shots to simulate for each timestep when running the circuit\n", + "NUM_SHOTS = 2**12\n", + "\n", + "# Number of timesteps to simulate\n", + "NUM_STEPS = 20" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c8c7d1e", "metadata": {}, "outputs": [], "source": [ - "# Create a runner object to simulate the circuit\n", "runner = QiskitRunner(\n", " cfg,\n", " lattice,\n", ")\n", "\n", + "\n", "# Simulate the circuits using both snapshots\n", "runner.run(\n", " NUM_STEPS, # Number of time steps\n", @@ -109,14 +115,6 @@ " statevector_snapshots=True,\n", ")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a4c15cbe-3766-4ea1-b3cb-f7caec41328c", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -135,7 +133,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.7" + "version": "3.13.9" } }, "nbformat": 4, diff --git a/demos/simulation/lqlga_simulation.ipynb b/demos/simulation/lqlga_simulation.ipynb index 91f62bc..a7a2b2e 100644 --- a/demos/simulation/lqlga_simulation.ipynb +++ b/demos/simulation/lqlga_simulation.ipynb @@ -1,5 +1,13 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "4c316e35", + "metadata": {}, + "source": [ + "# Simulating the Linear Encoding Quantum Lattice Gas Automata" + ] + }, { "cell_type": "code", "execution_count": null, @@ -17,7 +25,6 @@ ")\n", "from qlbm.infra import QiskitRunner, SimulationConfig\n", "from qlbm.lattice import LQLGALattice\n", - "from qlbm.lattice import LQLGALattice\n", "from qlbm.tools.utils import create_directory_and_parents" ] }, @@ -28,8 +35,6 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "\n", "lattice = LQLGALattice(\n", " {\n", " \"lattice\": {\n", @@ -71,18 +76,34 @@ { "cell_type": "code", "execution_count": null, - "id": "4d54ef63", + "id": "65ec52e8", + "metadata": {}, + "outputs": [], + "source": [ + "cfg.prepare_for_simulation()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11c5dbb7", "metadata": {}, "outputs": [], "source": [ - "cfg.prepare_for_simulation()\n", - "\n", "# Number of shots to simulate for each timestep when running the circuit\n", "NUM_SHOTS = 2**10\n", "\n", "# Number of timesteps to simulate\n", - "NUM_STEPS = 20\n", - "\n", + "NUM_STEPS = 20" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d54ef63", + "metadata": {}, + "outputs": [], + "source": [ "# Create a runner object to simulate the circuit\n", "runner = QiskitRunner(\n", " cfg,\n", diff --git a/demos/simulation/ms_simulation.ipynb b/demos/simulation/ms_simulation.ipynb new file mode 100644 index 0000000..8ce662e --- /dev/null +++ b/demos/simulation/ms_simulation.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "328661be", + "metadata": {}, + "source": [ + "# Simulation the Multi-Speed Collisionless QLBM" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be6f3d11", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit_aer import AerSimulator\n", + "\n", + "from qlbm.components import (\n", + " CQLBM,\n", + " EmptyPrimitive,\n", + " GridMeasurement,\n", + " MSInitialConditions,\n", + ")\n", + "from qlbm.infra import QiskitRunner, SimulationConfig\n", + "from qlbm.lattice import MSLattice\n", + "from qlbm.tools.utils import create_directory_and_parents\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8d2b671", + "metadata": {}, + "outputs": [], + "source": [ + "lattice = MSLattice(\n", + " {\n", + " \"lattice\": {\n", + " \"dim\": {\"x\": 64, \"y\": 32},\n", + " \"velocities\": {\n", + " \"x\": 4,\n", + " \"y\": 4,\n", + " },\n", + " },\n", + " \"geometry\": [\n", + " {\"shape\": \"cuboid\", \"x\": [10, 13], \"y\": [14, 17], \"boundary\": \"bounceback\"},\n", + " ],\n", + " }\n", + ")\n", + "\n", + "\n", + "output_dir = \"qlbm-output/ms-d2q9-64x32-1-obstacle-qiskit\"\n", + "create_directory_and_parents(output_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da32a65c", + "metadata": {}, + "outputs": [], + "source": [ + "cfg = SimulationConfig(\n", + " initial_conditions=MSInitialConditions(lattice),\n", + " algorithm=CQLBM(lattice),\n", + " postprocessing=EmptyPrimitive(lattice),\n", + " measurement=GridMeasurement(lattice),\n", + " target_platform=\"QISKIT\",\n", + " compiler_platform=\"QISKIT\",\n", + " optimization_level=0,\n", + " statevector_sampling=True,\n", + " execution_backend=AerSimulator(method=\"statevector\"),\n", + " sampling_backend=AerSimulator(method=\"statevector\"),\n", + ")\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qlbm-cpu-venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/demos/simulation/spacetime_simulation.ipynb b/demos/simulation/spacetime_simulation.ipynb index 2668488..e135c7c 100644 --- a/demos/simulation/spacetime_simulation.ipynb +++ b/demos/simulation/spacetime_simulation.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Simulating the Linear Space-Time QLBM" + ] + }, { "cell_type": "code", "execution_count": null, @@ -14,7 +21,7 @@ " SpaceTimeGridVelocityMeasurement,\n", " SpaceTimeQLBM,\n", ")\n", - "from qlbm.infra import QiskitRunner, SimulationConfig\n", + "from qlbm.infra import QiskitRunner, SimulationConfig, SpaceTimeResult\n", "from qlbm.lattice import SpaceTimeLattice\n", "from qlbm.tools.utils import create_directory_and_parents" ] @@ -29,12 +36,20 @@ "lattice = SpaceTimeLattice(\n", " num_timesteps=1,\n", " lattice_data={\n", - " \"lattice\": {\"dim\": {\"x\": 4, \"y\": 8}, \"velocities\": \"D2Q4\"},\n", - " \"geometry\": [],\n", + " \"lattice\": {\"dim\": {\"x\": 64, \"y\": 64}, \"velocities\": \"D2Q4\"},\n", + " \"geometry\": [\n", + " {\n", + " \"shape\": \"sphere\",\n", + " \"center\": [30, 30],\n", + " \"radius\": 15,\n", + " \"boundary\": \"bounceback\",\n", + " }\n", + " ],\n", " },\n", + " use_volumetric_ops=False,\n", ")\n", "\n", - "output_dir = f\"qlbm-output/spacetime-{lattice.logger_name()}-qiskit\"\n", + "output_dir = \"qlbm-output/spacetime-d2q4-64x64-1-sphere-qiskit\"\n", "create_directory_and_parents(output_dir)" ] }, @@ -135,7 +150,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.7" + "version": "3.13.9" } }, "nbformat": 4, From a130bf74bbee4ddde455f7aedb99699901217fa3 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 24 Nov 2025 16:26:00 +0100 Subject: [PATCH 57/61] Update visualization demo notebooks --- ...nents.ipynb => amplitude_components.ipynb} | 69 ++++++++----------- demos/visualization/geometry.ipynb | 12 ++-- demos/visualization/lqlga_components.ipynb | 4 +- 3 files changed, 38 insertions(+), 47 deletions(-) rename demos/visualization/{collisionless_components.ipynb => amplitude_components.ipynb} (67%) diff --git a/demos/visualization/collisionless_components.ipynb b/demos/visualization/amplitude_components.ipynb similarity index 67% rename from demos/visualization/collisionless_components.ipynb rename to demos/visualization/amplitude_components.ipynb index 218f5a0..de549e7 100644 --- a/demos/visualization/collisionless_components.ipynb +++ b/demos/visualization/amplitude_components.ipynb @@ -5,7 +5,7 @@ "id": "ca7a55e2-5729-4a0c-b1bc-19ace7be8100", "metadata": {}, "source": [ - "# Visualizing Collisionless QLBM circuits" + "# Visualizing Amplitude-Based QLBM circuits" ] }, { @@ -18,12 +18,14 @@ "# Import the required packages from the qlbm framework\n", "from qlbm.components import (\n", " CQLBM,\n", - " CollisionlessStreamingOperator,\n", + " ABStreamingOperator,\n", " ControlledIncrementer,\n", - " SpecularReflectionOperator,\n", + " MSStreamingOperator,\n", " SpeedSensitivePhaseShift,\n", ")\n", - "from qlbm.lattice import CollisionlessLattice" + "from qlbm.components.ab import ABStreamingOperator\n", + "from qlbm.lattice import ABLattice, MSLattice\n", + "from qlbm.lattice.lattices.ab_lattice import ABLattice" ] }, { @@ -34,7 +36,7 @@ "outputs": [], "source": [ "# Define an example which uses 4 velocity qubits and the qubits with speed 2 will stream\n", - "speed_shift_primitive = SpeedSensitivePhaseShift(4, 2, True)" + "speed_shift_primitive = SpeedSensitivePhaseShift(5, 1, True)" ] }, { @@ -73,29 +75,20 @@ { "cell_type": "code", "execution_count": null, - "id": "ad38ddb2-56fe-435d-b2db-17ed4a11efef", + "id": "37d61b3f-bfbf-45aa-a6ae-d529ebdaacc0", "metadata": {}, "outputs": [], "source": [ - "# Define a lattice based on which we can construct\n", - "# Operators and algorithms\n", - "example_lattice = CollisionlessLattice(\n", + "# Define a lattice based on which we can construct operators and algorithms\n", + "example_lattice = ABLattice(\n", " {\n", - " \"lattice\": {\"dim\": {\"x\": 8, \"y\": 8}, \"velocities\": {\"x\": 4, \"y\": 4}},\n", + " \"lattice\": {\"dim\": {\"x\": 128, \"y\": 8}, \"velocities\": \"d2q9\"},\n", " \"geometry\": [\n", - " {\"shape\": \"cuboid\", \"x\": [5, 6], \"y\": [1, 2], \"boundary\": \"specular\"}\n", + " {\"shape\": \"cuboid\", \"x\": [4, 7], \"y\": [1, 5], \"boundary\": \"bounceback\"}\n", " ],\n", " }\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "37d61b3f-bfbf-45aa-a6ae-d529ebdaacc0", - "metadata": {}, - "outputs": [], - "source": [ + ")\n", + "\n", "# All primitives can be drawn to the same interface\n", "ControlledIncrementer(example_lattice, reflection=False).draw(\"mpl\")" ] @@ -108,19 +101,17 @@ "outputs": [], "source": [ "# All operators can be drawn the same way\n", - "CollisionlessStreamingOperator(example_lattice, [0, 2, 3]).draw(\"mpl\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3cf55506-d0b0-4f26-832d-13e101ba725a", - "metadata": {}, - "outputs": [], - "source": [ - "SpecularReflectionOperator(example_lattice, example_lattice.shapes[\"bounceback\"]).draw(\n", - " \"mpl\"\n", - ")" + "MSStreamingOperator(\n", + " MSLattice(\n", + " {\n", + " \"lattice\": {\"dim\": {\"x\": 32, \"y\": 8}, \"velocities\": {\"x\": 4, \"y\": 4}},\n", + " \"geometry\": [\n", + " {\"shape\": \"cuboid\", \"x\": [4, 7], \"y\": [1, 5], \"boundary\": \"bounceback\"}\n", + " ],\n", + " }\n", + " ),\n", + " [0, 2, 3],\n", + ").draw(\"mpl\")" ] }, { @@ -130,14 +121,14 @@ "metadata": {}, "outputs": [], "source": [ - "# As can entire algorithms\n", - "CQLBM(example_lattice).draw(\"mpl\")" + "# This works for all quantum components in the library\n", + "ABStreamingOperator(example_lattice).draw(\"mpl\")" ] }, { "cell_type": "code", "execution_count": null, - "id": "66036f08-5fb8-4955-8192-de03b708f340", + "id": "8e80f2af", "metadata": {}, "outputs": [], "source": [] @@ -145,7 +136,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "qlbm-cpu-venv", "language": "python", "name": "python3" }, @@ -159,7 +150,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.13.9" } }, "nbformat": 4, diff --git a/demos/visualization/geometry.ipynb b/demos/visualization/geometry.ipynb index 3864e5a..2054778 100644 --- a/demos/visualization/geometry.ipynb +++ b/demos/visualization/geometry.ipynb @@ -27,8 +27,8 @@ "import pyvista as pv\n", "from pyvista import themes\n", "\n", - "from qlbm.infra import CollisionlessResult\n", - "from qlbm.lattice import CollisionlessLattice\n", + "from qlbm.infra import AmplitudeResult\n", + "from qlbm.lattice import MSLattice\n", "from qlbm.tools.utils import create_directory_and_parents\n", "\n", "pv.set_plot_theme(themes.ParaViewTheme())" @@ -40,7 +40,7 @@ "metadata": {}, "outputs": [], "source": [ - "lattice_2d = CollisionlessLattice(\n", + "lattice_2d = MSLattice(\n", " {\n", " \"lattice\": {\n", " \"dim\": {\"x\": 32, \"y\": 32},\n", @@ -57,7 +57,7 @@ " ],\n", " }\n", ")\n", - "lattice_3d = CollisionlessLattice(\n", + "lattice_3d = MSLattice(\n", " {\n", " \"lattice\": {\n", " \"dim\": {\"x\": 16, \"y\": 128, \"z\": 16},\n", @@ -95,7 +95,7 @@ "outputs": [], "source": [ "# Will output seven 2D stl files under `qlbm-output/visualization-components-2d/paraview`\n", - "CollisionlessResult(lattice_2d, root_directory_2d).visualize_geometry()" + "AmplitudeResult(lattice_2d, root_directory_2d).visualize_geometry()" ] }, { @@ -105,7 +105,7 @@ "outputs": [], "source": [ "# Will output one 3D stl files under `qlbm-output/visualization-components-3d/paraview`\n", - "CollisionlessResult(lattice_3d, root_directory_3d).visualize_geometry()" + "AmplitudeResult(lattice_3d, root_directory_3d).visualize_geometry()" ] }, { diff --git a/demos/visualization/lqlga_components.ipynb b/demos/visualization/lqlga_components.ipynb index 25719bd..3aedc8d 100644 --- a/demos/visualization/lqlga_components.ipynb +++ b/demos/visualization/lqlga_components.ipynb @@ -19,7 +19,7 @@ "from qlbm.components.lqlga.initial import LQGLAInitialConditions\n", "from qlbm.components.lqlga.lqlga import LQLGA\n", "from qlbm.components.lqlga.streaming import LQLGAStreamingOperator\n", - "from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice " + "from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice" ] }, { @@ -105,7 +105,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.7" + "version": "3.13.9" } }, "nbformat": 4, From e6c8513f1aa4180bb9985b37d966c80a0231c29f Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 24 Nov 2025 16:37:55 +0100 Subject: [PATCH 58/61] Fix documentation typos --- qlbm/infra/compiler.py | 2 +- qlbm/lattice/lattices/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qlbm/infra/compiler.py b/qlbm/infra/compiler.py index 313610a..412d443 100644 --- a/qlbm/infra/compiler.py +++ b/qlbm/infra/compiler.py @@ -35,7 +35,7 @@ class CircuitCompiler: =========================== ====================================================================== Example usage: we will construct an end-to-end QLBM algorithm and compile it to a qiskit simulator using Tket. - We begin by constructing a :class:`.SpaceTimeQLBM` algorithm for a :math:`4 \\times 8` lattice, with one time step. + We begin by constructing a :class:`.SpaceTimeQLBM` algorithm for a :math:`4 \times 8` lattice, with one time step. .. plot:: :include-source: diff --git a/qlbm/lattice/lattices/base.py b/qlbm/lattice/lattices/base.py index edda4a1..cc4ad15 100644 --- a/qlbm/lattice/lattices/base.py +++ b/qlbm/lattice/lattices/base.py @@ -139,7 +139,7 @@ class Lattice(ABC): shapes: Dict[str, List[Shape]] """ - Contains all of the :class:`.Shape`s encoding the solid geometry of the lattice. The key of the dictionary is the specific kind of boundary condition of the obstacle (i.e., ``"bounceback"`` or ``"specular"``). + Contains all of the :class:`.Shape`\ s encoding the solid geometry of the lattice. The key of the dictionary is the specific kind of boundary condition of the obstacle (i.e., ``"bounceback"`` or ``"specular"``). """ logger: Logger From 2967074eb0c5aff6655cb6943bd5c4104c7ba4e6 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 24 Nov 2025 16:38:45 +0100 Subject: [PATCH 59/61] Update simulation demo notebooks --- demos/simulation/lqlga_simulation.ipynb | 2 +- demos/simulation/ms_simulation.ipynb | 54 +++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/demos/simulation/lqlga_simulation.ipynb b/demos/simulation/lqlga_simulation.ipynb index a7a2b2e..cd9cb26 100644 --- a/demos/simulation/lqlga_simulation.ipynb +++ b/demos/simulation/lqlga_simulation.ipynb @@ -144,7 +144,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.7" + "version": "3.13.9" } }, "nbformat": 4, diff --git a/demos/simulation/ms_simulation.ipynb b/demos/simulation/ms_simulation.ipynb index 8ce662e..9dc16b3 100644 --- a/demos/simulation/ms_simulation.ipynb +++ b/demos/simulation/ms_simulation.ipynb @@ -75,6 +75,60 @@ " sampling_backend=AerSimulator(method=\"statevector\"),\n", ")\n" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92d4f9ab", + "metadata": {}, + "outputs": [], + "source": [ + "cfg.prepare_for_simulation()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2ba1f1f", + "metadata": {}, + "outputs": [], + "source": [ + "# Number of shots to simulate for each timestep when running the circuit\n", + "NUM_SHOTS = 2**12\n", + "\n", + "# Number of timesteps to simulate\n", + "NUM_STEPS = 20" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f26c6d9", + "metadata": {}, + "outputs": [], + "source": [ + "runner = QiskitRunner(\n", + " cfg,\n", + " lattice,\n", + ")\n", + "\n", + "\n", + "# Simulate the circuits using both snapshots\n", + "runner.run(\n", + " NUM_STEPS, # Number of time steps\n", + " NUM_SHOTS, # Number of shots per time step\n", + " output_dir,\n", + " statevector_snapshots=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1737979", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 44d85ac3ae3825f0254b9e5a09bb6152d3b54a3e Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 24 Nov 2025 16:39:19 +0100 Subject: [PATCH 60/61] Fix bug in amplitude component visualization demo notebook --- .../visualization/amplitude_components.ipynb | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/demos/visualization/amplitude_components.ipynb b/demos/visualization/amplitude_components.ipynb index de549e7..3a29926 100644 --- a/demos/visualization/amplitude_components.ipynb +++ b/demos/visualization/amplitude_components.ipynb @@ -24,8 +24,33 @@ " SpeedSensitivePhaseShift,\n", ")\n", "from qlbm.components.ab import ABStreamingOperator\n", - "from qlbm.lattice import ABLattice, MSLattice\n", - "from qlbm.lattice.lattices.ab_lattice import ABLattice" + "from qlbm.lattice import ABLattice, MSLattice" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e99560c", + "metadata": {}, + "outputs": [], + "source": [ + "example_lattice_ms = MSLattice(\n", + " {\n", + " \"lattice\": {\"dim\": {\"x\": 32, \"y\": 8}, \"velocities\": {\"x\": 4, \"y\": 4}},\n", + " \"geometry\": [\n", + " {\"shape\": \"cuboid\", \"x\": [4, 7], \"y\": [1, 5], \"boundary\": \"bounceback\"}\n", + " ],\n", + " }\n", + ")\n", + "\n", + "example_lattice_ab = ABLattice(\n", + " {\n", + " \"lattice\": {\"dim\": {\"x\": 128, \"y\": 8}, \"velocities\": \"d2q9\"},\n", + " \"geometry\": [\n", + " {\"shape\": \"cuboid\", \"x\": [4, 7], \"y\": [1, 5], \"boundary\": \"bounceback\"}\n", + " ],\n", + " }\n", + ")\n" ] }, { @@ -79,18 +104,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Define a lattice based on which we can construct operators and algorithms\n", - "example_lattice = ABLattice(\n", - " {\n", - " \"lattice\": {\"dim\": {\"x\": 128, \"y\": 8}, \"velocities\": \"d2q9\"},\n", - " \"geometry\": [\n", - " {\"shape\": \"cuboid\", \"x\": [4, 7], \"y\": [1, 5], \"boundary\": \"bounceback\"}\n", - " ],\n", - " }\n", - ")\n", - "\n", "# All primitives can be drawn to the same interface\n", - "ControlledIncrementer(example_lattice, reflection=False).draw(\"mpl\")" + "ControlledIncrementer(example_lattice_ms, reflection=False).draw(\"mpl\")" ] }, { @@ -102,14 +117,7 @@ "source": [ "# All operators can be drawn the same way\n", "MSStreamingOperator(\n", - " MSLattice(\n", - " {\n", - " \"lattice\": {\"dim\": {\"x\": 32, \"y\": 8}, \"velocities\": {\"x\": 4, \"y\": 4}},\n", - " \"geometry\": [\n", - " {\"shape\": \"cuboid\", \"x\": [4, 7], \"y\": [1, 5], \"boundary\": \"bounceback\"}\n", - " ],\n", - " }\n", - " ),\n", + " example_lattice_ms,\n", " [0, 2, 3],\n", ").draw(\"mpl\")" ] @@ -122,7 +130,7 @@ "outputs": [], "source": [ "# This works for all quantum components in the library\n", - "ABStreamingOperator(example_lattice).draw(\"mpl\")" + "ABStreamingOperator(example_lattice_ab).draw(\"mpl\")" ] }, { From d73e27acba1a888a3dd3218ea8aa0d5452c64215 Mon Sep 17 00:00:00 2001 From: Calin Georgescu Date: Mon, 24 Nov 2025 16:39:46 +0100 Subject: [PATCH 61/61] Update documentation website --- docs/source/examples/notebooks/collisionless_vis.nblink | 2 +- docs/source/index.rst | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/examples/notebooks/collisionless_vis.nblink b/docs/source/examples/notebooks/collisionless_vis.nblink index 35cee09..db3aa26 100644 --- a/docs/source/examples/notebooks/collisionless_vis.nblink +++ b/docs/source/examples/notebooks/collisionless_vis.nblink @@ -1,3 +1,3 @@ { - "path": "../../../../demos/visualization/collisionless_components.ipynb" + "path": "../../../../demos/visualization/amplitude_components.ipynb" } \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 865c11b..9664634 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -25,11 +25,11 @@ The :ref:`tools` module contains miscellaneous utilities. #. **L**\ inear \ **Q**\ uantum **L**\ attice **G**\ as **A**\ utomata (LQLGA) described in :cite:p:`spacetime2`, :cite:p:`lqlga1`, and :cite:p:`lqlga2`. -.. card:: Intro to QLBM - :link: internal_docs - :link-type: ref +.. .. card:: Intro to QLBM +.. :link: internal_docs +.. :link-type: ref - :fas:`lightbulb;sd-text-primary` Check out the basics. +.. :fas:`lightbulb;sd-text-primary` Check out the basics. .. card:: Internal Documentation :link: internal_docs