diff --git a/README.md b/README.md index 010d71b..5c35435 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ CI + + + PyPI version diff --git a/docs/_static/cards/jupyter.png b/docs/_static/cards/jupyter.png deleted file mode 100644 index eda35a2..0000000 Binary files a/docs/_static/cards/jupyter.png and /dev/null differ diff --git a/docs/_static/cards/python.png b/docs/_static/cards/python.png deleted file mode 100644 index 49ea8f5..0000000 Binary files a/docs/_static/cards/python.png and /dev/null differ diff --git a/docs/_static/cards/terminal.png b/docs/_static/cards/terminal.png deleted file mode 100644 index e1ce211..0000000 Binary files a/docs/_static/cards/terminal.png and /dev/null differ diff --git a/docs/conf.py b/docs/conf.py index 7609c67..dfe050b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ import qbraid_algorithms -project = "qbraid-algorithms" +project = "qBraid" copyright = "2024, qBraid Development Team" author = "qBraid Development Team" @@ -25,6 +25,7 @@ "sphinx.ext.autodoc", "sphinx_autodoc_typehints", "sphinx.ext.autosummary", + "sphinx_copybutton" ] autodoc_mock_imports = ["torchvision"] diff --git a/docs/index.rst b/docs/index.rst index afac2f1..fed0120 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,3 @@ -Documentation -============== - .. raw:: html @@ -49,47 +46,57 @@ Documentation

- qbraid logo + qbraid logo qBraid - + | algorithms

- A cloud-based platform for quantum computing. + Build hybrid quantum-classical algorithms with qBraid.

-
-
- -
-

Lab

- terminal -
-
-
- -
- -
-

CLI

- terminal -
-
-
- -
- -
-

SDK

- terminal -
-
-
-
- | +:Release: |release| + +Overview +--------- + +Python package for building, simulating, and benchmarking hybrid quantum-classical algorithms. + + +Installation +------------- + +qbraid-algorithms requires Python 3.9 or greater, and can be installed with pip as follows: + +.. code-block:: bash + + pip install qbraid-algorithms + + +Install from Source +^^^^^^^^^^^^^^^^^^^^ + +You can also install from source by cloning this repository and running a pip install command in the root directory of the repository: + +.. code-block:: bash + + git clone https://github.com/qBraid/qbraid-algorithms.git + cd qbraid-algorithms + pip3 install . + + +Resources +---------- + +- `User Guide `_ +- `Example Notebooks `_ +- `API Reference `_ + +| + .. toctree:: :maxdepth: 1 :caption: API Reference diff --git a/examples/sandbox/README.md b/examples/sandbox/README.md new file mode 100644 index 0000000..bda4cdb --- /dev/null +++ b/examples/sandbox/README.md @@ -0,0 +1,3 @@ +# qBraid-Algorithms Developer Sandbox + +This sandbox directory is provided as a collaborative and temporary space where contributors can freely share experimental notebooks and early-stage code. Its main purpose is to increase transparency about ongoing work, thus enhancing collaboration. Feel free to push your notebooks and code frequently, regardless of their completeness or correctness. This directory will be removed with the release of version 0.1 of the `qbraid-algorithms` project. diff --git a/pyproject.toml b/pyproject.toml index 341d24f..0f668d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = ["torch>=2.3.0,<3.0", "numpy>=1.17,<2.1", "scipy~=1.13.1", "bloqa [project.urls] Homepage = "https://github.com/qBraid/qbraid-algorithms" -Documentation = "https://docs.qbraid.com/en/stable/" +Documentation = "https://docs.qbraid.com" "Bug Tracker" = "https://github.com/qBraid/qbraid-algorithms/issues" Discord = "https://discord.gg/TPBU2sa8Et" "Launch on Lab" = "https://account.qbraid.com/?gitHubUrl=https://github.com/qBraid/qbraid-algorithms.git" @@ -38,7 +38,7 @@ Discord = "https://discord.gg/TPBU2sa8Et" visualization = ["matplotlib"] test = ["pytest", "pytest-cov"] lint = ["isort", "ruff", "qbraid-cli"] -docs = ["sphinx~=7.3.7", "sphinx-autodoc-typehints>=1.24,<2.3", "sphinx-rtd-theme~=2.0.0", "docutils<0.22"] +docs = ["sphinx~=7.3.7", "sphinx-autodoc-typehints>=1.24,<2.3", "sphinx-rtd-theme~=2.0.0", "docutils<0.22", "sphinx-copybutton"] [tool.setuptools_scm] write_to = "qbraid_algorithms/_version.py" @@ -67,7 +67,7 @@ exclude = ''' [tool.pylint.'MESSAGES CONTROL'] max-line-length = 100 -disable = "W0108, W0511, W0401, R0902" +disable = "W0108,W0511,W0401,R0902,R0903,R0913" [tool.pylint.MASTER] ignore-paths = [ diff --git a/qbraid_algorithms/qrc/__init__.py b/qbraid_algorithms/qrc/__init__.py index cf61dc8..cc88a45 100644 --- a/qbraid_algorithms/qrc/__init__.py +++ b/qbraid_algorithms/qrc/__init__.py @@ -19,10 +19,31 @@ QRCModel MagnusExpansion + DetuningLayer + MagnusExpansion + AnalogProgramEvolver + +Functions +---------- + +.. autosummary:: + :toctree: ../stubs/ + + one_hot_encoding + pca_reduction """ -from .magnus import MagnusExpansion -from .model import QRCModel +from .encoding import one_hot_encoding, pca_reduction +from .magnus_expansion import MagnusExpansion +from .qrc_model import DetuningLayer, QRCModel +from .time_evolution import AnalogProgramEvolver -__all__ = ["QRCModel", "MagnusExpansion"] +__all__ = [ + "QRCModel", + "MagnusExpansion", + "DetuningLayer", + "AnalogProgramEvolver", + "one_hot_encoding", + "pca_reduction", +] diff --git a/qbraid_algorithms/qrc/dynamics.py b/qbraid_algorithms/qrc/dynamics.py deleted file mode 100644 index 9511cf1..0000000 --- a/qbraid_algorithms/qrc/dynamics.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (C) 2024 qBraid -# -# This file is part of the qBraid-SDK -# -# The qBraid-SDK is free software released under the GNU General Public License v3 -# or later. You can redistribute and/or modify it under the terms of the GPL v3. -# See the LICENSE file in the project root or . -# -# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. - -""" -Module for simulating the dynamics of a quantum reservoir. - -""" - -import math -from dataclasses import dataclass, field -from typing import Any - -import numpy as np -from bloqade.emulate.ir.atom_type import AtomType -from bloqade.emulate.ir.emulator import Register -from bloqade.emulate.ir.state_vector import RydbergHamiltonian - -from .krylov import KrylovEvolution - - -@dataclass -class DetuningLayer: - """Class representing a detuning layer in a quantum reservoir.""" - - atoms: list[AtomType] # Atom positions - readouts: list[Any] # Readout observables - omega: float # Rabi frequency - t_start: float # Evolution starting time - t_end: float # Evolution ending time - step: float # Readout time step - reg: Register = field( - default_factory=lambda *args, **kwargs: Register(*args, **kwargs) - ) # Quantum state storage - - -def generate_sites(lattice_type, dimension, scale): - """ - Generate positions for atoms on a specified lattice type with a given scale. - - Args: - lattice_type (Any): Type of the lattice. - dimension (int): Number of principal components. - scale (float): Scale factor for lattice spacing. - - Returns: - Any: Positions of atoms. - - TODO: Implement actual site generation based on lattice type. - """ - raise NotImplementedError - - -def rydberg_h(atoms: list[AtomType], delta: float, omega: float) -> RydbergHamiltonian: - """ - Generate the Hamiltonian for a Rydberg atom system. - - Args: - atoms (list[AtomType]): Atom positions. - omega (float): Rabi frequency. - - Returns: - RydbergHamiltonian: Hamiltonian matrix. - """ - raise NotImplementedError - - -def set_zero_state(reg: Register): - """ - Set the quantum state to the zero state. - - Args: - reg (Register): Quantum state storage. - """ - raise NotImplementedError - - -def apply_layer(layer: DetuningLayer, x: np.ndarray) -> np.ndarray: - """ - Simulate quantum evolution and record output for a given set of PCA values (x). - - Args: - layer (DetuningLayer): Configuration and quantum state of the layer. - x (np.ndarray): Vector or matrix of real numbers representing PCA values for each image. - - Returns: - np.ndarray: Output values from the simulation. - - TODO: Implement the actual simulation using suitable quantum simulation libraries. - """ - h = rydberg_h(layer.atoms, x, layer.omega) - - reg = layer.reg - reg = set_zero_state(reg) - - t_start = layer.t_start - t_end = layer.t_end - t_step = layer.step - start_clock = NotImplemented - - # initialize output vector - steps = math.floor((t_end - t_start) / t_step) - out = np.zeros(steps * len(layer.readouts)) - - # Numerically simulate the quantum evolution with Krylov methods and store the readouts - i = 1 - prob = KrylovEvolution( - reg, start_clock=start_clock, durations=[t_step] * steps, hamiltonian=h, options=None - ) - for i in range(steps): - # ignore first time step, this is just the initial state - if i == 0: - continue - - # TODO: Implement the emulation step function. - # NOTE: The following lines are placehoders. They are not correct, and should be replaced. - prob.emulate_step(i, t_start + i * t_step, t_step) - for j, readout in enumerate(layer.readouts): - out[i * len(layer.readouts) + j] = readout(prob) - - return out diff --git a/qbraid_algorithms/qrc/encoding.py b/qbraid_algorithms/qrc/encoding.py index 88cf1ec..9bed34c 100644 --- a/qbraid_algorithms/qrc/encoding.py +++ b/qbraid_algorithms/qrc/encoding.py @@ -9,12 +9,11 @@ # THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. """ -Module for encoding of data. +Module for encoding of classical data. """ import numpy as np import torch -from sklearn.preprocessing import OneHotEncoder def one_hot_encoding(labels: np.ndarray, train: bool = True) -> torch.Tensor: @@ -28,6 +27,9 @@ def one_hot_encoding(labels: np.ndarray, train: bool = True) -> torch.Tensor: torch.Tensor: The one-hot encoded matrix where each row corresponds to a label. """ + # pylint: disable-next=import-outside-toplevel + from sklearn.preprocessing import OneHotEncoder + encoder = OneHotEncoder(sparse_output=False) reshaped_data = labels.reshape(-1, 1) if train: @@ -35,3 +37,42 @@ def one_hot_encoding(labels: np.ndarray, train: bool = True) -> torch.Tensor: else: encoded_data = encoder.transform(reshaped_data) return encoded_data + + +def pca_reduction( + data: torch.Tensor, + n_components: int, + data_dim: int, + delta_max: int, + train: bool = True, +) -> torch.Tensor: + """ + Perform PCA reduction on the provided data using PyTorch's pca_lowrank to + reduce its dimensionality. + + Args: + data (torch.Tensor): The input data tensor where each row represents a sample. + n_components (int): The number of principal components to retain. + data_dim (int): The dimension of the input data required for doing PCA. + delta_max (int): Scaling factor to bring PCA vals into a feasible range for local detuning. + train (bool, optional): Whether the data is training data. Defaults to True. + + Returns: + torch.Tensor: The transformed data + """ + # pylint: disable-next=import-outside-toplevel + from sklearn.decomposition import PCA + + # Perform PCA on training data + pca = PCA(n_components=n_components) + data_array: np.ndarray = data.data.numpy() + data_reshaped = data_array.reshape(-1, data_dim) + if train: + data_pca = pca.fit_transform(data_reshaped) + else: + data_pca = pca.transform(data_reshaped) + + # Scale PCA values to feasible range of local detuning + scaled_data_pca = data_pca / np.max(np.abs(data_pca)) * delta_max + + return scaled_data_pca diff --git a/qbraid_algorithms/qrc/krylov.py b/qbraid_algorithms/qrc/krylov.py deleted file mode 100644 index 7b1f9c4..0000000 --- a/qbraid_algorithms/qrc/krylov.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (C) 2024 qBraid -# -# This file is part of the qBraid-SDK -# -# The qBraid-SDK is free software released under the GNU General Public License v3 -# or later. You can redistribute and/or modify it under the terms of the GPL v3. -# See the LICENSE file in the project root or . -# -# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. - -""" -Module for quantum time evolution using Krylov subspace methods. - -""" -from dataclasses import dataclass - -import numpy as np - -# from bloqade.atom_arrangement import Square -# from bloqade.emulate.ir.emulator import Register -from bloqade.emulate.ir.state_vector import RydbergHamiltonian, StateVector -from scipy.linalg import expm - - -# Placeholder for Krylov options with dummy attributes -class KrylovOptions: # pylint: disable=too-few-public-methods - """Class that describes options for a Krylov subspace method. - - Args: - progress (bool): Whether to show progress during the evolution. - progress_name (str): Name for the progress indicator. - normalize_step (int): Frequency of normalization steps. - normalize_finally (bool): Whether to normalize the quantum state at the end. - tol (float): Tolerance for numerical operations. - - """ - - # pylint: disable-next=too-many-arguments - def __init__( - self, - progress: bool = False, - progress_name: str = "emulating", - normalize_step: int = 1, - normalize_finally: bool = True, - tol: float = 1e-7, - ): - self.progress = progress - self.progress_name = progress_name - self.normalize_step = normalize_step - self.normalize_finally = normalize_finally - self.tol = tol - - -@dataclass -class KrylovEvolution: - """Class that describes a time evolution using Krylov subspace methods. - - Args: - reg (Register): Quantum register for the evolution. - start_clock (float): Start time of the evolution. - durations (list[float]): Durations of each time step. - hamiltonian (RydbergHamiltonian): Hamiltonian for the evolution. - options (KrylovOptions): Options for the evolution process. - """ - - reg: StateVector # Register? - start_clock: float - durations: list[float] - hamiltonian: RydbergHamiltonian - options: KrylovOptions - - def generate_krylov_basis(self, h, psi_0, m): - """Generates the first m Krylov basis vectors.""" - n = len(psi_0) - k = np.zeros((n, m), dtype=complex) - k[:, 0] = psi_0 / np.linalg.norm(psi_0) - for j in range(1, m): - k[:, j] = h @ k[:, j - 1] - for i in range(j): - k[:, j] -= np.dot(k[:, i], k[:, j]) * k[:, i] - k[:, j] /= np.linalg.norm(k[:, j]) - return k - - def gram_schmidt(self, v): - """Orthonormalizes the vectors using the Gram-Schmidt process.""" - q, _ = np.linalg.qr(v) - return q - - def krylov_evolution(self, h, psi_0, t, m): - """Projects H onto the Krylov subspace and computes the time evolution.""" - k = self.generate_krylov_basis(h, psi_0, m) - h_m = k.T.conj() @ h @ k - exp_h_m = expm(-1j * h_m * t) - psi_t = k @ exp_h_m @ k.T.conj() @ psi_0 - return psi_t - - # pylint: disable-next=unused-argument - def emulate_step(self, step: int, clock: float, duration: float) -> "KrylovEvolution": - """ - Simulate a single time step of quantum evolution using the Krylov subspace method. - - Args: - step: Current step index. - clock: Current time. - duration: Duration of the current time step. - - Returns: - Self with the quantum state updated. - - TODO: Implement the emulation step function. - """ - try: - psi_0 = self.reg - evolved_state = self.krylov_evolution( - self.hamiltonian.rydberg, psi_0, duration, len(self.durations) - ) - self.reg = evolved_state - except Exception as err: - raise NotImplementedError("Emulation step failed") from err - - def normalize_register(self) -> None: - """ - Normalize the quantum register if specified in options. - - TODO: Implement the normalization logic. - """ - # https://github.com/QuEraComputing/bloqade-python/blob/17585b21ad8f099ac8ffe126257e8ffb6c7f4588/src/bloqade/emulate/ir/state_vector.py#L208-L215 - # data = self.reg.data - # data /= np.linalg.norm(data) - # self.reg.data = data - if self.options.normalize_finally: - norm = np.linalg.norm(self.reg) - if norm > self.options.tol: - self.reg /= norm diff --git a/qbraid_algorithms/qrc/linear.py b/qbraid_algorithms/qrc/linear.py deleted file mode 100644 index d44fa26..0000000 --- a/qbraid_algorithms/qrc/linear.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (C) 2024 qBraid -# -# This file is part of the qBraid-SDK -# -# The qBraid-SDK is free software released under the GNU General Public License v3 -# or later. You can redistribute and/or modify it under the terms of the GPL v3. -# See the LICENSE file in the project root or . -# -# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. - -""" -Module for performing linear regression on output data. - -""" - -import torch - - -def linear_regression(embeddings: torch.Tensor): - """ - Perform linear regression on the input data using PyTorch's Linear module. - - Args: - embeddings (torch.Tensor): The input data tensor. - - Returns: - torch.Tensor: The predicted output tensor. - - TODO: Implement the linear regression model, possibly using torch.nn.Linear. - """ - raise NotImplementedError diff --git a/qbraid_algorithms/qrc/magnus.py b/qbraid_algorithms/qrc/magnus_expansion.py similarity index 100% rename from qbraid_algorithms/qrc/magnus.py rename to qbraid_algorithms/qrc/magnus_expansion.py diff --git a/qbraid_algorithms/qrc/model.py b/qbraid_algorithms/qrc/model.py deleted file mode 100644 index 8b6c318..0000000 --- a/qbraid_algorithms/qrc/model.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (C) 2024 qBraid -# -# This file is part of the qBraid-SDK -# -# The qBraid-SDK is free software released under the GNU General Public License v3 -# or later. You can redistribute and/or modify it under the terms of the GPL v3. -# See the LICENSE file in the project root or . -# -# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. - -""" -Module for assembling QRC model components and computing prediction. - -""" - -from dataclasses import dataclass -from typing import Any, Callable - -import numpy as np - -from .dynamics import DetuningLayer - -# from torch import nn - - -@dataclass -class QRCModel: - """Quantum Reservoir Computing (QRC) model.""" - - model_pca: Any # PCA model component - spectral: float # Spectral radius or related parameter - delta_max: float # Maximum delta parameter - detuning_layer: DetuningLayer # Detuning layer - linear_regression: Callable # Linear regression model - - def __call__(self, xs: np.ndarray) -> list[int]: - """ - Compute predictions for input images or data using quantum reservoir computing. - - Args: - xs (np.ndarray): Input data, either a batch of images or a single image. - - Returns: - list[int]: Predicted classes or values. - - TODO: Implement the transformation and prediction steps. - """ - raise NotImplementedError - - -# Define neural network model -# class Net(nn.Module): -# def __init__(self): -# super(Net, self).__init__() -# self.fc1 = nn.Linear(dim_pca, 10) -# def forward(self, x): -# x = torch.relu(self.fc1(x)) -# return x -# # Train classical model using PCA features -# model_reg = Net() -# criterion = nn.CrossEntropyLoss() -# optimizer = optim.Adam(model_reg.parameters(), lr=0.01) -# for epoch in range(1000): -# for x, y in train_loader: -# x = x.view(-1, 28*28) -# x_pca = pca.transform(x.numpy()) -# x_pca = torch.tensor(x_pca, dtype=torch.float32) -# y = torch.tensor(y, dtype=torch.long) -# optimizer.zero_grad() -# output = model_reg(x_pca) -# loss = criterion(output, y) -# loss.backward() -# optimizer.step() -# # Train QRC model using quantum reservoir computing -# pre_layer = DetuningLayer(atoms, readouts, Ω, t_start, t_end, step) -# model_qrc = Net() -# for epoch in range(1000): -# for x, y in train_loader: -# x = x.view(-1, 28*28) -# x_pca = pca.transform(x.numpy()) diff --git a/qbraid_algorithms/qrc/pca.py b/qbraid_algorithms/qrc/pca.py deleted file mode 100644 index a7139d0..0000000 --- a/qbraid_algorithms/qrc/pca.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (C) 2024 qBraid -# -# This file is part of the qBraid-SDK -# -# The qBraid-SDK is free software released under the GNU General Public License v3 -# or later. You can redistribute and/or modify it under the terms of the GPL v3. -# See the LICENSE file in the project root or . -# -# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. - -""" -Module implemting Principal Component Analysis (PCA) for dimensionality reduction. - -""" -import numpy as np -import torch -from sklearn.decomposition import PCA - - -def pca_reduction( - data: torch.Tensor, - n_components: int, - data_dim: int, - delta_max: int, - train: bool = True, -) -> torch.Tensor: - """ - Perform PCA reduction on the provided data using PyTorch's pca_lowrank to - reduce its dimensionality. - - Args: - data (torch.Tensor): The input data tensor where each row represents a sample. - n_components (int): The number of principal components to retain. - data_dim (int): The dimension of the input data required for doing PCA. - delta_max (int): Scaling factor to bring PCA vals into a feasible range for local detuning. - train (bool, optional): Whether the data is training data. Defaults to True. - - Returns: - torch.Tensor: The transformed data - """ - # Perform PCA on training data - pca = PCA(n_components=n_components) - data_array: np.ndarray = data.data.numpy() - data_reshaped = data_array.reshape(-1, data_dim) - if train: - data_pca = pca.fit_transform(data_reshaped) - else: - data_pca = pca.transform(data_reshaped) - - # Scale PCA values to feasible range of local detuning - scaled_data_pca = data_pca / np.max(np.abs(data_pca)) * delta_max - - return scaled_data_pca diff --git a/qbraid_algorithms/qrc/qrc_model.py b/qbraid_algorithms/qrc/qrc_model.py new file mode 100644 index 0000000..1d84a51 --- /dev/null +++ b/qbraid_algorithms/qrc/qrc_model.py @@ -0,0 +1,107 @@ +# Copyright (C) 2024 qBraid +# +# This file is part of the qBraid-SDK +# +# The qBraid-SDK is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. + +""" +Module for assembling QRC model components and computing prediction. + +""" + +from dataclasses import dataclass +from decimal import Decimal +from typing import Any + +import numpy as np +from bloqade.ir.location import Chain + +from .time_evolution import AnalogProgramEvolver + + +@dataclass +class DetuningLayer: + """Class representing a detuning layer in a quantum reservoir.""" + + num_sites: int # Number of sites in the chain lattice + lattice_spacing: float # Lattice spacing () + omega: float # Rabi frequency + step_size: float # Time evolution duration + num_steps: int # Number of time steps + + +class QRCModel: + """Quantum Reservoir Computing (QRC) model.""" + + def __init__(self, model_pca: Any, delta_max: float, detuning_layer: DetuningLayer): + """ + Initialize the Quantum Reservoir Computing model with necessary components. + + Args: + model_pca (Any): PCA model component. + delta_max (float): Maximum delta parameter. + detuning_layer (DetuningLayer): Detuning layer for the model. + """ + self.model_pca = model_pca + self.delta_max = delta_max + self.detuning_layer = detuning_layer + + def apply_detuning(self, x: np.ndarray) -> np.ndarray: + """ + Simulate quantum evolution and record output for a given set of values (x). + + Args: + x (np.ndarray): Vector or matrix of real numbers representing PCA values for each image. + + Returns: + np.ndarray: Output values from the simulation. + """ + layer = self.detuning_layer + + # using 0th order. Will need to modify to consider slew rate based on hardware + amplitude_omegas = [layer.omega] * (layer.num_steps - 2) + amplitudes = list(np.pad(amplitude_omegas, (1, 1), mode="constant")) + + durations = [Decimal(layer.step_size)] * (layer.num_steps - 1) + + atoms = Chain(layer.num_sites, lattice_spacing=layer.lattice_spacing) + + evolver = AnalogProgramEvolver(atoms=atoms, rabi_amplitudes=amplitudes, durations=durations) + probabilities = evolver.evolve(backend="emulator") + + # TODO: added dot as placeholder, will need to revisit + output_vector = np.dot(probabilities, x) + + return output_vector + + def linear_regression(self, embeddings): + """ + Perform linear regression on given data + + Args: + embeddings: The input data tensor. + + Returns: + Any: The predicted output tensor. + + TODO: Implement the linear regression model, possibly using torch.nn.Linear. + """ + raise NotImplementedError + + def predict(self, xs: np.ndarray) -> list[int]: + """ + Compute predictions for input images or data using quantum reservoir computing. + + Args: + xs (np.ndarray): Input data, either a batch of images or a single image. + + Returns: + list[int]: Predicted classes or values. + + TODO: Implement the transformation and prediction steps. + """ + raise NotImplementedError diff --git a/qbraid_algorithms/qrc/time_evolution.py b/qbraid_algorithms/qrc/time_evolution.py new file mode 100644 index 0000000..435b640 --- /dev/null +++ b/qbraid_algorithms/qrc/time_evolution.py @@ -0,0 +1,106 @@ +# Copyright (C) 2024 qBraid +# +# This file is part of the qBraid-SDK +# +# The qBraid-SDK is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. + +""" +Module for quantum time evolution using emulator or QPU. + +""" +from collections import OrderedDict +from decimal import Decimal + +import numpy as np +from bloqade.atom_arrangement import Chain +from bloqade.builder.field import Detuning + + +class AnalogProgramEvolver: + """Class for evolving program over discrete list of time steps. + + Attributes: + atoms (Chain): Chain lattice. + amplitudes (List[float]): List of Rabi oscillation amplitudes. + durations (List[Decimal]): List of pulse durations. + time_steps (list[float]): The times to evaluate the state vector. + """ + + SUPPORTED_BACKENDS = ["emulator", "qpu"] + + def __init__( + self, + atoms: Chain, + rabi_amplitudes: list[float], + durations: list[Decimal], + ): + """Initializes the AnalogEvolution with provided parameters. + + Args: + atoms (Chain): Chain lattice. + rabi_amplitudes (list[float]): Rabi amplitudes for each pulse. + durations (list[Decimal]): Duration of each pulse. + + """ + self.atoms = atoms + self.amplitudes = rabi_amplitudes + self.durations = durations + self.time_steps = self._get_time_steps(durations) + + @staticmethod + def _get_time_steps(durations: list[Decimal]) -> list[float]: + """Generate time steps from list of pulse durations.""" + return list(np.cumsum([0.0] + [float(d) for d in durations])) + + @staticmethod + def compute_rydberg_probs(num_sites: int, counts: OrderedDict) -> np.ndarray: + """Calculate the average probability distribution of Rydberg states over all shots. + + Args: + num_sites (int): Number of sites in the chain lattice + counts (OrderedDict): An OrderedDict where keys are bitstring state representations + and values are the counts of each state observed. + + Returns: + np.ndarray: The probability of each state, averaged over all shots. + """ + prob = np.zeros(num_sites) + + total_shots = 0 # Total number of shots in the counts + for key, val in counts.items(): + prob += np.array([float(bit) for bit in [*key]]) * val + total_shots += val + + prob /= total_shots + return prob + + def evolve(self, backend: str) -> np.ndarray: + """Evolves program over discrete list of time steps""" + detuning: Detuning = self.atoms.rydberg.rabi.amplitude + amp_waveform = detuning.uniform.constant(max(self.amplitudes), sum(self.durations)) + program = amp_waveform.detuning.uniform.piecewise_linear(self.durations, self.amplitudes) + + if backend == "emulator": + [emulation] = program.bloqade.python().hamiltonian() + emulation.evolve(times=self.time_steps) + return emulation.hamiltonian.tocsr(time=self.time_steps[-1]).toarray() + + if backend == "qpu": + # TODO: Revise for async task handling to avoid blocking while waiting for results. + bitstring_counts_batch: list[OrderedDict] = ( + program.braket.aquila.run_async(100).report().counts() + ) + if ( + len(bitstring_counts_batch) != 1 + ): # TODO: Double-check that counts list will always be length 1 here. + raise ValueError("Expected a single batch of counts.") + bitstring_counts = bitstring_counts_batch[0] + return self.compute_rydberg_probs(self.atoms.L, bitstring_counts) + + raise ValueError( + f"Backend {backend} is not supported. Supported backends are: {self.SUPPORTED_BACKENDS}" + ) diff --git a/tests/test_qrc_dynamics.py b/tests/test_qrc_dynamics.py deleted file mode 100644 index 77515af..0000000 --- a/tests/test_qrc_dynamics.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (C) 2024 qBraid -# -# This file is part of the qBraid-SDK -# -# The qBraid-SDK is free software released under the GNU General Public License v3 -# or later. You can redistribute and/or modify it under the terms of the GPL v3. -# See the LICENSE file in the project root or . -# -# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. - -# pylint: disable=redefined-outer-name - -""" -Unit tests for Quantum Reservoir Computing (QRC) dynamics modules. - -""" -from unittest.mock import Mock - -import numpy as np -import pytest -from bloqade.emulate.ir.atom_type import AtomType -from bloqade.emulate.ir.emulator import Register -from bloqade.emulate.ir.space import Space, SpaceType -from bloqade.emulate.ir.state_vector import StateVector -from numpy.typing import NDArray - -from qbraid_algorithms.qrc.krylov import KrylovEvolution, KrylovOptions -from qbraid_algorithms.qrc.magnus import MagnusExpansion - - -@pytest.fixture -def program_register() -> Register: - """Create a program register.""" - return Mock() - - -@pytest.fixture -def atom_type() -> AtomType: - """Create an atom type.""" - return Mock() - - -@pytest.fixture -def configurations() -> NDArray: - """Create configurations.""" - return Mock() - - -@pytest.fixture -def space_type() -> SpaceType: - """Create a space type.""" - return Mock() - - -@pytest.fixture -def space(program_register, atom_type, configurations, space_type) -> Space: - """Create a space object.""" - return Space( - space_type=space_type, - atom_type=atom_type, - program_register=program_register, - configurations=configurations, - ) - - -@pytest.mark.skip(reason="Not implemented yet") -def test_rbh(space): - """Test the Rydberg Blockade Hamiltonian (RBH)""" - initial_state = np.array([1, 0, 0, 0], dtype=complex) - - # Create a KrylovEvolution instance - krylov_options = KrylovOptions() - krylov_evolution = KrylovEvolution( - reg=StateVector(data=initial_state, space=space), - start_clock=0.0, - durations=[0.1, 0.2, 0.3], - hamiltonian=None, # This will be initialized in __post_init__ - options=krylov_options, - ) - - # Simulate the evolution (example step) - krylov_evolution.emulate_step(step=0, clock=0.0, duration=0.1) - - final_state = np.array([], dtype=complex) # dummy value - expected_value = StateVector(data=final_state, space=space) - - assert krylov_evolution.reg == expected_value - - -@pytest.mark.skip(reason="Not completed yet") -def test_simulate_dynamics(): - """Test the simulation of quantum dynamics using Magnus expansion.""" - # Define a simple Hamiltonian and initial state - h = np.array([[0, 1], [1, 0]], dtype=complex) # Simple Hamiltonian - psi0 = np.array([1, 0], dtype=complex) # Initial state - t_final = 1.0 - dt = 0.01 - - # Create an instance of MagnusExpansion - magnus = MagnusExpansion(h) - - # Simulate the dynamics - final_state = magnus.simulate_dynamics(psi0, t_final, dt) - - # Add assertions to check the final state - # For example: - expected_final_state = np.array([0.54030231 + 0.84147098j, 0.00000000 + 0.0j]) - np.testing.assert_allclose(final_state, expected_final_state, rtol=1e-5) diff --git a/tests/test_qrc_model.py b/tests/test_qrc_model.py new file mode 100644 index 0000000..fdb06d7 --- /dev/null +++ b/tests/test_qrc_model.py @@ -0,0 +1,30 @@ +# Copyright (C) 2024 qBraid +# +# This file is part of the qBraid-SDK +# +# The qBraid-SDK is free software released under the GNU General Public License v3 +# or later. You can redistribute and/or modify it under the terms of the GPL v3. +# See the LICENSE file in the project root or . +# +# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3. + +""" +Unit tests for the QRC (Quantum Reservoir Computing) model. + +""" +import numpy as np +import pytest + +from qbraid_algorithms.qrc import DetuningLayer, QRCModel, pca_reduction + + +@pytest.mark.parametrize("dim_pca", [3, 10]) +def test_detuning_layer(dim_pca): + """Test applying detuning layer to single feature vector.""" + hyperparams = {"lattice_spacing": 4, "omega": 2 * np.pi, "step_size": 0.5, "num_steps": 20} + detuning_layer = DetuningLayer(num_sites=dim_pca, **hyperparams) + model = QRCModel(model_pca=pca_reduction, delta_max=0.6, detuning_layer=detuning_layer) + + input_vector = np.random.rand(2**dim_pca) + output_vector = model.apply_detuning(input_vector) + assert np.shape(input_vector) == np.shape(output_vector)