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

Feat db jobs and results #118

Draft
wants to merge 27 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
78dd518
feat: DB first draft
JulienCalistoTD Nov 26, 2024
9b997eb
feat: DB v2, export, chose name , check for same job
JulienCalistoTD Nov 27, 2024
55f0436
feat: export with fetch and start notebook
JulienCalistoTD Nov 27, 2024
e2d618c
feat: db v3, remove obj, and allow list
JulienCalistoTD Nov 27, 2024
4dcce28
doc: add doc and test doc
JulienCalistoTD Nov 29, 2024
c0a7178
test-fix: add test for db, fix repr and add eq of Result, BasisMeasur…
JulienCalistoTD Dec 4, 2024
77cf4c7
fix: doc setup_db
JulienCalistoTD Dec 4, 2024
60407bf
feat: .mpqp is not a folder, allowing us to store the db there
Henri-ColibrITD Dec 5, 2024
def8396
fix: replaces path concatenation operator
Henri-ColibrITD Dec 5, 2024
6292d22
feat: automated conversion from old env location
Henri-ColibrITD Dec 5, 2024
d37304f
fix: doc now passes
Henri-ColibrITD Dec 5, 2024
2f4dec1
chore: renaming for more user friendly names
Henri-ColibrITD Dec 5, 2024
4e4a083
fix: env tests partially rolled back
Henri-ColibrITD Dec 5, 2024
b2f88da
fix: circular import
Henri-ColibrITD Dec 5, 2024
3edf6c0
fix: decorator typos
Henri-ColibrITD Dec 5, 2024
6f7820f
fix: name conflict mitigation
Henri-ColibrITD Dec 5, 2024
a3dfc9d
fix: db path is always absolute now + dbrunner for doctest fixed
Henri-ColibrITD Dec 6, 2024
1c8e6c2
chore: basis repr rollback
Henri-ColibrITD Dec 9, 2024
21608ec
chore: review in progress
Henri-ColibrITD Dec 9, 2024
01a5597
feat: added the ability to save and load from the Result class itself
Henri-ColibrITD Dec 9, 2024
a3431e1
doc: queries doc improved
Henri-ColibrITD Dec 9, 2024
ff141c5
doc: improved load doc
Henri-ColibrITD Dec 9, 2024
066fabd
doc: setup, save and removing improvements
Henri-ColibrITD Dec 9, 2024
a2ad91c
fix: to_dict, ellipsis on doctest
JulienCalistoTD Dec 18, 2024
192bae0
doc: documentation, and chore
JulienCalistoTD Dec 18, 2024
1db6b08
Merge branch 'dev' into feat-DB-jobs-results
JulienCalistoTD Feb 18, 2025
c0f4390
chore: Files formated
github-actions[bot] Feb 18, 2025
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
939 changes: 939 additions & 0 deletions examples/notebooks/7_Data_Base.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion mpqp/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
AZUREDevice,
)
from .execution.simulated_devices import IBMSimulatedDevice
from .execution.remote_handler import get_all_job_ids
from .execution.remote_handler import get_all_remote_job_ids
from .execution.vqa import Optimizer, minimize
from .gates import (
CNOT,
Expand Down
53 changes: 39 additions & 14 deletions mpqp/core/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@

from copy import deepcopy
from numbers import Complex
from pickle import dumps
from typing import TYPE_CHECKING, Iterable, Optional, Sequence, Type
from typing import TYPE_CHECKING, Optional, Sequence, Type
from warnings import warn

import numpy as np
Expand Down Expand Up @@ -159,13 +158,18 @@ def __init__(
connections: set[int] = set.union(
*(instruction.connections() for instruction in data)
)
self._nb_qubits = max(connections) + 1
self._nb_qubits = (
max(connections) + 1 if len(connections) != 0 else 0
)
else:
self._nb_qubits = nb_qubits
self.add(deepcopy(data))

def __eq__(self, value: object) -> bool:
return dumps(self) == dumps(value)
Copy link
Contributor

Choose a reason for hiding this comment

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

can you provide me with an example where this didn't work (for my culture)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The problem came from somewhere else but I had kept the change. I discard the change and all the tests passed.

Copy link
Contributor

Choose a reason for hiding this comment

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

the change was not discarded though ?

if not isinstance(value, QCircuit):
return False

return self.to_dict() == value.to_dict()

def add(self, components: OneOrMany[Instruction | NoiseModel]):
"""Adds a ``component`` or a list of ``component`` at the end of the
Expand Down Expand Up @@ -205,7 +209,7 @@ def add(self, components: OneOrMany[Instruction | NoiseModel]):

"""

if isinstance(components, Iterable):
if not isinstance(components, (Instruction, NoiseModel)):
for comp in components:
self.add(comp)
return
Expand All @@ -231,7 +235,11 @@ def add(self, components: OneOrMany[Instruction | NoiseModel]):
components.c_targets = [
self.nb_cbits + i for i in range(len(components.targets))
]
self.nb_cbits = max(self.nb_cbits, max(components.c_targets) + 1)
self.nb_cbits = (
max(self.nb_cbits, max(components.c_targets) + 1)
if len(components.c_targets) != 0
else 0
)

if isinstance(components, NoiseModel):
self.noises.append(components)
Expand Down Expand Up @@ -318,12 +326,12 @@ def _update_targets_components(self, component: Instruction | NoiseModel):

if self.nb_cbits is None:
self.nb_cbits = 0
unique_cbits = set()
unique_cbits: set[int] = set()
for instruction in self.instructions:
if instruction != component and isinstance(instruction, BasisMeasure):
if instruction.c_targets:
unique_cbits.update(instruction.c_targets)
c_targets = []
c_targets: list[int] = []
i = 0
for _ in range(len(component.targets)):
while i in unique_cbits:
Expand Down Expand Up @@ -430,11 +438,26 @@ def append(self, other: QCircuit, qubits_offset: int = 0) -> None:
if isinstance(inst, ControlledGate):
inst.controls = [qubit + qubits_offset for qubit in inst.controls]
if isinstance(inst, BasisMeasure):
if not inst.user_set_c_targets:
if not inst._user_set_c_targets: # pyright: ignore[reportPrivateUsage]
inst.c_targets = None

self.add(inst)

def to_dict(self) -> dict[str, int | str | list[str] | float | None]:
"""
Serialize the quantum circuit to a dictionary.
Returns:
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think we can include a small example for this method in the docstring?

dict: A dictionary representation of the circuit.
"""

return {
attr_name: getattr(self, attr_name)
for attr_name in dir(self)
if attr_name not in {'_nb_qubits', 'gates', 'measurements', 'breakpoints'}
and not attr_name.startswith("__")
and not callable(getattr(self, attr_name))
}

def __iadd__(self, other: QCircuit):
self.append(other)
return self
Expand Down Expand Up @@ -1280,12 +1303,14 @@ def __str__(self) -> str:

def __repr__(self) -> str:
instructions_repr = ", ".join(repr(instr) for instr in self.instructions)

if self.noises:
noise_repr = ", ".join(map(repr, self.noises))
return f'QCircuit([{instructions_repr}, {noise_repr}], nb_qubits={self.nb_qubits}, nb_cbits={self.nb_cbits}, label="{self.label}")'
label = f', label="{self.label}"' if self.label is not None else ""
nb_cbits = f", nb_cbits={self.nb_cbits}" if self.nb_cbits is not None else ""
if instructions_repr == "":
noise = ", " + ", ".join(map(repr, self.noises)) if self.noises else ""
else:
return f'QCircuit([{instructions_repr}], nb_qubits={self.nb_qubits}, nb_cbits={self.nb_cbits}, label="{self.label}")'
noise = ", ".join(map(repr, self.noises)) if self.noises else ""

return f'QCircuit([{instructions_repr}{noise}], nb_qubits={self.nb_qubits}{nb_cbits}{label})'

def variables(self) -> set[Basic]:
"""Returns all the symbolic parameters involved in this circuit.
Expand Down
13 changes: 12 additions & 1 deletion mpqp/core/instruction/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def __str__(self) -> str:
from mpqp.core.circuit import QCircuit

connection = self.connections()
circuit_size = max(connection) + 1 if connection else 1
circuit_size = max(connection) + 1 if len(connection) == 0 else 1
Copy link
Contributor

Choose a reason for hiding this comment

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

You mean if len(connection) != 0 here ? Otherwise it doesn't make sense

circuit = QCircuit(circuit_size)
circuit.add(self)
return str(circuit)
Expand All @@ -115,6 +115,17 @@ def __repr__(self) -> str:
controls = str(self.controls) + "," if isinstance(self, ControlledGate) else ""
return f"{type(self).__name__}({controls}{self.targets})"

def to_dict(self):
return {
attr_name: getattr(self, attr_name)
for attr_name in dir(self)
if (
not attr_name.startswith("_abc_")
and not attr_name.startswith("__")
and not callable(getattr(self, attr_name))
)
}

def connections(self) -> set[int]:
"""Returns the indices of the qubits connected to the instruction.

Expand Down
11 changes: 11 additions & 0 deletions mpqp/core/instruction/measurement/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ def to_computational(self):
]
)

def __eq__(self, other: object) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you take into account here that we can call __eq__ to compare a Basis and VariableSizeBasis ?

if not isinstance(other, Basis):
return False

return (
self.nb_qubits == other.nb_qubits
and np.array_equal(self.basis_vectors, other.basis_vectors)
and self.symbols == other.symbols
and self.basis_vectors_labels == other.basis_vectors_labels
)


@typechecked
class VariableSizeBasis(Basis, ABC):
Expand Down
52 changes: 33 additions & 19 deletions mpqp/core/instruction/measurement/basis_measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ def __init__(
if not isinstance(basis, VariableSizeBasis):
self._dynamic = False
self.targets = list(range(basis.nb_qubits))
elif targets is not None:
basis.set_size(max(targets) + 1)

self.user_set_c_targets = c_targets is not None
self._user_set_c_targets = c_targets is not None
self.c_targets = c_targets
"""See parameter description."""
self.basis = basis
Expand Down Expand Up @@ -112,23 +114,35 @@ def pre_measure(self):
return self.basis.to_computational()

def __repr__(self) -> str:
targets = (
f"{self.targets}" if (not self._dynamic and len(self.targets)) != 0 else ""
)
options = ""
components = []
if not self._dynamic and len(self.targets) != 0:
components.append(str(self.targets))
if (
not self._dynamic
and self.c_targets is not None
and len(self.c_targets) != 0
):
components.append(f"c_targets={self.c_targets}")
if self.shots != 1024:
options += f"shots={self.shots}"
if not isinstance(self.basis, ComputationalBasis):
options += (
f", basis={self.basis}"
if len(options) != 0 or len(targets) != 0
else f"basis={self.basis}"
)
components.append(f"shots={self.shots}")
if self.label is not None:
options += (
f", label={self.label}"
if len(options) != 0 or len(targets) != 0
else f"label={self.label}"
)
separator = ", " if len(options) != 0 and len(targets) != 0 else ""
return f"BasisMeasure({targets}{separator}{options})"
components.append(f"label={self.label}")
if not self._dynamic:
components.append(f"basis={self.basis}")

return f"BasisMeasure({', '.join(components)})"

def __eq__(self, other: object) -> bool:
if not isinstance(other, BasisMeasure):
return False
return self.to_dict() == other.to_dict()

def to_dict(self):
return {
"targets": self.targets,
"c_targets": self.c_targets,
"shots": self.shots,
"basis": self.basis,
"label": self.label,
"_dynamic": self._dynamic,
}
18 changes: 18 additions & 0 deletions mpqp/core/instruction/measurement/expectation_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ def to_other_language(
else:
raise ValueError(f"Unsupported language: {language}")

def __eq__(self, other: object) -> bool:
from mpqp.tools.maths import matrix_eq

if not isinstance(other, Observable):
return False

return (
self.nb_qubits == other.nb_qubits
and matrix_eq(self.matrix, other.matrix)
and self.pauli_string == other.pauli_string
)


@typechecked
class ExpectationMeasure(Measure):
Expand Down Expand Up @@ -282,3 +294,9 @@ def to_other_language(
"appropriate data, and the data in later used in the needed "
"locations."
)

def __eq__(self, other: object) -> bool:
if not isinstance(other, ExpectationMeasure):
return False

return self.to_dict() == other.to_dict()
1 change: 1 addition & 0 deletions mpqp/core/instruction/measurement/measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ def __init__(
"""See parameter description."""
self.label = label
"""See parameter description."""
self._dynamic = False
if len(targets) == 0:
self._dynamic = True
51 changes: 29 additions & 22 deletions mpqp/execution/connection/env_manager.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
"""This module takes care of saving and loading the configuration of supported
providers."""
providers in our configuration file, located at ``~/.mpqp/.env``."""

import os
from getpass import getpass
from pathlib import Path
from typing import Callable

from dotenv import load_dotenv, set_key
from termcolor import colored
from typeguard import typechecked

MPQP_CONFIG_PATH = os.path.expanduser("~") + "/.mpqp"
MPQP_ENV = Path("~/.mpqp/.env").expanduser()


def _create_config_if_needed():
"""If there is not already a ``.mpqp`` file we create it."""
if not os.path.exists(MPQP_CONFIG_PATH):
open(MPQP_CONFIG_PATH, "a").close()
"""If configuration file does not exist we create it."""
# convert from old format to new one
if MPQP_ENV.parent.is_file():
os.rename(MPQP_ENV.parent, MPQP_ENV.parent.with_suffix(".tmp"))
MPQP_ENV.parent.mkdir(parents=True, exist_ok=True)
os.rename(MPQP_ENV.parent.with_suffix(".tmp"), MPQP_ENV)
return

if not MPQP_ENV.exists():
MPQP_ENV.parent.mkdir(parents=True, exist_ok=True)
MPQP_ENV.open("a").close()


def get_existing_config_str() -> str:
"""Gets the content of the ``.mpqp`` config file.
"""Gets the content of the configuration file.

Returns:
The string with .mpqp file content.
The content of the configuration file.

Example:
>>> save_env_variable('QLM_USER', 'hjaffali')
Expand All @@ -40,37 +49,36 @@ def get_existing_config_str() -> str:
BRAKET_CONFIGURED='True'

"""
if not os.path.exists(MPQP_CONFIG_PATH):
if not MPQP_ENV.exists():
return ""
with open(MPQP_CONFIG_PATH, "r") as mpqp:
file_str = mpqp.read()
with MPQP_ENV.open("r") as env:
file_str = env.read()
return file_str


def load_env_variables() -> bool:
"""Loads the variables stored in the ``.mpqp`` file.
"""Loads the variables stored in the configuration file.

Returns:
``True`` if the variables are loaded correctly.

Example:
>>> os.getenv("IBM_CONFIGURED")
>>> open(os.path.expanduser("~") + "/.mpqp", "w").write("IBM_CONFIGURED='True'\\n")
>>> os.getenv("IBM_CONFIGURED") # doctest: +SKIP
>>> open(os.path.expanduser("~") + "/.mpqp/.env", "w").write("IBM_CONFIGURED='True'\\n")
22
>>> os.getenv("IBM_CONFIGURED")
>>> os.getenv("IBM_CONFIGURED") # doctest: +SKIP
>>> load_env_variables()
True
>>> os.getenv("IBM_CONFIGURED")
'True'

"""
load_dotenv(MPQP_CONFIG_PATH, override=True)
return True
return load_dotenv(MPQP_ENV, override=True)


@typechecked
def get_env_variable(key: str) -> str:
"""Loads the ``.mpqp`` env file and returns the value associated with the key
"""Loads the configuration file and returns the value associated with the key
in parameter. If the variable does not exist, an empty string is returned.

Args:
Expand All @@ -87,18 +95,17 @@ def get_env_variable(key: str) -> str:
"""
_create_config_if_needed()
load_env_variables()
val = os.getenv(key, "")

return val
return os.getenv(key, "")


@typechecked
def save_env_variable(key: str, value: str) -> bool:
"""Adds or updates the ``key`` environment variable in ``.mpqp`` file.
"""Adds or updates the ``key`` environment variable in the configuration file.

Args:
key: Name of the environment variable.
value: Value to be saved.
value: Value of the environment variable.

Returns:
``True`` if the save was successful.
Expand All @@ -115,7 +122,7 @@ def save_env_variable(key: str, value: str) -> bool:
_create_config_if_needed()

try:
a, _, _ = set_key(MPQP_CONFIG_PATH, key, value)
a, _, _ = set_key(MPQP_ENV, key, value)
if a is None:
raise SystemError(
"Something went wrong when trying to modify the MPQP "
Expand Down
Loading
Loading