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] Add generic logic structure and backends functionalities #11

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ repos:
- id: black

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.4.2"
rev: "v0.4.10"
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please check if the template-python-project is up-to-date with pre-commits ?

hooks:
- id: ruff
args:
- --fix

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
rev: v1.10.1
hooks:
- id: mypy
exclude: examples|docs
8 changes: 0 additions & 8 deletions docs/sample_page.md
Original file line number Diff line number Diff line change
@@ -1,8 +0,0 @@
This is just a sample notebook to showcase the rendering of Jupyter notebooks in the documentation.

```python exec="on" source="material-block" session="main"
from qadence2_platforms.main import main

msg = main()
print(msg)
```
18 changes: 10 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ classifiers = [

# always specify a version for each package
# to maintain consistency
dependencies = ["pyqtorch",
dependencies = [
Copy link
Contributor

Choose a reason for hiding this comment

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

Do these dependencies have to be installed by default ? Or could they be added as extra ?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll change it once refactoring parts of the Embedding

"pulser",
Copy link
Member

Choose a reason for hiding this comment

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

I think pulser-core, pulser-simulation and later with torch, will be sufficient and we can avoid the pulser-pasqal module.

"pyqtorch",
]

[tool.hatch.metadata]
Expand Down Expand Up @@ -109,25 +111,25 @@ exclude_lines = [
]

[tool.ruff]
select = ["E", "F", "I", "Q"]
extend-ignore = ["F841"]
lint.select = ["E", "F", "I", "Q"]
lint.extend-ignore = ["F841"]
line-length = 100

[tool.ruff.isort]
[tool.ruff.lint.isort]
required-imports = ["from __future__ import annotations"]

[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]

[tool.ruff.mccabe]
[tool.ruff.lint.mccabe]
max-complexity = 15

[tool.ruff.flake8-quotes]
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"

[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_return_any = false
Copy link
Contributor

Choose a reason for hiding this comment

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

Why ?

Copy link
Member Author

Choose a reason for hiding this comment

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

it doesn't like when certain types of returns happen, namely if you do a function factory

warn_unused_configs = true
disallow_untyped_defs = true
no_implicit_optional = false
Expand Down
4 changes: 2 additions & 2 deletions qadence2_platforms/backend/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ def compile(model: Model, backend_name: str) -> Api: # type: ignore[return]
try:
interface = import_module(f"qadence2_platforms.backend.{backend_name}")
native_backend = import_module(backend_name)
register_interface = interface.RegisterInterface(model)
embedding = interface.Embedding(model)
register_interface = interface.RegisterInterface(model.register)
embedding = interface.EmbeddingModule(model)
native_circ = interface.Compiler().compile(model)
return Api(register_interface, embedding, native_circ, native_backend)
except Exception as e:
Expand Down
44 changes: 44 additions & 0 deletions qadence2_platforms/backend/bytecode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Callable, Generic, Iterator, Optional

from qadence2_platforms.types import (
BytecodeInstructType,
DeviceType,
EmbeddingType,
InstructionsObjectType,
)


class BytecodeApi(
Iterator, Generic[BytecodeInstructType, EmbeddingType, InstructionsObjectType], ABC
):
"""
An iterator class to be used by the runtime function. It contains which backend
to invoke, sequence instance and an iterable of instructions' partial functions
to be filled with user input (if necessary) or called directly, as well as the
device, if applicable.
"""

def __init__(
self,
backend: str,
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't that be a backend name ?

sequence: InstructionsObjectType,
Copy link
Contributor

Choose a reason for hiding this comment

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

It is highly confusing that sequence is of instruction type (sort of) and instructions is of bytecode type.

instructions: BytecodeInstructType,
variables: EmbeddingType,
device: DeviceType | None = None,
):
self.backend: str = backend
self.device: Optional[DeviceType] = device
self.sequence: InstructionsObjectType = sequence
self.variables: EmbeddingType = variables
self.instructions: BytecodeInstructType = instructions

@abstractmethod
def __call__(self, *args: Any, **kwargs: Any) -> Callable:
raise NotImplementedError()

@abstractmethod
def __next__(self) -> Callable:
raise NotImplementedError()
85 changes: 85 additions & 0 deletions qadence2_platforms/backend/dialect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from importlib import import_module
from types import ModuleType
from typing import Callable, Generic, Optional

from qadence2_platforms.backend.bytecode import BytecodeApi
from qadence2_platforms.backend.interface import RuntimeInterfaceApi
from qadence2_platforms.backend.sequence import SequenceApi
from qadence2_platforms.backend.utils import (
get_backend_module,
get_backend_register_fn,
get_device_instance,
get_embedding_instance,
get_native_seq_instance,
)
from qadence2_platforms.qadence_ir import Model
from qadence2_platforms.types import DeviceType, EmbeddingType, RegisterType


class DialectApi(ABC, Generic[RegisterType, DeviceType, EmbeddingType]):
"""
<Add the `Dialect` description here>

It is an intermediate resolver class that must be called by the compile function.
By invoking its `compile` method, it generates a `Bytecode` iterator instance, which
is necessary for the runtime functionalities, such as `sample`, `expectation`, etc.,
with arguments such as number of shots, feature parameters input, error mitigation
options, result types, and so on.

Here it is assumed that `Model`'s `inputs` attribute (a dictionary) will contain the
feature parameters (data provided by the user), and, subsequently, the inputs and all
the SSA form variables will be located in a single source, now called `embedding`.
"""

def __init__(self, backend: str, model: Model, device: Optional[str] = None):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def __init__(self, backend: str, model: Model, device: Optional[str] = None):
def __init__(self, backend_name: str, model: Model, device: Optional[str] = None):

self.model: Model = model
self.device_name: str = device or ""
Copy link
Contributor

Choose a reason for hiding this comment

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

Make type optional.

Copy link
Member Author

Choose a reason for hiding this comment

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

which type?

Copy link
Contributor

Choose a reason for hiding this comment

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

There is a type mismatch between self.device_name being a strand device being either a str or None.

self.backend_name: str = backend

self.device: DeviceType = get_device_instance(
backend=self.backend_name, device=self.device_name
)

register_fn: Callable = get_backend_register_fn(self.backend_name)
self.register: RegisterType = register_fn(self.model, self.device)

self.interface_backend: ModuleType = get_backend_module(
backend=self.backend_name
)
self.native_backend: ModuleType = import_module(self.backend_name)

embedding_instance: Callable = get_embedding_instance(self.backend_name)
self.embedding: EmbeddingType = embedding_instance(self.model)

native_seq_instance: Callable = get_native_seq_instance(
backend=self.backend_name, device=self.device_name
)
self.native_sequence: SequenceApi = native_seq_instance(
model=self.model, register=self.register, device=self.device
)

@abstractmethod
def compile_bytecode(self) -> BytecodeApi:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def compile_bytecode(self) -> BytecodeApi:
def compile(self, target = TargetType.BYTECODE) -> BytecodeApi:

And similar for the next method

Suggested change
def compile_bytecode(self) -> BytecodeApi:
def compile(self, target = TargetType.RUNTIME) -> BytecodeApi:

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, something along these lines.

"""
It resolves `QuInstruct` into appropriate backend's sequence type, creates the
appropriate backend's `Register` instance, addresses and converts the directives,
sets the appropriate data settings, and generates the `Bytecode` instance.

:return: the `Bytecode` instance.
"""
raise NotImplementedError()

@abstractmethod
def compile(self) -> RuntimeInterfaceApi:
"""
It resolves `Model` into an appropriate backend's runtime object. It must load
the desired backend and device, if available, and use the backend's implementation
to provide the correct interface for the expression to be converted into native
instructions and thus be runnable into the backends specifications.

:return: a `RuntimeInterface` instance.
"""
raise NotImplementedError()
52 changes: 52 additions & 0 deletions qadence2_platforms/backend/embedding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Generic, Optional

from qadence2_platforms import Model
from qadence2_platforms.types import (
DType,
EmbeddingMappingResultType,
EmbeddingType,
ParameterType,
)


class ParameterBufferApi(ABC, Generic[DType, ParameterType]):
"""
A generic parameter class to hold all root parameters passed by the user or
trainable variational parameters.
"""

_dtype: DType
vparams: dict[str, ParameterType]
fparams: dict[str, Optional[ParameterType]]

@property
def dtype(self) -> DType:
return self._dtype

@abstractmethod
def from_model(self, model: Model) -> ParameterBufferApi:
raise NotImplementedError()


class EmbeddingModuleApi(ABC, Generic[EmbeddingType, EmbeddingMappingResultType]):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class EmbeddingModuleApi(ABC, Generic[EmbeddingType, EmbeddingMappingResultType]):
class EmbeddingModule(ABC, Generic[EmbeddingType, EmbeddingMappingResultType]):

"""
A generic module class to hold and handle the parameters and expressions
functions coming from the `Model`. It may contain the list of user input
parameters, as well as the trainable variational parameters and the
evaluated functions from the data types being used, i.e. torch, numpy, etc.
"""

model: Model
param_buffer: ParameterBufferApi
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
param_buffer: ParameterBufferApi
param_buffer: ParameterBuffer

mapped_vars: EmbeddingMappingResultType
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
mapped_vars: EmbeddingMappingResultType
mapped_vars: EmbeddingMapType


@abstractmethod
def __call__(self, *args: Any, **kwargs: Any) -> dict[str, EmbeddingType]:
raise NotImplementedError()
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it necessary to call it ?


@abstractmethod
def name_mapping(self) -> EmbeddingMappingResultType:
raise NotImplementedError()
Empty file.
Empty file.
60 changes: 60 additions & 0 deletions qadence2_platforms/backend/interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Generic

from qadence2_platforms.types import (
EmbeddingType,
ExpectationResultType,
InterfaceCallResultType,
NativeBackendType,
NativeSequenceType,
RegisterType,
RunResultType,
SampleResultType,
)


class RuntimeInterfaceApi(
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class RuntimeInterfaceApi(
class RuntimeInterface(

Interface is redundant.

ABC,
Generic[
RegisterType,
EmbeddingType,
NativeSequenceType,
NativeBackendType,
RunResultType,
SampleResultType,
ExpectationResultType,
InterfaceCallResultType,
],
):
"""
Interface generic class to be used to build runtime classes for backends.
It may run with the qadence-core runtime functions when post-processing,
statistical analysis, etc.
"""

register: RegisterType
embedding: EmbeddingType
engine: NativeBackendType
sequence: NativeSequenceType

@abstractmethod
def __call__(self, *args: Any, **kwargs: Any) -> InterfaceCallResultType:
raise NotImplementedError()

@abstractmethod
def forward(self, *args: Any, **kwargs: Any) -> InterfaceCallResultType:
raise NotImplementedError()

@abstractmethod
def run(self, *args: Any, **kwargs: Any) -> RunResultType:
raise NotImplementedError()

@abstractmethod
def sample(self, *args: Any, **kwargs: Any) -> SampleResultType:
raise NotImplementedError()

@abstractmethod
def expectation(self, *args: Any, **kwargs: Any) -> ExpectationResultType:
raise NotImplementedError()
5 changes: 5 additions & 0 deletions qadence2_platforms/backend/pulser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import annotations

from .dialect import Dialect
from .embedding import EmbeddingModule
from .interface import RuntimeInterface
Loading
Loading