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/virtual gate set #4

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
fd270ad
add virtual gate set
nulinspiratie Nov 29, 2023
cd4293e
Merge remote-tracking branch 'origin/main' into feat/virtual-gate-set
nulinspiratie Nov 29, 2023
156d235
most features of virtual gate set are added
nulinspiratie Nov 29, 2023
2a87141
Add operation to config
nulinspiratie Nov 29, 2023
4c4f965
Merge branch 'main' into feat/virtual-gate-set
nulinspiratie Nov 30, 2023
d53f418
add pulse.id
nulinspiratie Nov 30, 2023
62684e4
add pulse name to elements
nulinspiratie Nov 30, 2023
883b3f9
Merge branch 'main' into feat/virtual-gate-set
nulinspiratie Nov 30, 2023
44b8a17
Do not validate virtual gate pulses
nulinspiratie Nov 30, 2023
27f344d
add config after
nulinspiratie Nov 30, 2023
d514a2b
added non-working tests
nulinspiratie Nov 30, 2023
c009b19
Allow quamcomponents to be sorted for config
nulinspiratie Nov 30, 2023
c313e3a
Merge branch 'feat/config-sorting' into feat/virtual-gate-set
nulinspiratie Nov 30, 2023
4a013e6
Merge branch 'main' into feat/virtual-gate-set
nulinspiratie Dec 5, 2023
e28163d
add custom pulse.channel
nulinspiratie Dec 6, 2023
b44d6a1
remove ability to set pulse channel
nulinspiratie Dec 6, 2023
044112b
add virtual_gate_set to quam.components import
nulinspiratie Dec 6, 2023
c25e13e
testing virtual gate set
nulinspiratie Dec 6, 2023
6806217
Fix pulse amplitude and length in VirtualGateSet
nulinspiratie Dec 6, 2023
ccae55a
started adding sticky to channels
nulinspiratie Dec 6, 2023
3923874
added sticky to channel
nulinspiratie Dec 6, 2023
e08e94e
Ensure sticky is added to config after channel
nulinspiratie Dec 6, 2023
c85c553
Sticky -> StickyChannelAddon
nulinspiratie Dec 6, 2023
0df0b6a
fix: pulse.pulse_name
nulinspiratie Dec 6, 2023
4605497
add amplitude_scale, duration to pulse
nulinspiratie Dec 6, 2023
72de244
Merge branch 'feat/sticky-channels' into feat/virtual-gate-set
nulinspiratie Dec 7, 2023
e8ed071
Merge remote-tracking branch 'origin/main' into feat/virtual-gate-set
nulinspiratie Dec 7, 2023
539be8a
Add duration attribute to StickyChannelAddon class
nulinspiratie Dec 7, 2023
89e3044
Merge branch 'feat/in-out-single-channel' into feat/virtual-gate-set
nulinspiratie Dec 7, 2023
26af1f0
Merge branch 'feat/in-out-single-channel' into feat/virtual-gate-set
nulinspiratie Dec 7, 2023
3af0a97
fix: output_offset
nulinspiratie Dec 7, 2023
adca72c
Merge branch 'feat/in-out-single-channel' into feat/virtual-gate-set
nulinspiratie Dec 7, 2023
65e8f86
Merge branch 'feat/in-out-single-channel' into feat/virtual-gate-set
nulinspiratie Dec 7, 2023
70666fc
Merge branch 'feat/2Q-operations' into feat/virtual-gate-set
nulinspiratie Jan 16, 2024
43f191c
Merge remote-tracking branch 'origin/main' into feat/virtual-gate-set
nulinspiratie Jan 25, 2024
eb23bc6
Merge branch 'feat/sort-config-generation' into feat/virtual-gate-set
nulinspiratie Jan 25, 2024
f20b022
Merge remote-tracking branch 'origin/main' into feat/virtual-gate-set
nulinspiratie Jan 25, 2024
5b2657a
Merge branch 'main' into feat/virtual-gate-set
nulinspiratie Jan 25, 2024
40898fe
Merge remote-tracking branch 'origin/main' into feat/virtual-gate-set
nulinspiratie Apr 23, 2024
b1b0c83
missed @quam_dataclass
nulinspiratie Apr 23, 2024
883dcd6
fix qua types import
nulinspiratie Apr 23, 2024
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
2 changes: 2 additions & 0 deletions quam/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from .hardware import *
from .octave import *
from .channels import *
from .virtual_gate_set import *
from . import pulses

__all__ = [
*hardware.__all__,
*channels.__all__,
*octave.__all__,
*virtual_gate_set.__all__,
"pulses",
]
39 changes: 39 additions & 0 deletions quam/components/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@


__all__ = [
"StickyChannelAddon",
"DigitalOutputChannel",
"Channel",
"SingleChannel",
Expand All @@ -43,6 +44,40 @@
]


@quam_dataclass
class StickyChannelAddon(QuamComponent):
duration: int
enabled: bool = True
analog: bool = True
digital: bool = True

@property
def channel(self) -> Optional["Channel"]:
"""If the parent is a channel, returns the parent, otherwise returns None."""
if isinstance(self.parent, Channel):
return self.parent
else:
return

@property
def config_settings(self):
if self.channel is not None:
return {"after": [self.channel]}

def apply_to_config(self, config: dict) -> None:
if self.channel is None:
return

if not self.enabled:
return

config["elements"][self.channel.name]["sticky"] = {
"analog": self.analog,
"digital": self.digital,
"duration": self.duration,
}


@quam_dataclass
class DigitalOutputChannel(QuamComponent):
"""QuAM component for a digital output channel (signal going out of the OPX)
Expand Down Expand Up @@ -127,12 +162,16 @@ class Channel(QuamComponent):
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
sticky (Sticky): Optional sticky parameters for the channel, i.e. defining
whether successive pulses are applied w.r.t the previous pulse or w.r.t 0 V.
If not specified, this channel is not sticky.
"""

operations: Dict[str, Pulse] = field(default_factory=dict)

id: Union[str, int] = None
_default_label: ClassVar[str] = "ch" # Used to determine name from id
sticky: StickyChannelAddon = None

digital_outputs: Dict[str, DigitalOutputChannel] = field(default_factory=dict)

Expand Down
91 changes: 91 additions & 0 deletions quam/components/virtual_gate_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from copy import copy
from dataclasses import field
import numpy as np
from typing import Dict, List, Union

from quam.components.pulses import Pulse
from quam.components.channels import SingleChannel
from quam.core import QuamComponent, quam_dataclass

from qm.qua._dsl import (
AmpValuesType,
QuaNumberType,
)


__all__ = ["VirtualPulse", "VirtualGateSet"]


@quam_dataclass
class VirtualPulse(Pulse):
amplitudes: Dict[str, float]
# pulses: List[Pulse] = None # Should be added later

@property
def virtual_gate_set(self):
virtual_gate_set = self.parent.parent
assert isinstance(virtual_gate_set, VirtualGateSet)
return virtual_gate_set

def waveform_function(self): ...


@quam_dataclass
class VirtualGateSet(QuamComponent):
gates: List[SingleChannel]
virtual_gates: Dict[str, List[float]]

pulse_defaults: List[Pulse] = field(default_factory=list)
operations: Dict[str, VirtualPulse] = field(default_factory=dict)

@property
def config_settings(self):
return {"after": self.gates}

def convert_amplitudes(self, **virtual_gate_amplitudes):
gate_amplitudes = np.zeros(len(self.gates))
for virtual_gate_name, amplitude in virtual_gate_amplitudes.items():
scales = self.virtual_gates[virtual_gate_name]
gate_amplitudes += amplitude * np.array(scales)

return gate_amplitudes

def play(
self,
pulse_name,
amplitude_scale: Union[float, AmpValuesType] = None,
duration: QuaNumberType = None,
**kwargs,
):
"""Play a pulse on all gates in the virtual gate set

Args:
pulse_name: The name of the pulse to play
amplitude_scale: The amplitude scale to apply to the pulse
duration: The duration of the pulse
**kwargs: Additional kwargs to pass to the play function
"""
for gate in self.gates:
gate.play(
pulse_name,
validate=False,
amplitude_scale=amplitude_scale,
duration=duration,
**kwargs,
)

def apply_to_config(self, config: dict) -> None:
for operation_name, operation in self.operations.items():
gate_pulses = [copy(pulse) for pulse in self.pulse_defaults]
gate_amplitudes = self.convert_amplitudes(**operation.amplitudes)

for gate, pulse, amplitude in zip(self.gates, gate_pulses, gate_amplitudes):
pulse.id = operation_name
pulse.amplitude = amplitude
pulse.length = operation.length
pulse.parent = None # Reset parent so it can be attached to new parent
pulse.parent = gate
pulse.apply_to_config(config)

element_config = config["elements"][gate.name]
element_config["operations"][operation_name] = pulse.pulse_name
129 changes: 129 additions & 0 deletions tests/components/test_virtual_gate_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from dataclasses import field
from typing import Any, List
import pytest

from quam.core import *
from quam.components import *


@quam_dataclass
class QuAM(QuamRoot):
gates: List[SingleChannel] = field(default_factory=list)
virtual_gate_set: VirtualGateSet = None


@pytest.fixture
def machine_virtual():
machine = QuAM()
machine.gates = [
SingleChannel(
id="gate1",
opx_output=("con1", 1),
),
SingleChannel(
id="gate2",
opx_output=("con1", 2),
),
]

machine.virtual_gate_set = VirtualGateSet(
gates=["#/gates/0", "#/gates/1"],
virtual_gates={"eps": [1, 0.1], "U": [0.1, 0.8]},
pulse_defaults=[
pulses.SquarePulse(amplitude=None, length=None),
pulses.SquarePulse(amplitude=None, length=None),
],
)

return machine


def test_instantiate_virtual_gate_set(machine_virtual): ...


def test_virtual_gate_set_generate_empty_config(machine_virtual):
machine_virtual.generate_config()


def test_generate_config_virtual_pulse(machine_virtual):
machine_virtual.virtual_gate_set.operations["readout"] = VirtualPulse(
length=40, amplitudes={"eps": 1, "U": 0.5}
)

config = machine_virtual.generate_config()
expected_config = {
"controllers": {
"con1": {
"analog_inputs": {},
"analog_outputs": {1: {"offset": 0}, 2: {"offset": 0}},
"digital_outputs": {},
}
},
"digital_waveforms": {"ON": {"samples": [[1, 0]]}},
"elements": {
"gate1": {
"operations": {"readout": "gate1.readout.pulse"},
"singleInput": {"port": ("con1", 1)},
},
"gate2": {
"operations": {"readout": "gate2.readout.pulse"},
"singleInput": {"port": ("con1", 2)},
},
},
"integration_weights": {},
"mixers": {},
"oscillators": {},
"pulses": {
"const_pulse": {
"length": 1000,
"operation": "control",
"waveforms": {"I": "const_wf", "Q": "zero_wf"},
},
"gate1.readout.pulse": {
"length": 40,
"operation": "control",
"waveforms": {"single": "gate1.readout.wf"},
},
"gate2.readout.pulse": {
"length": 40,
"operation": "control",
"waveforms": {"single": "gate2.readout.wf"},
},
},
"version": 1,
"waveforms": {
"const_wf": {"sample": 0.1, "type": "constant"},
"gate1.readout.wf": {"sample": 1.05, "type": "constant"},
"gate2.readout.wf": {"sample": 0.5, "type": "constant"},
"zero_wf": {"sample": 0.0, "type": "constant"},
},
}

assert config == expected_config


class MockPlay:
calls = []

def __init__(self) -> None:
MockPlay.calls.clear()

def __call__(self, *args: Any, **kwargs: Any) -> Any:
MockPlay.calls.append({"args": args, "kwargs": kwargs})


def test_play_virtual_gate_set(machine_virtual, mocker):
machine_virtual.virtual_gate_set.operations["readout"] = VirtualPulse(
length=40, amplitudes={"eps": 1, "U": 0.5}
)

mocker.patch("quam.components.channels.play", MockPlay())
machine_virtual.virtual_gate_set.play("readout")

assert len(MockPlay.calls) == 2
assert not MockPlay.calls[0]["args"]
assert MockPlay.calls[0]["kwargs"]["pulse"] == "readout"
assert MockPlay.calls[0]["kwargs"]["element"] == "gate1"
assert MockPlay.calls[1]["kwargs"]["pulse"] == "readout"
assert MockPlay.calls[1]["kwargs"]["element"] == "gate2"
MockPlay.calls