Skip to content

Commit

Permalink
Release v0.11.0
Browse files Browse the repository at this point in the history
Release v0.11.0
  • Loading branch information
HGSilveri authored Mar 30, 2023
2 parents f7a1746 + 6aedb6b commit c26d4d8
Show file tree
Hide file tree
Showing 39 changed files with 18,611 additions and 1,225 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:
jobs:
full-tests:
runs-on: ${{ matrix.os }}
env:
# Set Matplotlib backend to fix flaky execution on Windows
MPLBACKEND: agg
strategy:
fail-fast: false
matrix:
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.10.0
0.11.0
10 changes: 9 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,19 @@ computers and simulators, check the pages in :doc:`review`.

review

.. toctree::
:maxdepth: 2
:caption: Classical Simulation

tutorials/noisy_sim
tutorials/spam
tutorials/laser_noise
tutorials/kraus_ops

.. toctree::
:maxdepth: 1
:caption: Advanced Features

tutorials/noisy_sim
tutorials/phase_shifts_vz_gates
tutorials/composite_wfs
tutorials/paramseqs
Expand Down
3 changes: 3 additions & 0 deletions docs/source/tutorials/kraus_ops.nblink
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"path": "../../../tutorials/classical_simulation/Simulating with effective noise channels.ipynb"
}
3 changes: 3 additions & 0 deletions docs/source/tutorials/laser_noise.nblink
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"path": "../../../tutorials/classical_simulation/Simulating with laser noises.ipynb"
}
2 changes: 1 addition & 1 deletion docs/source/tutorials/noisy_sim.nblink
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"path": "../../../tutorials/advanced_features/Simulating Sequences with Errors and Noise.ipynb"
"path": "../../../tutorials/classical_simulation/Simulating Sequences with Errors and Noise.ipynb"
}
3 changes: 3 additions & 0 deletions docs/source/tutorials/spam.nblink
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"path": "../../../tutorials/classical_simulation/Simulating with SPAM errors.ipynb"
}
73 changes: 54 additions & 19 deletions pulser-core/pulser/json/abstract_repr/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import TYPE_CHECKING, Any, Type, Union, cast, overload

import jsonschema
import jsonschema.exceptions

import pulser
import pulser.devices as devices
Expand All @@ -32,7 +33,7 @@
BINARY_OPERATORS,
UNARY_OPERATORS,
)
from pulser.json.exceptions import AbstractReprError
from pulser.json.exceptions import AbstractReprError, DeserializeDeviceError
from pulser.parametrized import ParamObj, Variable
from pulser.pulse import Pulse
from pulser.register.mappable_reg import MappableRegister
Expand Down Expand Up @@ -263,30 +264,44 @@ def _deserialize_channel(obj: dict[str, Any]) -> Channel:
params["eom_config"] = None
if obj["eom_config"] is not None:
data = obj["eom_config"]
params["eom_config"] = RydbergEOM(
mod_bandwidth=data["mod_bandwidth"],
limiting_beam=RydbergBeam[data["limiting_beam"]],
max_limiting_amp=data["max_limiting_amp"],
intermediate_detuning=data["intermediate_detuning"],
controlled_beams=tuple(
RydbergBeam[beam] for beam in data["controlled_beams"]
),
)
try:
params["eom_config"] = RydbergEOM(
mod_bandwidth=data["mod_bandwidth"],
limiting_beam=RydbergBeam[data["limiting_beam"]],
max_limiting_amp=data["max_limiting_amp"],
intermediate_detuning=data["intermediate_detuning"],
controlled_beams=tuple(
RydbergBeam[beam] for beam in data["controlled_beams"]
),
)
except ValueError as e:
raise AbstractReprError(
"RydbergEOM deserialization failed."
) from e
elif obj["basis"] == "digital":
channel_cls = Raman
elif obj["basis"] == "XY":
channel_cls = Microwave
# No other basis allowed by the schema

for param in dataclasses.fields(channel_cls):
if param.init and param.name != "eom_config":
params[param.name] = obj[param.name]
return channel_cls(**params)
try:
return channel_cls(**params)
except (ValueError, NotImplementedError) as e:
raise AbstractReprError("Channel deserialization failed.") from e


def _deserialize_layout(layout_obj: dict[str, Any]) -> RegisterLayout:
return RegisterLayout(
layout_obj["coordinates"], slug=layout_obj.get("slug")
)
try:
return RegisterLayout(
layout_obj["coordinates"], slug=layout_obj.get("slug")
)
except ValueError as e:
raise AbstractReprError(
"Register layout deserialization failed."
) from e


def _deserialize_device_object(obj: dict[str, Any]) -> Device | VirtualDevice:
Expand All @@ -312,7 +327,10 @@ def _deserialize_device_object(obj: dict[str, Any]) -> Device | VirtualDevice:
)
else:
params[param.name] = obj[param.name]
return device_cls(**params)
try:
return device_cls(**params)
except (ValueError, TypeError) as e:
raise AbstractReprError("Device deserialization failed.") from e


def deserialize_abstract_sequence(obj_str: str) -> Sequence:
Expand Down Expand Up @@ -405,8 +423,25 @@ def deserialize_device(obj_str: str) -> BaseDevice:
Returns:
BaseDevice: The Pulser device.
Raises:
DeserializeDeviceError: Whenever the device deserialization
fails due to an invalid 'obj_str'.
"""
obj = json.loads(obj_str)
# Validate the format of the data against the JSON schema.
jsonschema.validate(instance=obj, schema=schemas["device"])
return _deserialize_device_object(obj)
if not isinstance(obj_str, str):
type_error = TypeError(
f"'obj_str' must be a string, not {type(obj_str)}."
)
raise DeserializeDeviceError from type_error

try:
obj = json.loads(obj_str)
# Validate the format of the data against the JSON schema.
jsonschema.validate(instance=obj, schema=schemas["device"])
return _deserialize_device_object(obj)
except (
json.JSONDecodeError, # From json.loads
jsonschema.exceptions.ValidationError, # From jsonschema.validate
AbstractReprError, # From _deserialize_device_object
) as e:
raise DeserializeDeviceError from e
8 changes: 2 additions & 6 deletions pulser-core/pulser/json/abstract_repr/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,8 @@ def abstract_repr(name: str, *args: Any, **kwargs: Any) -> dict[str, Any]:
res.update(signature.extra) # Starts with extra info ({} if undefined)
# With PulseSignature.all_pos_args(), we safeguard against the opposite
# case where an expected keyword argument is given as a positional argument
res.update(
{
arg_name: arg_val
for arg_name, arg_val in zip(signature.all_pos_args(), args)
}
)
res.update(dict(zip(signature.all_pos_args(), args)))

# Account for keyword arguments given as pos args
max_pos_args = len(signature.pos) + len(
set(signature.keyword) - set(kwargs)
Expand Down
6 changes: 6 additions & 0 deletions pulser-core/pulser/json/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ class AbstractReprError(Exception):
"""

pass


class DeserializeDeviceError(Exception):
"""Exception raised when device deserialization fails."""

pass
75 changes: 60 additions & 15 deletions pulser-core/pulser/parametrized/paramobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import numpy as np

import pulser.parametrized
from pulser.json.abstract_repr.serializer import abstract_repr
from pulser.json.abstract_repr.signatures import (
BINARY_OPERATORS,
Expand Down Expand Up @@ -161,6 +162,16 @@ def __init__(self, cls: Callable, *args: Any, **kwargs: Any) -> None:
self._instance = None
self._vars_state: dict[str, int] = {}

@property
def _default_kwargs(self) -> dict[str, Any]:
"""The default values for the object's keyword arguments."""
cls_signature = inspect.signature(self.cls).parameters
return {
param: cls_signature[param].default
for param in cls_signature
if cls_signature[param].default != cls_signature[param].empty
}

@property
def variables(self) -> dict[str, Variable]:
"""Returns all involved variables."""
Expand Down Expand Up @@ -241,19 +252,18 @@ def _to_abstract_repr(self) -> dict[str, Any]:
# classmethod
cls_name = self.args[0].__name__
name = f"{cls_name}.{op_name}"
if cls_name == "Pulse":
signature = (
"amplitude",
"detuning",
"phase",
"post_phase_shift",
)
all_args = {
**dict(zip(signature, self.args[1:])),
**self.kwargs,
}
if "post_phase_shift" not in all_args:
all_args["post_phase_shift"] = 0.0
signature = SIGNATURES[
"Pulse" if cls_name == "Pulse" else name
]
# No existing classmethod has *args in its signature
assert (
signature.var_pos is None
), "Unexpected signature with VAR_POSITIONAL arguments."
all_args = {
**self._default_kwargs,
**dict(zip(signature.all_pos_args(), self.args[1:])),
**self.kwargs,
}
if name == "Pulse.ConstantAmplitude":
all_args["amplitude"] = abstract_repr(
"ConstantWaveform", 0, all_args["amplitude"]
Expand All @@ -265,13 +275,48 @@ def _to_abstract_repr(self) -> dict[str, Any]:
)
return abstract_repr("Pulse", **all_args)
else:
return abstract_repr(name, *self.args[1:], **self.kwargs)
return abstract_repr(name, **all_args)

raise NotImplementedError(
"Instance or static method serialization is not supported."
)
elif op_name in SIGNATURES:
return abstract_repr(op_name, *self.args, **self.kwargs)
signature = SIGNATURES[op_name]
filtered_defaults = {
key: value
for key, value in self._default_kwargs.items()
if key in signature.keyword
}
full_kwargs = {**filtered_defaults, **self.kwargs}
if signature.var_pos is not None:
# No args can be given with a keyword
return abstract_repr(op_name, *self.args, **full_kwargs)

all_args = {
**full_kwargs,
**dict(zip(signature.all_pos_args(), self.args)),
}
if op_name == "InterpolatedWaveform" and all_args["times"] is None:
if isinstance(
all_args["values"],
pulser.parametrized.Variable, # Avoids circular import
):
num_values = all_args["values"].size
else:
try:
num_values = len(all_args["values"])
except TypeError:
raise AbstractReprError(
"An InterpolatedWaveform with 'values' of unknown "
"length and unspecified 'times' can't be "
"serialized to the abstract representation. To "
"keep the same argument for 'values', provide "
"compatible 'times' explicitly."
)

all_args["times"] = np.linspace(0, 1, num=num_values)

return abstract_repr(op_name, **all_args)

elif op_name in UNARY_OPERATORS:
return dict(expression=op_name, lhs=self.args[0])
Expand Down
Loading

0 comments on commit c26d4d8

Please sign in to comment.