From 660c55bc4f2d528f36bb4b0e36ff5bfaa24b38dc Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Tue, 2 Sep 2025 15:47:35 +0200
Subject: [PATCH 01/16] Rectify MCSWAP attribute documentation
---
qlbm/components/common/primitives.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qlbm/components/common/primitives.py b/qlbm/components/common/primitives.py
index ef629fc..79901a1 100644
--- a/qlbm/components/common/primitives.py
+++ b/qlbm/components/common/primitives.py
@@ -60,7 +60,7 @@ class MCSwap(LBMPrimitive):
========================= ======================================================================
Attribute Summary
========================= ======================================================================
- :attr:`lattice` The :class:`.CollisionlessLattice` or :class:`.SpaceTimeLattice` based on which the number of qubits is inferred.
+ :attr:`lattice` The :class:`.Lattice` based on which the number of qubits is inferred.
:attr:`control_qubits` The qubits that control the swap gate.
:attr:`target_qubits` The two qubits to be swapped.
:attr:`logger` The performance logger, by default ``getLogger("qlbm")``.
From a2ee4000e559e630e9538c724c7eb253b4193ac6 Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Tue, 2 Sep 2025 15:48:08 +0200
Subject: [PATCH 02/16] Separate lattice geometry parsing functionality
---
qlbm/lattice/lattices/base.py | 58 ++++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/qlbm/lattice/lattices/base.py b/qlbm/lattice/lattices/base.py
index 6ae96bf..e0ec3e8 100644
--- a/qlbm/lattice/lattices/base.py
+++ b/qlbm/lattice/lattices/base.py
@@ -250,12 +250,17 @@ def __parse_input_dict(
f"Only 1, 2, and 3-dimensional lattices are supported. Provided lattice has {len(lattice_dict['dim'])} dimensions." # type: ignore
)
+ # Set for access to the geometry parsing utiliti
+ self.num_dims = num_dimensions
+
grid_list: List[int] = [
# -1 because the bit_length() would "overshoot" for powers of 2
lattice_dict["dim"][dimension_letter(dim)] - 1
for dim in range(num_dimensions)
]
+ self.num_gridpoints = grid_list
+
discretization: LatticeDiscretization = LatticeDiscretization.CFLDISCRETIZATION
velocity_list: List[int] = []
@@ -300,13 +305,36 @@ def __parse_input_dict(
for dim in range(num_dimensions)
]
- parsed_obstacles: Dict[str, List[Shape]] = {"specular": [], "bounceback": []}
-
if "geometry" not in input_dict:
- return grid_list, velocity_list, parsed_obstacles, discretization
+ return (
+ grid_list,
+ velocity_list,
+ {"specular": [], "bounceback": []},
+ discretization,
+ )
geometry_list: List[Dict[str, List[int]]] = input_dict["geometry"] # type: ignore
+ parsed_obstacles = self.parse_geometry_dict(geometry_list)
+
+ return grid_list, velocity_list, parsed_obstacles, discretization
+
+ def parse_geometry_dict(self, geometry_list) -> Dict[str, List[Shape]]:
+ """
+ Parses the 'geometry' section of the lattice specification.
+
+ Parameters
+ ----------
+ geometry_list : List[Dict]
+ A list of the geometry components. See demos for concrete examples.
+
+ Returns
+ -------
+ Dict[str, List[Shape]]
+ A dictionary where keys consist of boundary conditions (specular or bounceback) and entries consists of all shapes of that boundary condition.
+ """
+ parsed_obstacles: Dict[str, List[Shape]] = {"specular": [], "bounceback": []}
+
# Check whether obstacles are well-defined
for c, obstacle_dict in enumerate(geometry_list): # type: ignore
if "boundary" not in obstacle_dict:
@@ -331,12 +359,12 @@ def __parse_input_dict(
# Parsing blocks
if obstacle_dict["shape"] == "cuboid":
if (
- len(obstacle_dict) - 2 != num_dimensions
+ len(obstacle_dict) - 2 != self.num_dims
): # -1 to account for the boundary specification
raise LatticeException(
- f"Obstacle {c + 1} has {len(obstacle_dict) - 2} dimensions whereas the lattice has {num_dimensions}."
+ f"Obstacle {c + 1} has {len(obstacle_dict) - 2} dimensions whereas the lattice has {self.num_dims}."
)
- for dim in range(num_dimensions):
+ for dim in range(self.num_dims):
dim_index = dimension_letter(dim)
if len(obstacle_dict[dim_index]) != 2:
@@ -351,7 +379,7 @@ def __parse_input_dict(
if (
obstacle_dict[dim_index][0] < 0
- or obstacle_dict[dim_index][-1] > lattice_dict["dim"][dim_index]
+ or obstacle_dict[dim_index][-1] > self.num_gridpoints[dim]
):
raise LatticeException(
f"Obstacle {c + 1} is out of bounds in the {dim_index}-dimension."
@@ -364,11 +392,11 @@ def __parse_input_dict(
obstacle_dict[dimension_letter(numeric_dim_index)][0],
obstacle_dict[dimension_letter(numeric_dim_index)][1],
)
- for numeric_dim_index in range(num_dimensions)
+ for numeric_dim_index in range(self.num_dims)
],
[
- (grid_list[numeric_dim_index]).bit_length()
- for numeric_dim_index in range(num_dimensions)
+ (self.num_gridpoints[numeric_dim_index]).bit_length()
+ for numeric_dim_index in range(self.num_dims)
],
obstacle_dict["boundary"], # type: ignore
)
@@ -382,9 +410,9 @@ def __parse_input_dict(
raise LatticeException(
f"Obstacle {c + 1}: sphere obstacle does not specify a radius."
)
- if len(obstacle_dict["center"]) != num_dimensions:
+ if len(obstacle_dict["center"]) != self.num_dims:
raise LatticeException(
- f"Obstacle {c + 1}: center is {len(obstacle_dict['center'])}-dimensional whereas the lattice is {num_dimensions}-dimensional."
+ f"Obstacle {c + 1}: center is {len(obstacle_dict['center'])}-dimensional whereas the lattice is {self.num_dims}-dimensional."
)
if int(obstacle_dict["radius"]) <= 0: # type: ignore
raise LatticeException(
@@ -395,14 +423,14 @@ def __parse_input_dict(
tuple(int(coord) for coord in obstacle_dict["center"]),
int(obstacle_dict["radius"]), # type: ignore
[
- (grid_list[numeric_dim_index]).bit_length()
- for numeric_dim_index in range(num_dimensions)
+ (self.num_gridpoints[numeric_dim_index]).bit_length()
+ for numeric_dim_index in range(self.num_dims)
],
obstacle_dict["boundary"], # type: ignore
)
)
- return grid_list, velocity_list, parsed_obstacles, discretization
+ return parsed_obstacles
def to_json(self) -> str:
"""
From ac24ab5561eb252083a28e058ba9cfe7c31d933b Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Tue, 2 Sep 2025 15:48:31 +0200
Subject: [PATCH 03/16] Generalize qubit inversion utility nomenclature
---
qlbm/tools/utils.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/qlbm/tools/utils.py b/qlbm/tools/utils.py
index cd3c218..3a56be4 100644
--- a/qlbm/tools/utils.py
+++ b/qlbm/tools/utils.py
@@ -255,14 +255,14 @@ def get_time_series(
return speed_controls # type: ignore
-def get_qubits_to_invert(gridpoint_encoded: int, num_qubits: int) -> List[int]:
+def get_qubits_to_invert(number_encoded: int, num_qubits: int) -> List[int]:
r"""
- Returns the indices at which a given gridpoint encoded value has a 0. Inverting these indices results in a :math:`\ket{1}^{\otimes n}` state.
+ Returns the indices at which a given number encoded value has a 0. Inverting these indices results in a :math:`\ket{1}^{\otimes n}` state.
Parameters
----------
- gridpoint_encoded : int
- The integer representation of the gridpoint.
+ number_encoded : int
+ The integer representation of the number.
num_qubits : int
The total nuimber of grid qubits.
@@ -271,4 +271,4 @@ def get_qubits_to_invert(gridpoint_encoded: int, num_qubits: int) -> List[int]:
List[int]
The indices of the (qu)bits that have value 0.
"""
- return [i for i in range(num_qubits) if not bit_value(gridpoint_encoded, i)]
+ return [i for i in range(num_qubits) if not bit_value(number_encoded, i)]
From b65e52286763ee641bab27e4738a607a44853aac Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Tue, 2 Sep 2025 21:21:38 +0200
Subject: [PATCH 04/16] Update metadata in pyproject.toml
---
pyproject.toml | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 9dc4819..c1838fd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,12 +35,15 @@ dependencies = [
"tqdm>=4.66",
"vtk",
]
-authors = [
- { name = "qlbm authors" },
-]
-maintainers = [{ name = "Calin Georgescu", email = "qcfd-EWI@tudelft.nl" }]
+authors = [{ name = "qlbm authors" }, { email = "qcfd-EWI@tudelft.nl" }]
+maintainers = [{ name = "Calin Georgescu", email = "c.a.georgescu@tudelft.nl" }]
description = "Quantum Algorithms for Lattice Boltzmann Methods."
readme = "README.md"
+license = { text = "MPL-2.0" }
+
+
+[project.urls]
+Homepage = "https://qcfd-lab.github.io/qlbm/"
[project.optional-dependencies]
@@ -80,7 +83,7 @@ include = ["qlbm", "qlbm.*"]
[tool.mypy]
python_version = "3.12"
-warn_return_any = false # False positives due to Qiskit implementation
+warn_return_any = false # False positives due to Qiskit implementation
warn_unused_configs = true
warn_unreachable = true
check_untyped_defs = true
@@ -121,7 +124,7 @@ exclude_also = [
"raise AssertionError",
"raise NotImplementedError",
"@(abc\\.)?abstractmethod",
- ]
+]
[tool.ruff]
# Exclude a variety of commonly ignored directories.
@@ -199,4 +202,4 @@ docstring-code-format = true
docstring-code-line-length = "dynamic"
[tool.ruff.lint.pydocstyle]
-convention = "numpy"
\ No newline at end of file
+convention = "numpy"
From 8addd54482dd7c7abbe649aa6f81c9a5fa15b8d3 Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Wed, 3 Sep 2025 15:29:31 +0200
Subject: [PATCH 05/16] Update container documentation and installation
instructions
---
Docker/README.md | 29 +++++++++++++++++++++++++++++
README.md | 29 ++++++++++++++++-------------
2 files changed, 45 insertions(+), 13 deletions(-)
create mode 100644 Docker/README.md
diff --git a/Docker/README.md b/Docker/README.md
new file mode 100644
index 0000000..6aa04bc
--- /dev/null
+++ b/Docker/README.md
@@ -0,0 +1,29 @@
+# Container files
+
+We provide two docker files fo `qlbm`: one for regular development and one for GPU integration.
+
+## Development container
+
+`build_cpu.Dockerfile` can be used to build a container image that has all dependencies of `qlbm` for active development. You can build the image from the repository root as follows:
+
+```bash
+docker build -t qlbm -f Docker/build_cpu.Dockerfile .
+```
+
+Once the image is built, it will have all the dependencies, but not `qlbm` itself. You can actively link the repository files to the running container with the command
+
+```bash
+docker run -it --volume $(pwd):/qlbm/ qlbm
+```
+
+Once inside the container, you can quickly link the files in the volume to `pip` with
+
+```
+pip install -e .[cpu,dev,docs]
+```
+
+This will allow you to edit the files on your machine's file system in a text editor of your choice, and have the updates immediately available in the running container, without reinstalling anything.
+
+## GPU container
+
+`build_gpu.Dockerfile` contains an image allows you to simulate `qlbm` algorithms on nVidia GPU hardware with Qiskit and using the nVidia cuQuantum appliance.
diff --git a/README.md b/README.md
index f5ae311..06b90bc 100644
--- a/README.md
+++ b/README.md
@@ -21,18 +21,7 @@
-
-## PyPI installation
-
-`qlbm` can be installed through `pip`. We recommend the use of a Python 3.12 or 3.13 virtual environment:
-
-```bash
-python -m venv qlbm-cpu-venv
-pip install --upgrade pip
-pip install qlbm
-```
-
-## Local installation
+## Install from source
Alternatively, you can also install the latest version of `qlbm` by cloning the repository and installing from source as follows (again using Python 3.12 or 3.13):
@@ -56,7 +45,21 @@ make install-cpu
source qlbm-cpu-venv/bin/activate
```
-`qlbm` additionally supports several other options, including GPU and MPI simulation. There are also Docker container images in the `Docker` directory. Due to how quickly the code base is evolving, we recommend using the CPU option for stability purposes.
+## PyPI installation
+
+`qlbm` can also be installed through `pip`. We recommend the use of a Python 3.12 or 3.13 virtual environment:
+
+```bash
+python -m venv qlbm-cpu-venv
+pip install --upgrade pip
+pip install qlbm
+```
+
+Note that `qlbm` evolves quickly and it is likely that the GitHub repository contains new features that the PyPI installation does not. To get the latest developments, we recommend the source installation.
+
+## Container installation
+
+There are also Docker container images in the `Docker` directory that can be used to install `qlbm` in a container environment. Due to how quickly the code base is evolving, we recommend using the CPU option for stability purposes.
## Algorithms and Usage
From ec3a8bd7b078d13cf87c486b8a57fe6a94d9f65b Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Wed, 3 Sep 2025 15:30:00 +0200
Subject: [PATCH 06/16] Update CPU container image
---
Docker/build_cpu.Dockerfile | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/Docker/build_cpu.Dockerfile b/Docker/build_cpu.Dockerfile
index 5715202..aa64c9b 100644
--- a/Docker/build_cpu.Dockerfile
+++ b/Docker/build_cpu.Dockerfile
@@ -1,16 +1,12 @@
-FROM python:3.11-bookworm
+FROM python:3.13-bookworm
-RUN apt-get update -qq && apt-get install -y -qq libboost-all-dev cmake
+RUN apt-get update -qq && apt-get install -y -qq libboost-all-dev cmake pandoc libglfw3-dev libgles2-mesa-dev
WORKDIR /qlbm
-COPY Makefile Makefile
COPY pyproject.toml pyproject.toml
-COPY README.md README.md
-
-RUN make install-cpu
-
-COPY qlbm qlbm
-COPY test test
+RUN python -m pip install --upgrade pip \
+ && pip install --no-cache-dir -e ".[cpu,dev,docs]" \
+ && rm -rf /root/.cache/pip
ENTRYPOINT /bin/bash
\ No newline at end of file
From d952670ead9586c9e37d7d4acf8ec89470c73453 Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Wed, 3 Sep 2025 22:22:20 +0200
Subject: [PATCH 07/16] Add support for multiple geometries in base lattice and
result
---
qlbm/infra/result/base.py | 11 +++++++++--
qlbm/lattice/lattices/base.py | 19 ++++++++++++++++++-
2 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/qlbm/infra/result/base.py b/qlbm/infra/result/base.py
index ff3b39d..8987f5a 100644
--- a/qlbm/infra/result/base.py
+++ b/qlbm/infra/result/base.py
@@ -64,8 +64,15 @@ def visualize_geometry(self):
Output files are formatted as ``output_dir/paraview_dir/cube_.stl``.
The output is created through the :class:`.Shape`'s :meth:`.Shape.stl_mesh` method.
"""
- for c, shape in enumerate(flatten(self.lattice.shapes.values())):
- shape.stl_mesh().save(f"{self.paraview_dir}/{shape.name()}_{c}.stl")
+ if not self.lattice.has_multiple_geometries():
+ for c, shape in enumerate(flatten(self.lattice.shapes.values())):
+ shape.stl_mesh().save(f"{self.paraview_dir}/{shape.name()}_{c}.stl")
+ else:
+ for cg, g in enumerate(self.lattice.geometries): # type: ignore
+ for cs, shape in enumerate(flatten(g.values())):
+ shape.stl_mesh().save(
+ f"{self.paraview_dir}/lattice_{cg}_{shape.name()}_{cs}.stl"
+ )
def save_timestep_array(
self,
diff --git a/qlbm/lattice/lattices/base.py b/qlbm/lattice/lattices/base.py
index e0ec3e8..1d91b8f 100644
--- a/qlbm/lattice/lattices/base.py
+++ b/qlbm/lattice/lattices/base.py
@@ -167,6 +167,7 @@ def __init__(
def parse_input_data(
self,
lattice_data: str | Dict, # type: ignore
+ compressed_grid: bool = True,
) -> Tuple[List[int], List[int], Dict[str, List[Shape]], LatticeDiscretization]:
r"""
Parses the lattice input data, provided in either a file path or a dictionary.
@@ -176,6 +177,8 @@ def parse_input_data(
lattice_data : str | Dict
Either a file path to read a JSON-formatted specification from,
or a dictionary formatted as in the main class description.
+ compressed_grid : bool
+ Whether the grid data is compressed into logarithmically many qubits.
Returns
-------
@@ -250,7 +253,7 @@ def __parse_input_dict(
f"Only 1, 2, and 3-dimensional lattices are supported. Provided lattice has {len(lattice_dict['dim'])} dimensions." # type: ignore
)
- # Set for access to the geometry parsing utiliti
+ # Set for access to the geometry parsing utilities
self.num_dims = num_dimensions
grid_list: List[int] = [
@@ -399,6 +402,7 @@ def parse_geometry_dict(self, geometry_list) -> Dict[str, List[Shape]]:
for numeric_dim_index in range(self.num_dims)
],
obstacle_dict["boundary"], # type: ignore
+ num_gridpoints=self.num_gridpoints,
)
)
elif obstacle_dict["shape"] == "sphere":
@@ -490,3 +494,16 @@ def logger_name(self) -> str:
A string that can be used to sufficiently identify the lattice specification.
"""
pass
+
+ @abstractmethod
+ def has_multiple_geometries(self) -> bool:
+ """
+ Whether multiple lattice geometries are simulated simultaneously.
+
+ Returns
+ -------
+ bool
+ Whether multiple lattice geometries are simulated simultaneously.
+ """
+ pass
+
From 8a8d776556523b34c0a7d72322c125397197a6cd Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Wed, 3 Sep 2025 22:23:30 +0200
Subject: [PATCH 08/16] Fix bug in LQLGA geometry overflow guard
---
qlbm/lattice/geometry/shapes/block.py | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/qlbm/lattice/geometry/shapes/block.py b/qlbm/lattice/geometry/shapes/block.py
index 3704c1c..2fcece0 100644
--- a/qlbm/lattice/geometry/shapes/block.py
+++ b/qlbm/lattice/geometry/shapes/block.py
@@ -102,8 +102,13 @@ def __init__(
bounds: List[Tuple[int, int]],
num_grid_qubits: List[int],
boundary_condition: str,
+ num_gridpoints: List[int] | None = None,
) -> None:
super().__init__(num_grid_qubits, boundary_condition)
+ if num_gridpoints is None:
+ self.num_gridpoints = [2**nq for nq in num_grid_qubits]
+ else:
+ self.num_gridpoints = num_gridpoints
# TODO: check whether the number of dimensions is consistent
self.bounds: List[Tuple[int, int]] = bounds
if self.num_dims == 3:
@@ -709,14 +714,15 @@ def get_d2q4_surfaces(self) -> List[List[List[Tuple[int, ...]]]]:
@override
def get_lqlga_reflection_data_d1q2(self):
+ print(self.num_grid_qubits[0])
return self.get_lqlga_reflection_data_1d_from_points(
[tuple([self.bounds[0][0]])],
0,
1,
tuple([-1]),
- 2 ** self.num_grid_qubits[0],
+ self.num_gridpoints[0] + 1,
) + self.get_lqlga_reflection_data_1d_from_points(
- [tuple([self.bounds[0][1]])], 1, 0, tuple([1]), 2 ** self.num_grid_qubits[0]
+ [tuple([self.bounds[0][1]])], 1, 0, tuple([1]), self.num_gridpoints[0] + 1
)
@override
@@ -726,9 +732,13 @@ def get_lqlga_reflection_data_d1q3(self):
1,
2,
tuple([-1]),
- 2 ** self.num_grid_qubits[0],
+ self.num_grid_qubits[0] + 1,
) + self.get_lqlga_reflection_data_1d_from_points(
- [tuple([self.bounds[0][1]])], 2, 1, tuple([1]), 2 ** self.num_grid_qubits[0]
+ [tuple([self.bounds[0][1]])],
+ 2,
+ 1,
+ tuple([1]),
+ self.num_grid_qubits[0] + 1,
)
@override
From 9902e211513dc8790d59009ed87177752a6e214b Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Wed, 3 Sep 2025 22:24:14 +0200
Subject: [PATCH 09/16] Add multiple geometry support for LQLGA
---
qlbm/components/__init__.py | 2 +
qlbm/components/lqlga/__init__.py | 3 +-
qlbm/components/lqlga/initial.py | 3 +
qlbm/components/lqlga/lqlga.py | 32 ++++--
qlbm/components/lqlga/reflection.py | 168 +++++++++++++++++++++++++++-
5 files changed, 198 insertions(+), 10 deletions(-)
diff --git a/qlbm/components/__init__.py b/qlbm/components/__init__.py
index 3c79cd7..0cd14be 100644
--- a/qlbm/components/__init__.py
+++ b/qlbm/components/__init__.py
@@ -34,6 +34,7 @@
GenericLQLGACollisionOperator,
LQGLAInitialConditions,
LQLGAGridVelocityMeasurement,
+ LQLGAMGReflectionOperator,
LQLGAReflectionOperator,
LQLGAStreamingOperator,
)
@@ -64,6 +65,7 @@
"LQGLAInitialConditions",
"LQLGA",
"LQLGAGridVelocityMeasurement",
+ "LQLGAMGReflectionOperator",
"LQLGAReflectionOperator",
"LQLGAStreamingOperator",
"EQCCollisionOperator",
diff --git a/qlbm/components/lqlga/__init__.py b/qlbm/components/lqlga/__init__.py
index 11a630a..3b2ca8c 100644
--- a/qlbm/components/lqlga/__init__.py
+++ b/qlbm/components/lqlga/__init__.py
@@ -4,7 +4,7 @@
from .initial import LQGLAInitialConditions
from .lqlga import LQLGA
from .measurement import LQLGAGridVelocityMeasurement
-from .reflection import LQLGAReflectionOperator
+from .reflection import LQLGAMGReflectionOperator, LQLGAReflectionOperator
from .streaming import LQLGAStreamingOperator
__all__ = [
@@ -13,5 +13,6 @@
"LQLGA",
"LQLGAGridVelocityMeasurement",
"LQLGAReflectionOperator",
+ "LQLGAMGReflectionOperator"
"LQLGAStreamingOperator",
]
diff --git a/qlbm/components/lqlga/initial.py b/qlbm/components/lqlga/initial.py
index e20c1da..c73f5c0 100644
--- a/qlbm/components/lqlga/initial.py
+++ b/qlbm/components/lqlga/initial.py
@@ -82,6 +82,9 @@ def create_circuit(self):
if is_enabled
)
+ if self.lattice.has_multiple_geometries():
+ circuit.h(self.lattice.marker_index())
+
return circuit
@override
diff --git a/qlbm/components/lqlga/lqlga.py b/qlbm/components/lqlga/lqlga.py
index d4e6dc5..c74575a 100644
--- a/qlbm/components/lqlga/lqlga.py
+++ b/qlbm/components/lqlga/lqlga.py
@@ -7,7 +7,10 @@
from qlbm.components.base import LBMAlgorithm
from qlbm.components.lqlga.collision import GenericLQLGACollisionOperator
-from qlbm.components.lqlga.reflection import LQLGAReflectionOperator
+from qlbm.components.lqlga.reflection import (
+ LQLGAMGReflectionOperator,
+ LQLGAReflectionOperator,
+)
from qlbm.components.lqlga.streaming import LQLGAStreamingOperator
from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice
@@ -72,12 +75,27 @@ def create_circuit(self) -> QuantumCircuit:
LQLGAStreamingOperator(self.lattice, self.logger).circuit, inplace=True
)
- circuit.compose(
- LQLGAReflectionOperator(
- self.lattice, self.lattice.shapes["bounceback"], self.logger
- ).circuit,
- inplace=True,
- )
+ if self.lattice.has_multiple_geometries():
+ circuit.compose(
+ LQLGAMGReflectionOperator(
+ self.lattice,
+ [
+ gdict["bounceback"] + gdict["specular"]
+ for gdict in self.lattice.geometries
+ ],
+ self.logger,
+ ).circuit,
+ inplace=True,
+ )
+ else:
+ circuit.compose(
+ LQLGAReflectionOperator(
+ self.lattice,
+ self.lattice.shapes["bounceback"] + self.lattice.shapes["specular"],
+ self.logger,
+ ).circuit,
+ inplace=True,
+ )
return circuit
diff --git a/qlbm/components/lqlga/reflection.py b/qlbm/components/lqlga/reflection.py
index d990d36..400136a 100644
--- a/qlbm/components/lqlga/reflection.py
+++ b/qlbm/components/lqlga/reflection.py
@@ -2,16 +2,18 @@
from logging import Logger, getLogger
from time import perf_counter_ns
-from typing import List, cast
+from typing import List, Tuple, cast
from qiskit import QuantumCircuit
from typing_extensions import override
from qlbm.components.base import LQLGAOperator
+from qlbm.components.common.primitives import MCSwap
from qlbm.lattice.geometry.shapes.base import LQLGAShape, Shape
from qlbm.lattice.lattices.lqlga_lattice import LQLGALattice
from qlbm.lattice.spacetime.properties_base import LatticeDiscretization
from qlbm.tools.exceptions import CircuitException
+from qlbm.tools.utils import get_qubits_to_invert
class LQLGAReflectionOperator(LQLGAOperator):
@@ -19,7 +21,7 @@ class LQLGAReflectionOperator(LQLGAOperator):
Operator implementing reflection in the :class:`.LQLGA` algorithm.
Reflections in this algorithm can be entirely implemented by swap gates.
- The number of gates scales with the number of grridpoints of the solid geometry.
+ The number of gates scales with the number of gridpoints of the solid geometry.
The depth of the operator is 1.
============================ ======================================================================
@@ -123,3 +125,165 @@ def __create_circuit_d1q3(self) -> QuantumCircuit:
@override
def __str__(self) -> str:
return f"[PointWiseLQLGAReflectionOperator for lattice {self.lattice}, shapes {self.shapes}]"
+
+
+class LQLGAMGReflectionOperator(LQLGAOperator):
+ """
+ Operator implementing reflection in the :class:`.LQLGA` algorithm with multiple geometries.
+
+ Reflections in this algorithm can be entirely implemented a series of controlled swap gates.
+ This is equivalent to multiple controlled realizations of the :class:`.LQLGAReflectionOperator`
+ applied in series.
+
+ ============================ ======================================================================
+ Attribute Summary
+ ============================ ======================================================================
+ :attr:`lattice` The lattice the operator acts on.
+ :attr:`shapes` A list of boundary-conditioned shapes.
+ :attr:`logger` The performance logger, by default ``getLogger("qlbm")``.
+ ============================ ======================================================================
+
+ Example usage:
+
+ .. plot::
+ :include-source:
+
+ from qlbm.components.lqlga import LQLGAReflectionOperator
+ from qlbm.lattice import LQLGALattice
+
+ lattice = LQLGALattice(
+ {
+ "lattice": {
+ "dim": {"x": 7},
+ "velocities": "D1Q3",
+ },
+ "geometry": [{"shape": "cuboid", "x": [3, 5], "boundary": "bounceback"}],
+ },
+ )
+ reflection_operator = LQLGAReflectionOperator(
+ lattice, shapes=lattice.shapes["bounceback"]
+ )
+ reflection_operator.draw("mpl")
+
+ """
+
+ shapes: List[List[LQLGAShape]]
+ """
+ A list of shapes that require reflection at the boundaries.
+ """
+
+ def __init__(
+ self,
+ lattice: LQLGALattice,
+ shapes: List[List[Shape]],
+ logger: Logger = getLogger("qlbm"),
+ ) -> None:
+ super().__init__(lattice, logger)
+ self.shapes = cast(List[List[LQLGAShape]], shapes)
+
+ 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:
+ discretization = self.lattice.discretization
+ if discretization == LatticeDiscretization.D1Q2:
+ return self.__create_circuit_d1q2()
+
+ elif discretization == LatticeDiscretization.D1Q3:
+ return self.__create_circuit_d1q3()
+
+ raise CircuitException(f"Reflection Operator unsupported for {discretization}.")
+
+ def __create_circuit_d1q2(self) -> QuantumCircuit:
+ circuit = self.lattice.circuit.copy()
+
+ for c, geometry in enumerate(self.shapes):
+ # Prepare the /ket{1} state in the marker register
+ qubits_to_invert = [
+ q + self.lattice.marker_index()[0]
+ for q in get_qubits_to_invert(c, self.lattice.num_marker_qubits)
+ ]
+
+ if qubits_to_invert:
+ circuit.x(qubits_to_invert)
+
+ for shape in geometry:
+ for reflection_data in shape.get_lqlga_reflection_data_d1q2():
+ circuit.compose(
+ MCSwap(
+ self.lattice,
+ self.lattice.marker_index(),
+ cast(
+ Tuple[int, int],
+ tuple(
+ [
+ self.lattice.velocity_index_tuple(
+ reflection_data.gridpoints[0],
+ reflection_data.velocity_indices_to_swap[0],
+ ),
+ self.lattice.velocity_index_tuple(
+ reflection_data.gridpoints[1],
+ reflection_data.velocity_indices_to_swap[1],
+ ),
+ ],
+ ),
+ ),
+ self.logger,
+ ).circuit,
+ inplace=True,
+ )
+
+ if qubits_to_invert:
+ circuit.x(qubits_to_invert)
+ return circuit
+
+ def __create_circuit_d1q3(self) -> QuantumCircuit:
+ circuit = self.lattice.circuit.copy()
+ for c, geometry in enumerate(self.shapes):
+ # Prepare the /ket{1} state in the marker register
+ qubits_to_invert = [
+ q + self.lattice.marker_index()[0]
+ for q in get_qubits_to_invert(c, self.lattice.num_marker_qubits)
+ ]
+
+ if qubits_to_invert:
+ circuit.x(qubits_to_invert)
+
+ for shape in geometry:
+ for reflection_data in shape.get_lqlga_reflection_data_d1q3():
+ circuit.compose(
+ MCSwap(
+ self.lattice,
+ self.lattice.marker_index(),
+ cast(
+ Tuple[int, int],
+ tuple(
+ [
+ self.lattice.velocity_index_tuple(
+ reflection_data.gridpoints[0],
+ reflection_data.velocity_indices_to_swap[0],
+ ),
+ self.lattice.velocity_index_tuple(
+ reflection_data.gridpoints[1],
+ reflection_data.velocity_indices_to_swap[1],
+ ),
+ ],
+ ),
+ ),
+ ).circuit,
+ inplace=True,
+ )
+
+ if qubits_to_invert:
+ circuit.x(qubits_to_invert)
+
+ return circuit
+
+ @override
+ def __str__(self) -> str:
+ return f"[LQLGAMGReflectionOperator for lattice {self.lattice}, shapes {self.shapes}]"
From e396dfc99a3ac4613812ce7d2b10ad79cf4b0620 Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Wed, 3 Sep 2025 22:26:40 +0200
Subject: [PATCH 10/16] Implement multiple geometry interface for all lattices
---
.../lattice/lattices/collisionless_lattice.py | 4 +
qlbm/lattice/lattices/lqlga_lattice.py | 100 +++++++++++++++++-
qlbm/lattice/lattices/spacetime_lattice.py | 5 +
3 files changed, 105 insertions(+), 4 deletions(-)
diff --git a/qlbm/lattice/lattices/collisionless_lattice.py b/qlbm/lattice/lattices/collisionless_lattice.py
index d02724e..efe3dcc 100644
--- a/qlbm/lattice/lattices/collisionless_lattice.py
+++ b/qlbm/lattice/lattices/collisionless_lattice.py
@@ -531,3 +531,7 @@ 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
diff --git a/qlbm/lattice/lattices/lqlga_lattice.py b/qlbm/lattice/lattices/lqlga_lattice.py
index 41743c2..893043d 100644
--- a/qlbm/lattice/lattices/lqlga_lattice.py
+++ b/qlbm/lattice/lattices/lqlga_lattice.py
@@ -4,6 +4,7 @@
from math import prod
from typing import Dict, List, Tuple, cast, override
+from numpy import ceil, log2
from qiskit import QuantumCircuit, QuantumRegister
from qlbm.lattice.geometry.shapes.base import Shape
@@ -13,6 +14,7 @@
LatticeDiscretizationProperties,
)
from qlbm.tools.exceptions import LatticeException
+from qlbm.tools.utils import flatten
class LQLGALattice(Lattice):
@@ -67,6 +69,9 @@ class LQLGALattice(Lattice):
num_base_qubits: int
"""The number of qubits required to represent the lattice."""
+ num_marker_qubits: int
+ """The number of qubits used to identify geometries, if parallel lattices are being simulated."""
+
velocity_register: QuantumRegister
"""The quantum register representing the velocities of the lattice."""
@@ -76,6 +81,7 @@ def __init__(self, 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)
@@ -85,12 +91,18 @@ def __init__(self, lattice_data, logger=...):
prod(map(lambda x: x + 1, self.num_gridpoints))
* self.num_velocities_per_point
)
+ 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_total_qubits = self.num_base_qubits + self.num_marker_qubits
- self.registers = self.get_registers()
+ temp_registers = self.get_registers()
- self.velocity_register = self.registers
+ self.velocity_register, self.marker_register = temp_registers
+ self.registers = tuple(flatten(temp_registers))
self.circuit = QuantumCircuit(*self.registers)
@@ -106,7 +118,18 @@ def get_registers(self) -> Tuple[List[QuantumRegister], ...]:
)
]
- return velocity_registers # type: ignore
+ marker_register = (
+ [
+ QuantumRegister(
+ int(ceil(log2(len(self.geometries)))),
+ name="m",
+ )
+ ]
+ if self.has_multiple_geometries()
+ else []
+ )
+
+ return (velocity_registers, marker_register)
def gridpoint_index_tuple(self, gridpoint: Tuple[int, ...]) -> int:
"""
@@ -245,6 +268,21 @@ def velocity_index_tuple(self, gridpoint: Tuple[int, ...], velocity: int) -> int
+ velocity
)
+ def marker_index(self) -> List[int]:
+ """
+ Get the indices of the qubits addressing the marker.
+
+ This is only useful if multiple lattice geometries are addressed simultaneously.
+
+ Returns
+ -------
+ List[int]
+ The absolute indices of the marker qubits.
+ """
+ return list(
+ range(self.num_base_qubits, self.num_base_qubits + self.num_marker_qubits)
+ )
+
def get_velocity_qubits_of_line(self, line_index: int) -> Tuple[int, int]:
r"""
Returns the velocity qubits of the positive and negative directions of a streaming line.
@@ -282,3 +320,57 @@ def logger_name(self) -> str:
if c < len(self.num_gridpoints) - 1:
gp_string += "x"
return f"lqlga-d{self.num_dims}-q{self.num_velocities_per_point}-{gp_string}"
+
+ def set_geometries(self, geometries):
+ """
+ Updates the geometry setup of the lattice.
+
+ For a given lattice (set number of gridpoints and velocity discretization),
+ set multiple geometry configurations to simulate simultaneously.
+
+ .. code-block:: python
+ from qlbm.lattice import LQLGALattice
+
+ lattice = LQLGALattice(
+ {
+ "lattice": {
+ "dim": {"x": 5},
+ "velocities": "D1Q2",
+ },
+ },
+ )
+
+ lattice.set_geometries(
+ [
+ [{"shape": "cuboid", "x": [3, 4], "boundary": "bounceback"}],
+ [{"shape": "cuboid", "x": [1, 2], "boundary": "specular"}],
+ [{"shape": "cuboid", "x": [1, 4], "boundary": "specular"}],
+ ]
+ )
+
+ lattice.circuit.draw("mpl")
+
+ Parameters
+ ----------
+ geometries : Dict
+ A list of geometries to simulate on the same lattice.
+ """
+ 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)
+
+ @override
+ def has_multiple_geometries(self) -> bool:
+ return len(self.geometries) > 1
diff --git a/qlbm/lattice/lattices/spacetime_lattice.py b/qlbm/lattice/lattices/spacetime_lattice.py
index e7099e2..ef993b8 100644
--- a/qlbm/lattice/lattices/spacetime_lattice.py
+++ b/qlbm/lattice/lattices/spacetime_lattice.py
@@ -468,3 +468,8 @@ def comparator_periodic_volume_bounds(
adjusted_bounds.append((new_bounds, overflow_occurred))
return adjusted_bounds
+
+
+ @override
+ def has_multiple_geometries(self):
+ return False # multiple geometries unsupported for STQBM
From f30cc369ab183897dee8ac17e1a32edae2c5abf5 Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Wed, 3 Sep 2025 22:27:31 +0200
Subject: [PATCH 11/16] Add LQLGA multiple geometry tests
---
test/unit/lqlga/lqlga_lattice_test.py | 90 ++++++++++++++++++++++++++-
1 file changed, 88 insertions(+), 2 deletions(-)
diff --git a/test/unit/lqlga/lqlga_lattice_test.py b/test/unit/lqlga/lqlga_lattice_test.py
index 15daded..d25fa2c 100644
--- a/test/unit/lqlga/lqlga_lattice_test.py
+++ b/test/unit/lqlga/lqlga_lattice_test.py
@@ -5,14 +5,100 @@
def test_lqlga_lattice_num_registers_d1q2(lattice_d1q2_256):
+ assert len(lattice_d1q2_256.registers) == 256 # One empty register
+ assert all(reg.size == 2 for reg in lattice_d1q2_256.velocity_register)
+ assert lattice_d1q2_256.circuit.num_qubits == 512
+
+
+def test_lqlga_lattice_num_registers_multiple_geometries_once_one_geometry_d1q2(
+ lattice_d1q2_256,
+):
+ 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
+
+ lattice_d1q2_256.set_geometries(
+ [[{"shape": "cuboid", "x": [12, 27], "boundary": "bounceback"}]]
+ )
+
+ 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
+
+
+def test_lqlga_lattice_num_registers_multiple_geometries_once_two_geometries_d1q2(
+ lattice_d1q2_256,
+):
+ 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
+
+ lattice_d1q2_256.set_geometries(
+ [
+ [{"shape": "cuboid", "x": [12, 27], "boundary": "bounceback"}],
+ [{"shape": "cuboid", "x": [12, 27], "boundary": "specular"}],
+ ]
+ )
+
+ assert len(lattice_d1q2_256.registers) == 256 + 1
+ assert all(reg.size == 2 for reg in lattice_d1q2_256.velocity_register)
+ assert lattice_d1q2_256.circuit.num_qubits == 512 + 1 # One extra marker qubit
+
+
+def test_lqlga_lattice_num_registers_multiple_geometries_twice_d1q2(
+ lattice_d1q2_256,
+):
assert len(lattice_d1q2_256.registers) == 256
- assert all(reg.size == 2 for reg in lattice_d1q2_256.registers)
+ assert all(reg.size == 2 for reg in lattice_d1q2_256.velocity_register)
assert lattice_d1q2_256.circuit.num_qubits == 512
+ lattice_d1q2_256.set_geometries(
+ [
+ [{"shape": "cuboid", "x": [12, 27], "boundary": "bounceback"}],
+ [{"shape": "cuboid", "x": [12, 27], "boundary": "specular"}],
+ ]
+ )
+
+ assert len(lattice_d1q2_256.registers) == 256 + 1
+ assert all(reg.size == 2 for reg in lattice_d1q2_256.velocity_register)
+ assert lattice_d1q2_256.circuit.num_qubits == 512 + 1 # One extra marker qubit
+
+ lattice_d1q2_256.set_geometries(
+ [[{"shape": "cuboid", "x": [12, 27], "boundary": "bounceback"}]]
+ )
+
+ 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 # Marker qubit removed
+
+
+def test_lqlga_lattice_num_registers_multiple_geometries_once_many_d1q2(
+ lattice_d1q2_256,
+):
+ 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
+
+ lattice_d1q2_256.set_geometries(
+ [
+ [{"shape": "cuboid", "x": [12, 27], "boundary": "bounceback"}]
+ + (
+ [{"shape": "cuboid", "x": [42, 43], "boundary": "specular"}]
+ if i % 3 == 0
+ else []
+ )
+ for i in range(67)
+ ]
+ )
+
+ assert len(lattice_d1q2_256.registers) == 256 + 1
+ assert all(reg.size == 2 for reg in lattice_d1q2_256.velocity_register)
+ assert lattice_d1q2_256.circuit.num_qubits == 512 + 7 # 7 qubits to encode 67 geometries
+
def test_lqlga_lattice_num_registers_d2q4(lattice_d2q4_256_8):
assert len(lattice_d2q4_256_8.registers) == 256 * 8
- assert all(reg.size == 4 for reg in lattice_d2q4_256_8.registers)
+ assert all(reg.size == 4 for reg in lattice_d2q4_256_8.velocity_register)
assert lattice_d2q4_256_8.circuit.num_qubits == 256 * 8 * 4
From ce10060c02c6f0192eb624bbe3687de3f598dd3e Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Wed, 3 Sep 2025 22:28:09 +0200
Subject: [PATCH 12/16] Fix typo
---
qlbm/components/lqlga/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qlbm/components/lqlga/__init__.py b/qlbm/components/lqlga/__init__.py
index 3b2ca8c..ee261af 100644
--- a/qlbm/components/lqlga/__init__.py
+++ b/qlbm/components/lqlga/__init__.py
@@ -13,6 +13,6 @@
"LQLGA",
"LQLGAGridVelocityMeasurement",
"LQLGAReflectionOperator",
- "LQLGAMGReflectionOperator"
+ "LQLGAMGReflectionOperator",
"LQLGAStreamingOperator",
]
From 2b1cc141554518ff7a679ae77a027afb63227ebb Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Thu, 4 Sep 2025 14:35:19 +0200
Subject: [PATCH 13/16] Remove duplicate documentation nblinks
---
examples/index.rst | 20 --------------------
examples/notebooks/collisionless_vis.nblink | 3 ---
examples/notebooks/flowfield_vis.nblink | 3 ---
examples/notebooks/geometry_vis.nblink | 3 ---
examples/notebooks/lqlga_vis.nblink | 3 ---
examples/notebooks/spacetime_vis.nblink | 3 ---
6 files changed, 35 deletions(-)
delete mode 100644 examples/index.rst
delete mode 100644 examples/notebooks/collisionless_vis.nblink
delete mode 100644 examples/notebooks/flowfield_vis.nblink
delete mode 100644 examples/notebooks/geometry_vis.nblink
delete mode 100644 examples/notebooks/lqlga_vis.nblink
delete mode 100644 examples/notebooks/spacetime_vis.nblink
diff --git a/examples/index.rst b/examples/index.rst
deleted file mode 100644
index 4a34a27..0000000
--- a/examples/index.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-.. _tutorials:
-
-Tutorials
-================================
-
-We are working on integrating more current tutorials into the web documentation.
-More Jupyter notebooks that demonstrate the features of ``qlbm`` are available
-`in the demos directory of the GitHub repository `_.
-Check the ``README`` of the directory for an explanation of the different available demos.
-
-Currently, the following visualization tutorials are available online:
-
-.. toctree::
- :caption: Tutorials
- :maxdepth: 2
-
- notebooks/collisionless_vis
- notebooks/spacetime_vis
- notebooks/geometry_vis
- notebooks/flowfield_vis
diff --git a/examples/notebooks/collisionless_vis.nblink b/examples/notebooks/collisionless_vis.nblink
deleted file mode 100644
index 35cee09..0000000
--- a/examples/notebooks/collisionless_vis.nblink
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "path": "../../../../demos/visualization/collisionless_components.ipynb"
-}
\ No newline at end of file
diff --git a/examples/notebooks/flowfield_vis.nblink b/examples/notebooks/flowfield_vis.nblink
deleted file mode 100644
index 6e10e1e..0000000
--- a/examples/notebooks/flowfield_vis.nblink
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "path": "../../../../demos/visualization/flowfields.ipynb"
-}
\ No newline at end of file
diff --git a/examples/notebooks/geometry_vis.nblink b/examples/notebooks/geometry_vis.nblink
deleted file mode 100644
index 0da5790..0000000
--- a/examples/notebooks/geometry_vis.nblink
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "path": "../../../../demos/visualization/geometry.ipynb"
-}
\ No newline at end of file
diff --git a/examples/notebooks/lqlga_vis.nblink b/examples/notebooks/lqlga_vis.nblink
deleted file mode 100644
index a8ca849..0000000
--- a/examples/notebooks/lqlga_vis.nblink
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "path": "../../../../demos/visualization/lqlga_components.ipynb"
-}
\ No newline at end of file
diff --git a/examples/notebooks/spacetime_vis.nblink b/examples/notebooks/spacetime_vis.nblink
deleted file mode 100644
index 68905b1..0000000
--- a/examples/notebooks/spacetime_vis.nblink
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "path": "../../../../demos/visualization/spacetime_components.ipynb"
-}
\ No newline at end of file
From d6553188e9161a9d1598f3c30eb1b656eeed110a Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Thu, 4 Sep 2025 14:35:51 +0200
Subject: [PATCH 14/16] Include LQLGA in web tutorials
---
docs/source/examples/index.rst | 1 +
docs/source/examples/notebooks/lqlga_vis.nblink | 3 +++
2 files changed, 4 insertions(+)
create mode 100644 docs/source/examples/notebooks/lqlga_vis.nblink
diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst
index 4a34a27..d247629 100644
--- a/docs/source/examples/index.rst
+++ b/docs/source/examples/index.rst
@@ -16,5 +16,6 @@ Currently, the following visualization tutorials are available online:
notebooks/collisionless_vis
notebooks/spacetime_vis
+ notebooks/lqlga_vis
notebooks/geometry_vis
notebooks/flowfield_vis
diff --git a/docs/source/examples/notebooks/lqlga_vis.nblink b/docs/source/examples/notebooks/lqlga_vis.nblink
new file mode 100644
index 0000000..a8ca849
--- /dev/null
+++ b/docs/source/examples/notebooks/lqlga_vis.nblink
@@ -0,0 +1,3 @@
+{
+ "path": "../../../../demos/visualization/lqlga_components.ipynb"
+}
\ No newline at end of file
From c5b06786a9cd45edd1338211e36579229a2f18d0 Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Thu, 4 Sep 2025 14:55:27 +0200
Subject: [PATCH 15/16] Update notebook titles
---
demos/visualization/collisionless_components.ipynb | 2 +-
demos/visualization/lqlga_components.ipynb | 14 +++++++++++---
2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/demos/visualization/collisionless_components.ipynb b/demos/visualization/collisionless_components.ipynb
index 549d556..218f5a0 100644
--- a/demos/visualization/collisionless_components.ipynb
+++ b/demos/visualization/collisionless_components.ipynb
@@ -5,7 +5,7 @@
"id": "ca7a55e2-5729-4a0c-b1bc-19ace7be8100",
"metadata": {},
"source": [
- "# Visualizing Collisionless circuits"
+ "# Visualizing Collisionless QLBM circuits"
]
},
{
diff --git a/demos/visualization/lqlga_components.ipynb b/demos/visualization/lqlga_components.ipynb
index 4786403..25719bd 100644
--- a/demos/visualization/lqlga_components.ipynb
+++ b/demos/visualization/lqlga_components.ipynb
@@ -1,5 +1,13 @@
{
"cells": [
+ {
+ "cell_type": "markdown",
+ "id": "c3a6c231",
+ "metadata": {},
+ "source": [
+ "# Visualizing LQLGA circuits"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -11,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 "
]
},
{
@@ -83,7 +91,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3 (ipykernel)",
+ "display_name": "qlbm-cpu-venv",
"language": "python",
"name": "python3"
},
@@ -97,7 +105,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.12.10"
+ "version": "3.13.7"
}
},
"nbformat": 4,
From c5627e199c340ba4ae8f7089caf15260f91186fc Mon Sep 17 00:00:00 2001
From: Calin Georgescu
Date: Thu, 4 Sep 2025 15:13:48 +0200
Subject: [PATCH 16/16] Update simulation demo notebooks
---
.../simulation/collisionless_simulation.ipynb | 14 +-
demos/simulation/lqlga_simulation.ipynb | 151 +-----------------
demos/simulation/spacetime_simulation.ipynb | 13 +-
3 files changed, 10 insertions(+), 168 deletions(-)
diff --git a/demos/simulation/collisionless_simulation.ipynb b/demos/simulation/collisionless_simulation.ipynb
index a74da16..64490ad 100644
--- a/demos/simulation/collisionless_simulation.ipynb
+++ b/demos/simulation/collisionless_simulation.ipynb
@@ -1,15 +1,5 @@
{
"cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "6e87b04a-a016-4208-b38f-62a84692aedc",
- "metadata": {},
- "outputs": [],
- "source": [
- "%pip install qlbm matplotlib seaborn pandas"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -131,7 +121,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3 (ipykernel)",
+ "display_name": "qlbm-cpu-venv",
"language": "python",
"name": "python3"
},
@@ -145,7 +135,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.12"
+ "version": "3.13.7"
}
},
"nbformat": 4,
diff --git a/demos/simulation/lqlga_simulation.ipynb b/demos/simulation/lqlga_simulation.ipynb
index 15ebf1d..2ac16f8 100644
--- a/demos/simulation/lqlga_simulation.ipynb
+++ b/demos/simulation/lqlga_simulation.ipynb
@@ -37,137 +37,10 @@
" \"geometry\": [{\"shape\": \"cuboid\", \"x\": [3, 5], \"boundary\": \"bounceback\"}],\n",
" },\n",
")\n",
- "initial_conditions = LQGLAInitialConditions(lattice, [(tuple([2]), (True, True, True))])\n",
- "initial_conditions.draw(\"mpl\")\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "05e422b4",
- "metadata": {},
- "outputs": [],
- "source": [
- "from qlbm.components.lqlga import LQLGAStreamingOperator\n",
- "from qlbm.lattice import LQLGALattice\n",
"\n",
- "lattice = LQLGALattice(\n",
- " {\n",
- " \"lattice\": {\n",
- " \"dim\": {\"x\": 4},\n",
- " \"velocities\": \"D1Q3\",\n",
- " },\n",
- " \"geometry\": [],\n",
- " # \"geometry\": [{\"shape\": \"cuboid\", \"x\": [3, 5], \"boundary\": \"bounceback\"}],\n",
- " },\n",
- ")\n",
- "streaming_operator = LQLGAStreamingOperator(lattice)\n",
- "streaming_operator.draw(\"mpl\")\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "020bb111",
- "metadata": {},
- "outputs": [],
- "source": [
- "from qlbm.components.lqlga import LQLGAReflectionOperator\n",
- "from qlbm.lattice import LQLGALattice\n",
"\n",
- "lattice = LQLGALattice(\n",
- " {\n",
- " \"lattice\": {\n",
- " \"dim\": {\"x\": 7},\n",
- " \"velocities\": \"D1Q3\",\n",
- " },\n",
- " \"geometry\": [{\"shape\": \"cuboid\", \"x\": [3, 5], \"boundary\": \"bounceback\"}],\n",
- " },\n",
- ")\n",
- "reflection_operator = LQLGAReflectionOperator(\n",
- " lattice, shapes=lattice.shapes[\"bounceback\"]\n",
- ")\n",
- "reflection_operator.draw(\"mpl\")\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "db7e5c99",
- "metadata": {},
- "outputs": [],
- "source": [
- "from qlbm.components.lqlga import LQLGA\n",
- "from qlbm.lattice import LQLGALattice\n",
- "\n",
- "lattice = LQLGALattice(\n",
- " {\n",
- " \"lattice\": {\n",
- " \"dim\": {\"x\": 7},\n",
- " \"velocities\": \"D1Q3\",\n",
- " },\n",
- " \"geometry\": [{\"shape\": \"cuboid\", \"x\": [3, 5], \"boundary\": \"bounceback\"}],\n",
- " },\n",
- ")\n",
- "\n",
- "LQLGA(lattice=lattice).draw(\"mpl\")\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "bb1f01f6",
- "metadata": {},
- "outputs": [],
- "source": [
- "from qlbm.components.lqlga import LQLGAGridVelocityMeasurement\n",
- "from qlbm.lattice import LQLGALattice\n",
- "\n",
- "lattice = LQLGALattice(\n",
- " {\n",
- " \"lattice\": {\n",
- " \"dim\": {\"x\": 5},\n",
- " \"velocities\": \"D1Q3\",\n",
- " },\n",
- " \"geometry\": [],\n",
- " },\n",
- ")\n",
- "\n",
- "LQLGAGridVelocityMeasurement(lattice=lattice).draw(\"mpl\")\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "34715b68",
- "metadata": {},
- "outputs": [],
- "source": [
- "lattice = LQLGALattice(\n",
- " {\n",
- " \"lattice\": {\n",
- " \"dim\": {\"x\": 4},\n",
- " \"velocities\": \"D1Q3\",\n",
- " },\n",
- " \"geometry\": [],\n",
- " # \"geometry\": [{\"shape\": \"cuboid\", \"x\": [3, 5], \"boundary\": \"bounceback\"}],\n",
- " },\n",
- ")\n",
- "\n",
- "output_dir = f\"qlbm-output/lqlga-{lattice.logger_name()}-qiskit\"\n",
- "create_directory_and_parents(output_dir)\n",
- "\n",
- "lattice.circuit.draw(\"mpl\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "b084dda3",
- "metadata": {},
- "outputs": [],
- "source": [
- "LQLGA(lattice).circuit.draw(\"mpl\")"
+ "output_dir = \"qlbm-output/lqlga-d1q3-7-qiskit\"\n",
+ "create_directory_and_parents(output_dir)\n"
]
},
{
@@ -203,12 +76,10 @@
"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",
"NUM_SHOTS = 2**10\n",
"\n",
"# Number of timesteps to simulate\n",
- "NUM_STEPS = 100\n",
+ "NUM_STEPS = 20\n",
"\n",
"# Create a runner object to simulate the circuit\n",
"runner = QiskitRunner(\n",
@@ -228,17 +99,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "76799b9e",
- "metadata": {},
- "outputs": [],
- "source": [
- "cfg.initial_conditions.draw(\"mpl\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "244ed487",
+ "id": "ed774960",
"metadata": {},
"outputs": [],
"source": []
@@ -246,7 +107,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "qiskit2-venv",
+ "display_name": "qlbm-cpu-venv",
"language": "python",
"name": "python3"
},
@@ -260,7 +121,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.13.0"
+ "version": "3.13.7"
}
},
"nbformat": 4,
diff --git a/demos/simulation/spacetime_simulation.ipynb b/demos/simulation/spacetime_simulation.ipynb
index 112c828..2668488 100644
--- a/demos/simulation/spacetime_simulation.ipynb
+++ b/demos/simulation/spacetime_simulation.ipynb
@@ -1,14 +1,5 @@
{
"cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%pip install qlbm matplotlib seaborn pandas"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -130,7 +121,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "qiskit2-venv",
+ "display_name": "qlbm-cpu-venv",
"language": "python",
"name": "python3"
},
@@ -144,7 +135,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.13.0"
+ "version": "3.13.7"
}
},
"nbformat": 4,