forked from pasqal-io/Pulser
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Serialization to the abstract representation (pasqal-io#355)
* Abstract representation for pulses and waveforms * Initial version of abstract seq format (pasqal-io#346) * Preliminary AbstractReprEncoder * added abstract_repr tests (pasqal-io#347) * WIP: Sequence to abstract_repr conversion * Abstract representation schema check (pasqal-io#350) * added json schema test for abstract repr * changed export POC to fit schema * separate tests for abstract repr * sequence misc cleanup and comment * WIP: Updates to the format * Moving custom serialization exceptions * Handling variables in the serialization * Format updates * WIP: Unit tests * Updates to the JSON schema * Restrict export of InterpolatedWaveform * More updates to the JSON Schema * Adding support for new operations * Changing how signatures are stored for abstract representation * Adding support for `set` and `np.ndarray` * Import sorting + myp * Fixing `InterpolatedWaveform` export * Preparing for merge * Adding support for phase shifts and a sequence name * Adding support for "delay" and updating the JSON schema * Updates for the latest schema * Feat: deserialize abstract repr for non param sequences (+refacto abstract serialization) * Fix: Get rid of inline import statements + type-hint fixes * Moving pulser.json.signatures into pulser.json.abstract_repr * Removing `str` variable handling logic * Feat: Deserialize parametrized objects * Make CI run on all PRs * Fix mypy errors * Fix flake8 errors * Fix format and import sorting * Serializer support for args as kwargs * Add function to test the serialization roundtrip * Post-merge fixes * Dropping usage of pos-only args due to python 3.7 conflict * Reaching 100% coverage of the deserializer * Updating test ids * Fixing the path to the json schema * Finish serializer coverage * Adding comments + small typo fixes Co-authored-by: Piotr Migdał <[email protected]> Co-authored-by: MB <[email protected]>
- Loading branch information
1 parent
4696b37
commit 4592d39
Showing
25 changed files
with
2,718 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,6 @@ name: build | |
|
||
on: | ||
pull_request: | ||
branches: | ||
- master | ||
- develop | ||
push: | ||
branches: | ||
- master | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
include README.md | ||
include LICENSE | ||
include pulser/devices/interaction_coefficients/C6_coeffs.json | ||
include pulser/json/abstract_repr/schema.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Copyright 2022 Pulser Development Team | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Serialization and deserialization tools for the abstract representation.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
# Copyright 2022 Pulser Development Team | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Deserializer from JSON in the abstract representation.""" | ||
from __future__ import annotations | ||
|
||
import json | ||
from pathlib import Path | ||
from typing import TYPE_CHECKING, Any, Union, cast, overload | ||
|
||
import jsonschema | ||
|
||
import pulser | ||
import pulser.devices as devices | ||
from pulser.json.abstract_repr.signatures import ( | ||
BINARY_OPERATORS, | ||
UNARY_OPERATORS, | ||
) | ||
from pulser.json.exceptions import AbstractReprError | ||
from pulser.parametrized import ParamObj, Variable | ||
from pulser.pulse import Pulse | ||
from pulser.register.register import Register | ||
from pulser.waveforms import ( | ||
BlackmanWaveform, | ||
CompositeWaveform, | ||
ConstantWaveform, | ||
CustomWaveform, | ||
InterpolatedWaveform, | ||
KaiserWaveform, | ||
RampWaveform, | ||
Waveform, | ||
) | ||
|
||
if TYPE_CHECKING: # pragma: no cover | ||
from pulser.sequence import Sequence | ||
|
||
with open(Path(__file__).parent / "schema.json") as f: | ||
schema = json.load(f) | ||
|
||
VARIABLE_TYPE_MAP = {"int": int, "float": float} | ||
|
||
ExpReturnType = Union[int, float, ParamObj] | ||
|
||
|
||
@overload | ||
def _deserialize_parameter(param: int, vars: dict[str, Variable]) -> int: | ||
pass | ||
|
||
|
||
@overload | ||
def _deserialize_parameter(param: float, vars: dict[str, Variable]) -> float: | ||
pass | ||
|
||
|
||
@overload | ||
def _deserialize_parameter( | ||
param: dict[str, str], vars: dict[str, Variable] | ||
) -> Variable: | ||
pass | ||
|
||
|
||
def _deserialize_parameter( | ||
param: Union[int, float, dict[str, Any]], | ||
vars: dict[str, Variable], | ||
) -> Union[ExpReturnType, Variable]: | ||
"""Deserialize a parameterized object. | ||
A parameter can be either a literal, a variable or an expression. | ||
In the first case, return the literal. Otherwise, return a reference | ||
to the variable, or build an expression referencing variables. | ||
Args: | ||
param: The JSON parametrized object to deserialize | ||
vars: The references to the sequence variables | ||
Returns: | ||
A literal (int | float), a ``Variable``, or a ``ParamObj``. | ||
""" | ||
if not isinstance(param, dict): | ||
# This is a literal | ||
return param | ||
|
||
if "variable" in param: | ||
# This is a reference to a variable. | ||
if param["variable"] not in vars: | ||
raise AbstractReprError( | ||
f"Variable '{param['variable']}' used in operations " | ||
"but not found in declared variables." | ||
) | ||
return vars[param["variable"]] | ||
|
||
if "expression" not in param: | ||
# Can't deserialize param if it is a dict without a | ||
# `variable` or an `expression` key | ||
raise AbstractReprError( | ||
f"Parameter '{param}' is neither a literal nor " | ||
"a variable or an expression." | ||
) | ||
|
||
# This is a unary or a binary expression | ||
expression = ( | ||
param["expression"] if param["expression"] != "div" else "truediv" | ||
) | ||
|
||
if expression in UNARY_OPERATORS: | ||
return cast( | ||
ExpReturnType, | ||
UNARY_OPERATORS[expression]( | ||
_deserialize_parameter(param["lhs"], vars) | ||
), | ||
) | ||
elif expression in BINARY_OPERATORS: | ||
return cast( | ||
ExpReturnType, | ||
BINARY_OPERATORS[expression]( | ||
_deserialize_parameter(param["lhs"], vars), | ||
_deserialize_parameter(param["rhs"], vars), | ||
), | ||
) | ||
else: | ||
raise AbstractReprError(f"Expression '{param['expression']}' invalid.") | ||
|
||
|
||
def _deserialize_waveform(obj: dict, vars: dict) -> Waveform: | ||
|
||
if obj["kind"] == "constant": | ||
return ConstantWaveform( | ||
duration=_deserialize_parameter(obj["duration"], vars), | ||
value=_deserialize_parameter(obj["value"], vars), | ||
) | ||
if obj["kind"] == "ramp": | ||
return RampWaveform( | ||
duration=_deserialize_parameter(obj["duration"], vars), | ||
start=_deserialize_parameter(obj["start"], vars), | ||
stop=_deserialize_parameter(obj["stop"], vars), | ||
) | ||
if obj["kind"] == "blackman": | ||
return BlackmanWaveform( | ||
duration=_deserialize_parameter(obj["duration"], vars), | ||
area=_deserialize_parameter(obj["area"], vars), | ||
) | ||
if obj["kind"] == "blackman_max": | ||
return BlackmanWaveform.from_max_val( | ||
max_val=_deserialize_parameter(obj["max_val"], vars), | ||
area=_deserialize_parameter(obj["area"], vars), | ||
) | ||
if obj["kind"] == "interpolated": | ||
return InterpolatedWaveform( | ||
duration=_deserialize_parameter(obj["duration"], vars), | ||
values=_deserialize_parameter(obj["values"], vars), | ||
times=_deserialize_parameter(obj["times"], vars), | ||
) | ||
if obj["kind"] == "kaiser": | ||
return KaiserWaveform( | ||
duration=_deserialize_parameter(obj["duration"], vars), | ||
area=_deserialize_parameter(obj["area"], vars), | ||
beta=_deserialize_parameter(obj["beta"], vars), | ||
) | ||
if obj["kind"] == "kaiser_max": | ||
return KaiserWaveform.from_max_val( | ||
max_val=_deserialize_parameter(obj["max_val"], vars), | ||
area=_deserialize_parameter(obj["area"], vars), | ||
beta=_deserialize_parameter(obj["beta"], vars), | ||
) | ||
if obj["kind"] == "composite": | ||
wfs = [_deserialize_waveform(wf, vars) for wf in obj["waveforms"]] | ||
return CompositeWaveform(*wfs) | ||
if obj["kind"] == "custom": | ||
return CustomWaveform( | ||
samples=_deserialize_parameter(obj["samples"], vars) | ||
) | ||
|
||
raise AbstractReprError("The object does not encode a known waveform.") | ||
|
||
|
||
def _deserialize_operation(seq: Sequence, op: dict, vars: dict) -> None: | ||
if op["op"] == "target": | ||
seq.target_index( | ||
qubits=_deserialize_parameter(op["target"], vars), | ||
channel=op["channel"], | ||
) | ||
elif op["op"] == "align": | ||
seq.align(*op["channels"]) | ||
elif op["op"] == "delay": | ||
seq.delay( | ||
duration=_deserialize_parameter(op["time"], vars), | ||
channel=op["channel"], | ||
) | ||
elif op["op"] == "phase_shift": | ||
seq.phase_shift_index( | ||
_deserialize_parameter(op["phi"], vars), | ||
*[_deserialize_parameter(t, vars) for t in op["targets"]], | ||
) | ||
elif op["op"] == "pulse": | ||
pulse = Pulse( | ||
amplitude=_deserialize_waveform(op["amplitude"], vars), | ||
detuning=_deserialize_waveform(op["detuning"], vars), | ||
phase=_deserialize_parameter(op["phase"], vars), | ||
post_phase_shift=_deserialize_parameter( | ||
op["post_phase_shift"], vars | ||
), | ||
) | ||
seq.add( | ||
pulse=pulse, | ||
channel=op["channel"], | ||
protocol=op["protocol"], | ||
) | ||
|
||
|
||
def deserialize_abstract_sequence(obj_str: str) -> Sequence: | ||
"""Deserialize a sequence from an abstract JSON object. | ||
Args: | ||
obj_str: the JSON string representing the sequence encoded | ||
in the abstract JSON format. | ||
Returns: | ||
Sequence: The Pulser sequence. | ||
""" | ||
obj = json.loads(obj_str) | ||
|
||
# Validate the format of the data against the JSON schema. | ||
jsonschema.validate(instance=obj, schema=schema) | ||
|
||
# Device | ||
device_name = obj["device"] | ||
device = getattr(devices, device_name) | ||
|
||
# Register | ||
qubits = obj["register"] | ||
reg = Register({q["name"]: (q["x"], q["y"]) for q in qubits}) | ||
|
||
seq = pulser.Sequence(reg, device) | ||
|
||
# Channels | ||
for name, channel_id in obj["channels"].items(): | ||
seq.declare_channel(name, channel_id) | ||
|
||
# Variables | ||
vars = {} | ||
for name, desc in obj["variables"].items(): | ||
v = seq.declare_variable( | ||
cast(str, name), | ||
size=len(desc["value"]), | ||
dtype=VARIABLE_TYPE_MAP[desc["type"]], | ||
) | ||
vars[name] = v | ||
|
||
# Operations | ||
for op in obj["operations"]: | ||
_deserialize_operation(seq, op, vars) | ||
|
||
# Measurement | ||
if obj["measurement"] is not None: | ||
seq.measure(obj["measurement"]) | ||
|
||
return seq |
Oops, something went wrong.