From 2f2e632ac5b7761c46f7d78a23ab4b4c5798724b Mon Sep 17 00:00:00 2001 From: Sungwoo Ahn Date: Tue, 28 Jan 2025 11:03:48 +0100 Subject: [PATCH] Qadence Transition (#34) * legacy prototype 14/01/2025 * remove folder from tracking * test * test * block system * check * block system done * parameters * reorganize * function lists * complete transition.md * complete transition.md * apply review --------- Co-authored-by: Sungwoo AHN --- docs/transition.md | 192 ++++++++++++++++++++++++ mkdocs.yml | 1 + qadence2/extensions/legacy/__init__.py | 11 +- qadence2/extensions/legacy/operators.py | 24 ++- qadence2/extensions/legacy/utils.py | 31 ++-- 5 files changed, 245 insertions(+), 14 deletions(-) create mode 100644 docs/transition.md diff --git a/docs/transition.md b/docs/transition.md new file mode 100644 index 0000000..208740a --- /dev/null +++ b/docs/transition.md @@ -0,0 +1,192 @@ + +# Transition Guide +This document is for Qadence users to use Qadence2 funcationalities with Qadence format. +For Qadence2 code details, please refer to https://github.com/pasqal-io/qadence2-core. + +## Operators + +Qadence operators can be used in the same way (including the order of target and control) in Qadence 2. + +Qadence +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence import RX, CNOT + +rx = RX(0, 0.5) +cnot = CNOT(0, 1) +``` + +Qadence2 +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence2.extensions.legacy import RX, CNOT + +rx = RX(0, 0.5) +cnot = CNOT(0, 1) +``` + +## Block System + +Building quantum expressions with `chain` and `kron` is also identical. + +Qadence +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence import X, Y, chain, kron + +chain_0 = chain(X(0), Y(0)) +chain_1 = chain(X(1), Y(1)) + +kron_block = kron(chain_0, chain_1) +``` + +Qadence2 +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence2.extensions.legacy import X, Y, chain, kron + +chain_0 = chain(X(0), Y(0)) +chain_1 = chain(X(1), Y(1)) + +kron_block = kron(chain_0, chain_1) +``` + +## Compose Functions + +Custom gates can also be applied in the same way, and with the definition change, Qadence2 now use `*` instead of `@`. + +Qadence +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence import X, Y, add + +def xy_int(i: int, j: int): + return (1/2) * (X(i)@X(j) + Y(i)@Y(j)) + +n_qubits = 3 + +xy_ham = add(xy_int(i, i+1) for i in range(n_qubits-1)) +``` + +Qadence2 +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence2.extensions.legacy import X, Y, add + +def xy_int(i: int, j: int): + return (1/2) * (X(i)*X(j) + Y(i)*Y(j)) + +n_qubits = 3 + +xy_ham = add(xy_int(i, i+1) for i in range(n_qubits-1)) +``` + +## Quantum Fourier Transform Example + +The code initializes a quantum circuit with `CPHASE` gates and applies `qft_layer` to transform the input quantum state into its frequency domain representation. + +Qadence +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence import H, CPHASE, PI, chain, kron + +def qft_layer(qs: tuple, l: int): + cphases = chain(CPHASE(qs[j], qs[l], PI/2**(j-l)) for j in range(l+1, len(qs))) + return H(qs[l]) * cphases + +def qft(qs: tuple): + return chain(qft_layer(qs, l) for l in range(len(qs))) +``` + +Qadence2 +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence2.extensions.legacy import H, CPHASE, PI, chain, kron + +def qft_layer(qs: tuple, l: int): + cphases = chain(CPHASE(qs[j], qs[l], PI/2**(j-l)) for j in range(l+1, len(qs))) + return H(qs[l]) * cphases + +def qft(qs: tuple): + return chain(qft_layer(qs, l) for l in range(len(qs))) +``` + + +## Quantum Models + +Qadence2 uses `Expression` and `IR` to represent the details of the quantum circuits and algorithms and `Platform` to match the executing backend, whereas Qadence is not separated. +As a result, Qadence can call `run,` `sample,` and `expectation` with the circuit representation itself. +However, Qadence2 requires it first to be compiled to execute them with the backends. +Qadence2 uses `compile_to_model` and `compile_to_backend` to compile them into `model` and `compiled_model,` respectively. After this procedure, it is only ready to execute `run,` `sample,` and `expectation.` + +Qadence +```python exec="on" source="material-block" html="1" session="getting_started" +import torch +from qadence import QuantumModel, PI, Z +from qadence import QuantumCircuit, RX, RY, chain, kron +from qadence import FeatureParameter, VariationalParameter + +phi = FeatureParameter("phi") + +block = chain( + kron(RX(0, phi), RY(1, phi)), +) + +circuit = QuantumCircuit(2, block) + +observable = Z(0) + Z(1) + +model = QuantumModel(circuit, observable) + +values = {"phi": torch.tensor(PI)} + +wf = model.run(values) +xs = model.sample(values, n_shots=100) +ex = model.expectation(values) +``` + +Qadence2 +```python exec="on" source="material-block" html="1" session="getting_started" +import torch +from qadence2.extensions.legacy import PI, Z, RX, RY, chain, kron +from qadence2_expressions import compile_to_model, parameter +from qadence2_platforms.compiler import compile_to_backend + +phi = parameter("phi") + +block = chain( + kron(RX(0, phi), RY(1, phi)), +) + +model = compile_to_model(block) +compiled_model = compile_to_backend(model, "pyqtorch") + +observable = Z(0) + Z(1) + +values = {"phi": torch.tensor(PI)} + +wf = compiled_model.run(values) +xs = compiled_model.sample(values, shots=100) +ex = compiled_model.expectation(values, observable=observable) +``` + +## Quantum Registers + +Qadence2 can represent the relationships between qubits using `grid_type`, `grid_scale`, and `qubit_position`. The `connectivity` in `qadence2_ir` is for accurately representing the connectivity between qubits. + +Qadence +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence import Register + +reg = Register.all_to_all(n_qubits = 4) +reg_line = Register.line(n_qubits = 4) +reg_squre = Register.square(qubits_side = 2) +reg_triang = Register.triangular_lattice(n_cells_row = 2, n_cells_col = 2) +``` + +Qadence2 +```python exec="on" source="material-block" html="1" session="getting_started" +from qadence2_expressions.core import set_grid_type, set_qubits_positions, set_grid_scale +from qadence2.extensions.legacy import PI, RX, RY, CZ +from qadence2_expressions import compile_to_model + +expr = RX(2, PI / 2) * CZ() * RY(0, PI / 2) + +set_grid_type("linear") # "square", "triangular" +set_qubits_positions([(-2, 1), (0, 0), (3, 1)]) +set_grid_scale(0.4) + +compile_to_model(expr) +``` diff --git a/mkdocs.yml b/mkdocs.yml index a2657df..ad6886b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,6 +5,7 @@ repo_name: "qadence2_core" nav: - Overview: index.md - Sample page: getting_started_example.md + - Qadence transition: transition.md theme: name: material diff --git a/qadence2/extensions/legacy/__init__.py b/qadence2/extensions/legacy/__init__.py index 09f1801..1740910 100644 --- a/qadence2/extensions/legacy/__init__.py +++ b/qadence2/extensions/legacy/__init__.py @@ -1,10 +1,10 @@ from __future__ import annotations -from qadence2_expressions.operators import CZ, X, Y, Z +from qadence2_expressions.operators import CZ, H, X, Y, Z from .model import QuantumModel -from .operators import CNOT, RX, RY, RZ, N -from .utils import add, chain, kron, mul, pow +from .operators import CNOT, CPHASE, PHASE, RX, RY, RZ, N, T +from .utils import PI, add, chain, kron, mul, pow __all__ = [ "QuantumModel", @@ -22,4 +22,9 @@ "Z", "CZ", "N", + "PI", + "H", + "T", + "PHASE", + "CPHASE", ] diff --git a/qadence2/extensions/legacy/operators.py b/qadence2/extensions/legacy/operators.py index 441773c..e568783 100644 --- a/qadence2/extensions/legacy/operators.py +++ b/qadence2/extensions/legacy/operators.py @@ -2,13 +2,19 @@ import qadence2_expressions.operators as ops from qadence2_expressions import variable +from qadence2_expressions.core.constructors import ( + parametric_operator, + promote, + unitary_hermitian_operator, +) from qadence2_expressions.core.expression import Expression +from qadence2_expressions.operators import _join_rotation # The N = (1/2)(I-Z) operator N = ops.Z1 -def CNOT(target: int, control: int) -> Expression: +def CNOT(control: int, target: int) -> Expression: return ops.NOT(target=(target,), control=(control,)) @@ -29,3 +35,19 @@ def _get_variable(expr: Expression | str | float) -> Expression: return variable(expr) if isinstance(expr, (Expression, float)): return expr + + +def T(target: int) -> Expression: + return unitary_hermitian_operator("T")(target=(target,)) + + +def PHASE(target: int, parameters: Expression | str | float) -> Expression: + return parametric_operator("PHASE", promote(_get_variable(parameters)), join=_join_rotation)( + target=(target,) + ) + + +def CPHASE(control: int, target: int, parameters: Expression | str | float) -> Expression: + return parametric_operator("CPHASE", promote(_get_variable(parameters)), join=_join_rotation)( + target=(target,), control=(control,) + ) diff --git a/qadence2/extensions/legacy/utils.py b/qadence2/extensions/legacy/utils.py index 461a86f..0a3b542 100644 --- a/qadence2/extensions/legacy/utils.py +++ b/qadence2/extensions/legacy/utils.py @@ -1,10 +1,13 @@ from __future__ import annotations +import math from enum import Enum, auto from typing import Generator from qadence2_expressions.core.expression import Expression +PI = math.pi + class ParadigmStrategy(Enum): DIGITAL = auto() @@ -14,21 +17,29 @@ class ParadigmStrategy(Enum): RYDBERG = auto() -def add(expr: Generator | list[Expression]) -> Expression: - return Expression.add(*tuple(expr)) +def add(*expr: Generator | list[Expression]) -> Expression: + if len(expr) > 0 and isinstance(expr[0], Generator): + return Expression.add(*tuple(*expr)) + return Expression.add(*expr) -def mul(expr: Generator | list[Expression]) -> Expression: - return Expression.mul(*tuple(expr)) +def mul(*expr: Generator | list[Expression]) -> Expression: + if len(expr) > 0 and isinstance(expr[0], Generator): + return Expression.mul(*tuple(*expr)) + return Expression.mul(*expr) -def chain(expr: Generator | list[Expression]) -> Expression: - return mul(expr) +def chain(*expr: Generator | list[Expression]) -> Expression: + return mul(*expr) -def kron(expr: Generator | list[Expression]) -> Expression: - return Expression.kron(*tuple(expr)) +def kron(*expr: Generator | list[Expression]) -> Expression: + if len(expr) > 0 and isinstance(expr[0], Generator): + return Expression.kron(*tuple(*expr)) + return Expression.kron(*expr) -def pow(expr: Generator | list[Expression]) -> Expression: - return Expression.pow(*tuple(expr)) +def pow(*expr: Generator | list[Expression]) -> Expression: + if len(expr) > 0 and isinstance(expr[0], Generator): + return Expression.pow(*tuple(*expr)) + return Expression.pow(*expr)