Skip to content

Commit

Permalink
fix pulse shape deserialization not working for Custom, IIR, and SNZ
Browse files Browse the repository at this point in the history
  • Loading branch information
hay-k committed Aug 19, 2024
1 parent a0193d3 commit 63b80e2
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 17 deletions.
59 changes: 44 additions & 15 deletions src/qibolab/pulses.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@
"""


def _np_array_from_string_or_array_like(x) -> np.ndarray:
"""Convert input into numpy array.
The input can be in one of these forms:
1. string in form '[1, 2, 3, ...]'
2. list, e.g. [1, 2, 3, ...]
3. numpy array. This will be identity conversion.
"""
if isinstance(x, str):
return np.fromstring(x[1:-1], sep=",")
elif isinstance(x, (list, np.ndarray)):
return np.array(x)
else:
raise ValueError(f"Data in unrecognized format: {x}")


class PulseType(Enum):
"""An enumeration to distinguish different types of pulses.
Expand Down Expand Up @@ -215,12 +231,19 @@ def eval(value: str) -> "PulseShape":
To be replaced by proper serialization.
"""
shape_name = re.findall(r"(\w+)", value)[0]
if shape_name not in globals():
raise ValueError(f"shape {value} not found")
shape_parameters = re.findall(r"[-\w+\d\.\d]+", value)[1:]
# TODO: create multiple tests to prove regex working correctly
return globals()[shape_name](*shape_parameters)
match = re.fullmatch(r"(\w+)\((.*)\)", value)
shape_name, params = None, None
if match is not None:
shape_name, params = match.groups()
if match is None or shape_name not in globals():
raise ValueError(f"shape {value} not recognized")

single_item_pattern = r"[^,\s\[\]\(\)]+"
csv_items_pattern = rf"(?:{single_item_pattern}(?:,\s*)?)*"
param_pattern = (
rf"\[{csv_items_pattern}\]|\w+\({csv_items_pattern}\)|{single_item_pattern}"
)
return globals()[shape_name](*re.findall(param_pattern, params))


class Rectangular(PulseShape):
Expand Down Expand Up @@ -509,12 +532,14 @@ class IIR(PulseShape):
# p = [b0 = 1−k +k ·α, b1 = −(1−k)·(1−α),a0 = 1 and a1 = −(1−α)]
# p = [b0, b1, a0, a1]

def __init__(self, b, a, target: PulseShape):
def __init__(self, b, a, target):
self.name = "IIR"
self.target: PulseShape = target
self.target: PulseShape = (
PulseShape.eval(target) if isinstance(target, str) else target
)
self._pulse: Pulse = None
self.a: np.ndarray = np.array(a)
self.b: np.ndarray = np.array(b)
self.a: np.ndarray = _np_array_from_string_or_array_like(a)
self.b: np.ndarray = _np_array_from_string_or_array_like(b)
# Check len(a) = len(b) = 2

def __eq__(self, item) -> bool:
Expand Down Expand Up @@ -596,8 +621,10 @@ class SNZ(PulseShape):
def __init__(self, t_idling, b_amplitude=None):
self.name = "SNZ"
self.pulse: Pulse = None
self.t_idling: float = t_idling
self.b_amplitude = b_amplitude
self.t_idling: float = float(t_idling)
self.b_amplitude = (
float(b_amplitude) if b_amplitude is not None else b_amplitude
)

def __eq__(self, item) -> bool:
"""Overloads == operator."""
Expand Down Expand Up @@ -708,9 +735,11 @@ def __init__(self, envelope_i, envelope_q=None):

self.name = "Custom"
self.pulse: Pulse = None
self.envelope_i: np.ndarray = np.array(envelope_i)
self.envelope_i: np.ndarray = _np_array_from_string_or_array_like(envelope_i)
if envelope_q is not None:
self.envelope_q: np.ndarray = np.array(envelope_q)
self.envelope_q: np.ndarray = _np_array_from_string_or_array_like(
envelope_q
)
else:
self.envelope_q = self.envelope_i

Expand Down Expand Up @@ -745,7 +774,7 @@ def envelope_waveform_q(self, sampling_rate=SAMPLING_RATE) -> Waveform:
raise ShapeInitError

def __repr__(self):
return f"{self.name}({self.envelope_i[:3]}, ..., {self.envelope_q[:3]}, ...)"
return f"{self.name}({self.envelope_i[:3]}, {self.envelope_q[:3]})"


@dataclass
Expand Down
67 changes: 65 additions & 2 deletions tests/test_pulses.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Custom,
Drag,
DrivePulse,
Exponential,
FluxPulse,
Gaussian,
GaussianSquare,
Expand Down Expand Up @@ -275,8 +276,70 @@ def test_pulses_pulseshape_sampling_rate(shape):
def test_pulseshape_eval():
shape = PulseShape.eval("Rectangular()")
assert isinstance(shape, Rectangular)
with pytest.raises(ValueError):
shape = PulseShape.eval("Ciao()")

shape = PulseShape.eval("Exponential(1, 2)")
assert isinstance(shape, Exponential)
assert shape.tau == 1
assert shape.upsilon == 2

shape = PulseShape.eval("Exponential(4, 5, 6)")
assert isinstance(shape, Exponential)
assert shape.tau == 4
assert shape.upsilon == 5
assert shape.g == 6

shape = PulseShape.eval("Gaussian(3.1)")
assert isinstance(shape, Gaussian)
assert shape.rel_sigma == 3.1

shape = PulseShape.eval("GaussianSquare(5, 78)")
assert isinstance(shape, GaussianSquare)
assert shape.rel_sigma == 5
assert shape.width == 78

shape = PulseShape.eval("Drag(4, 0.1)")
assert isinstance(shape, Drag)
assert shape.rel_sigma == 4
assert shape.beta == 0.1

shape = PulseShape.eval("IIR([1, 2, 3], [5], Drag(3, 0.2))")
assert isinstance(shape, IIR)
assert np.array_equal(shape.b, np.array([1, 2, 3]))
assert np.array_equal(shape.a, np.array([5]))
assert isinstance(shape.target, Drag)
assert shape.target.rel_sigma == 3
assert shape.target.beta == 0.2

shape = PulseShape.eval("SNZ(10, 20)")
assert isinstance(shape, SNZ)
assert shape.t_idling == 10
assert shape.b_amplitude == 20

shape = PulseShape.eval("eCap(3.14)")
assert isinstance(shape, eCap)
assert shape.alpha == 3.14

shape = PulseShape.eval("Custom([1, 2, 3], [4, 5, 6])")
assert isinstance(shape, Custom)
assert np.array_equal(shape.envelope_i, np.array([1, 2, 3]))
assert np.array_equal(shape.envelope_q, np.array([4, 5, 6]))

with pytest.raises(ValueError, match="shape .* not recognized"):
_ = PulseShape.eval("Ciao()")


@pytest.mark.parametrize(
"value_str",
["-0.1", "+0.1", "1.", "-3.", "+1.", "-0.1e2", "1e-2", "+1e3", "-3e-1", "-.4"],
)
def test_pulse_shape_eval_numeric_varieties(value_str):
shape = PulseShape.eval(f"Drag(1, {value_str})")
assert isinstance(shape, Drag)
assert shape.beta == float(value_str)

shape = PulseShape.eval(f"Custom([0.1, {value_str}])")
assert isinstance(shape, Custom)
assert np.array_equal(shape.envelope_i, np.array([0.1, float(value_str)]))


@pytest.mark.parametrize("rel_sigma,beta", [(5, 1), (5, -1), (3, -0.03), (4, 0.02)])
Expand Down

0 comments on commit 63b80e2

Please sign in to comment.