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,