Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] Code prototypes #24

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0d11763
add observable properly on expectation method
Doomsk Oct 22, 2024
b4498ad
add observable handling
Doomsk Oct 22, 2024
198c558
fix observable on expectation
Doomsk Oct 24, 2024
364aa5d
add observable properly on expectation method
Doomsk Oct 22, 2024
3b186bc
add observable handling
Doomsk Oct 22, 2024
c114041
fix observable on expectation
Doomsk Oct 24, 2024
8026e9a
Merge remote-tracking branch 'origin/em/hotfix/observable_on_expectat…
Doomsk Oct 24, 2024
3fb1d3f
fix mypy on getting observable for pyqtorch
Doomsk Oct 24, 2024
80caf9e
fix mypy on getting observable for pyqtorch
Doomsk Oct 24, 2024
ce0bd97
add observable properly on expectation method
Doomsk Oct 22, 2024
02482fe
add observable handling
Doomsk Oct 22, 2024
ab8b144
fix observable on expectation
Doomsk Oct 24, 2024
7b438ed
fix mypy on getting observable for pyqtorch
Doomsk Oct 24, 2024
cf15e8f
fix mypy on getting observable for pyqtorch
Doomsk Oct 24, 2024
2b5f0f9
Merge remote-tracking branch 'origin/em/hotfix/observable_on_expectat…
Doomsk Oct 24, 2024
1318da6
fix problem with variable params on pyq
Doomsk Oct 29, 2024
62cb5fc
fix problem with variable params on pyq
Doomsk Oct 29, 2024
1c95065
fixed changes
Doomsk Oct 29, 2024
31ed823
fix dtype for pyqtorch\nadd __call__ method to pyqtorch interface
Doomsk Oct 29, 2024
19c154b
fix some issues on interface
Doomsk Nov 1, 2024
42c5957
to add changes
Doomsk Nov 4, 2024
4a7f9d5
fix some issues on interface
Doomsk Nov 4, 2024
53dbc9d
add observables handling for fresnel1
Doomsk Nov 14, 2024
e9aef31
add observables handling for fresnel1
Doomsk Nov 14, 2024
bd4d126
fix minor issues
Doomsk Nov 14, 2024
9f8fb2d
fix fresnel1 observables
Doomsk Nov 18, 2024
6abdf60
add docstring
Doomsk Nov 19, 2024
aaa7191
add more tests
Doomsk Nov 21, 2024
f0681e1
add more tests
Doomsk Nov 21, 2024
2544f90
add more tests
Doomsk Nov 21, 2024
d1b3793
add pyq tests
Doomsk Nov 21, 2024
cd814b8
lint test code
Doomsk Nov 25, 2024
d4a44d7
add docstrings
Doomsk Nov 25, 2024
72c8762
lint fixes
Doomsk Nov 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ repos:
rev: "v0.6.2"
hooks:
- id: ruff
args:
- --fix
args: [--fix, --show-fixes, --show-files]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.0
Expand All @@ -29,3 +28,6 @@ repos:
rev: v0.7.3
hooks:
- id: pydocstringformatter
args:
- --no-final-period
- --no-split-summary-body
14 changes: 6 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,15 @@ dependencies = [
"isort",
"ruff",
"pydocstringformatter",
"qadence2-expressions"
]

[tool.hatch.envs.default.scripts]
test = "pytest -n auto --cov-config=pyproject.toml --ignore=./tests/test_examples.py {args}"
test = "pytest -n auto --cov-config=pyproject.toml --cov=qadence2_platforms {args}"

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = """-vvv --cov-report=term-missing --cov-config=pyproject.toml --cov=template_python --cov=tests"""
addopts = """-vvv --cov-config=pyproject.toml --cov=qadence2_platforms"""
xfail_strict = true
filterwarnings = [
"ignore:Call to deprecated create function FieldDescriptor",
Expand Down Expand Up @@ -113,12 +114,9 @@ packages = ["qadence2_platforms"]
[tool.coverage.run]
branch = true
parallel = true
# uncomment to omit any file from the
# coverage. Regexps can be used
# to select all files from a folder
#omit = [
# "template_python/to_omit.py",
#]
omit = [
"*/utils/templates/*"
]

[tool.coverage.report]
exclude_lines = [
Expand Down
4 changes: 4 additions & 0 deletions qadence2_platforms/__init__.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this import + default type setting be pushed down into the pyqtorch subfolder? If you're using a different backend importing torch is not necessary.

Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from __future__ import annotations

from torch import float64, set_default_dtype

from .abstracts import AbstractInterface

set_default_dtype(float64)

PACKAGE_NAME = __name__
BACKEND_FOLDER_NAME = "backends"
USER_BACKENDS_FOLDER_NAME = "user_backends"
Expand Down
18 changes: 11 additions & 7 deletions qadence2_platforms/abstracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from abc import ABC, abstractmethod
from enum import Enum, auto
from typing import Any, Callable, Generic, Optional, TypeVar
from typing import Any, Generic, Iterable, TypeVar

ArrayType = TypeVar("ArrayType")
SequenceType = TypeVar("SequenceType")
Expand Down Expand Up @@ -78,6 +78,16 @@ def sequence(self) -> SequenceType:
"""
pass

@abstractmethod
def parameters(self) -> Iterable[Any]:
"""
Get the parameters from the backend as an iterable.

Returns:
Parameters as an iterable.
"""
pass

@abstractmethod
def set_parameters(self, params: dict[str, ParameterType]) -> None:
"""
Expand All @@ -90,9 +100,7 @@ def set_parameters(self, params: dict[str, ParameterType]) -> None:
@abstractmethod
def run(
self,
*,
values: dict[str, ArrayType] | None = None,
callback: Optional[Callable] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring should reflect this change.

**kwargs: Any,
) -> RunResultType:
"""
Expand All @@ -111,10 +119,8 @@ def run(
@abstractmethod
def sample(
self,
*,
values: dict[str, ArrayType] | None = None,
shots: int | None = None,
callback: Optional[Callable] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring should reflect this change

**kwargs: Any,
) -> SampleResultType:
"""
Expand All @@ -134,10 +140,8 @@ def sample(
@abstractmethod
def expectation(
self,
*,
values: dict[str, ArrayType] | None = None,
observable: Any | None = None,
callback: Optional[Callable] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring to be updated.

**kwargs: Any,
) -> ExpectationResultType:
"""
Expand Down
199 changes: 194 additions & 5 deletions qadence2_platforms/backends/fresnel1/functions.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from __future__ import annotations

from enum import Enum, auto
from typing import Any
from functools import reduce
from typing import Any, Iterable, cast

import numpy as np
import qutip
from pulser.parametrized.variable import VariableItem
from pulser.sequence import Sequence
from pulser.waveforms import ConstantWaveform

from qadence2_platforms.backends.utils import InputType, Support

DEFAULT_AMPLITUDE = 4 * np.pi
DEFAULT_DETUNING = 10 * np.pi

Expand Down Expand Up @@ -86,15 +90,17 @@ def x(sequence: Sequence, **_: Any) -> None:

def h(
sequence: Sequence,
duration: VariableItem | float = 1000.0,
duration: VariableItem | float = np.pi,
support_list: str = "global",
**_: Any,
) -> None:
support_list = "GLOBAL"
amplitude = sequence.device.channels["rydberg_global"].max_amp or DEFAULT_AMPLITUDE
duration *= 1000 * 2 * np.pi / amplitude
duration = int(duration) if duration > 16 else 16
detuning = np.pi

sequence.enable_eom_mode("global", amp_on=amplitude, correct_phase_drift=True)
sequence.enable_eom_mode(
"global", amp_on=amplitude, correct_phase_drift=True, detuning_on=detuning
)
sequence.add_eom_pulse(
"global", duration=int(duration), phase=np.pi / 2, post_phase_shift=np.pi
)
Expand Down Expand Up @@ -186,3 +192,186 @@ def local_pulse_core(
"dmm_0",
"no-delay" if concurrent else "min-delay",
)


def parse_native_observables(
num_qubits: int, observable: list[InputType] | InputType
) -> list[qutip.Qobj]:
return QuTiPObservablesParser.build(num_qubits, observable)


class QuTiPObservablesParser:
"""
Convert InputType object to Qutip native quantum objects for simulation on QuTiP.

It is intended to be used on expectation method of the Fresnel1 interface class.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used on the expectation method

InputType can be qadence2-expressions expression or any other module with the same
methods.
"""

operators_mapping = {
"I": qutip.qeye(2),
"Z": qutip.sigmaz(),
}

@classmethod
def _compl_tensor_op(cls, num_qubits: int, expr: InputType) -> qutip.Qobj:
"""
Use it for pure kron operations or for single input operator that needs to match
a bigger Hilbert space, e.g. `expr = Z(0)` but the number of qubits is 3.

Args:
num_qubits (int): the number of qubits to create the qutip object to
expr (InputType): the input expression. Any qadence2-expressions expression
compatible object, with the same methods

Returns:
A QuTiP object with the Hilbert space compatible with `num_qubits`
"""

op: qutip.Qobj
arg: InputType

if expr.subspace:
native_ops: list[qutip.Qobj] = []
support_set: set = expr.subspace.subspace

for k in range(num_qubits):
if k in support_set:
sub_num_qubits: int = cast(Support, expr.args[1]).max_index
arg = cast(InputType, expr.args[0])
op = cls._get_op(sub_num_qubits, arg)

else:
op = cls.operators_mapping["I"]

native_ops.append(op)

return qutip.tensor(*native_ops)

arg = cast(InputType, expr.args[0])
op = cls._get_op(num_qubits, arg)
return qutip.tensor(*([op] * num_qubits))

@classmethod
def _arith_tensor_op(cls, num_qubits: int, expr: InputType) -> qutip.Qobj:
"""
Use it for the arithmetic operations addition and multiplication that need to
have an extended Hilbert space compatible with `num_qubits`.

Args:
num_qubits (int): the number of qubits to create the qutip object to
expr (InputType): the input expression. Any qadence2-expressions expression
compatible object, with the same methods

Returns:
A QuTiP object with the Hilbert space compatible with `num_qubits`
"""

subspace: set = cast(Support, expr.subspace).subspace
super_space: set = set(range(num_qubits))

if super_space.issuperset(subspace):
native_ops: list[qutip.Qobj] = []
arg_subspace: set = cast(Support, expr.args[1]).subspace

for k in range(num_qubits):
if k in arg_subspace:
sub_num_qubits: int = cast(int, expr.args[1])
arg: InputType = cast(InputType, expr.args[0])
op: qutip.Qobj = cls._get_op(sub_num_qubits, arg)
native_ops.append(op)
else:
native_ops.append(cls.operators_mapping["I"])

return qutip.tensor(*native_ops)

raise ValueError(
f"subspace of the object ({subspace}) is bigger than the "
f"sequence space ({super_space})"
)

@classmethod
def _get_op(cls, num_qubits: int, op: InputType) -> qutip.Qobj | None:
"""
Convert an expression into a native QuTiP object. A simple symbol,
a quantum operator, and an operation (addition, multiplication or
kron tensor) are valid objects.

Args:
num_qubits (int): the number of qubits to create the qutip object to
op (InputType): the input expression. Any qadence2-expressions expression
compatible object, with the same methods

Returns:
A QuTiP object with the Hilbert space compatible with `num_qubits`
"""

if op.is_symbol is True:
symbol: str = cast(str, op.args[0])
return cls.operators_mapping[symbol]

op_arg: qutip.Qobj

if op.is_quantum_operator is True:
sub_num_qubits: int = cast(Support, op.args[1]).max_index + 1

if sub_num_qubits < num_qubits:
op_arg = cls._compl_tensor_op(num_qubits, op)

else:
arg: InputType = cast(InputType, op.args[0])
op_arg = cls._get_op(num_qubits, arg)

return op_arg

ops: list[qutip.Qobj] = []
args: Iterable = cast(Iterable, op.args)

if op.is_addition is True:

for arg in args:
ops.append(cls._arith_tensor_op(num_qubits, arg))

return reduce(lambda a, b: a + b, ops)

if op.is_multiplication is True:

for arg in args:
ops.append(cls._arith_tensor_op(num_qubits, arg))

return reduce(lambda a, b: a * b, ops)

if op.is_kronecker_product is True:
return cls._compl_tensor_op(num_qubits, op)

raise NotImplementedError(
f"could not retrieve the expression {op} ({type(op)}) from the observables"
)

@classmethod
def _iterate_over_obs(cls, n_qubits: int, op: Iterable | InputType) -> list[qutip.Qobj]:
if isinstance(op, Iterable):
return [cls._get_op(n_qubits, arg) for arg in op]

args: Iterable = cast(Iterable, op.args)
return [cls._get_op(n_qubits, cast(InputType, arg)) for arg in args]

@classmethod
def build(cls, num_qubits: int, observables: list[InputType] | InputType) -> list[qutip.Qobj]:
"""
Parses an input expression or list of expressions into a native QuTiP object.

Args:
num_qubits (int): the number of qubits to create the qutip object to
observables (InputType): the input expression. Any qadence2-expressions
expression compatible object, with the same methods

Returns:
A QuTiP object with the Hilbert space compatible with `num_qubits`

Returns:
"""
if not isinstance(observables, list):
return [cls._get_op(num_qubits, observables)]
return cls._iterate_over_obs(num_qubits, observables)
Loading