mirror of
https://github.com/nqrduck/quackseq.git
synced 2024-11-21 13:32:25 +00:00
Phase cycling.
This commit is contained in:
parent
bdb5dd7cd5
commit
fc99ded526
6 changed files with 176 additions and 96 deletions
|
@ -1,6 +1,7 @@
|
|||
"""Options for the pulse parameters. Options can be of different types, for example boolean, numeric or function. Generally pulse parameters have different values for the different events in a pulse sequence."""
|
||||
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from quackseq.functions import Function
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -82,7 +83,6 @@ class BooleanOption(Option):
|
|||
def set_value(self, value):
|
||||
"""Sets the value of the option."""
|
||||
self.value = value
|
||||
self.value_changed.emit()
|
||||
|
||||
|
||||
class NumericOption(Option):
|
||||
|
@ -105,7 +105,7 @@ class NumericOption(Option):
|
|||
is_float (bool): If the value is a float.
|
||||
min_value: The minimum value of the option.
|
||||
max_value: The maximum value of the option.
|
||||
slider (bool): If the option should be displayed as a slider. This is not used for the pulseq module, but visualizations can use this information.
|
||||
slider (bool): If the option should be displayed as a slider. This is not used for the quackseq module, but visualizations can use this information.
|
||||
"""
|
||||
super().__init__(name, value)
|
||||
self.is_float = is_float
|
||||
|
@ -115,12 +115,12 @@ class NumericOption(Option):
|
|||
|
||||
def set_value(self, value):
|
||||
"""Sets the value of the option."""
|
||||
if value < self.min_value:
|
||||
self.value = self.min_value
|
||||
self.value_changed.emit()
|
||||
elif value >= self.max_value:
|
||||
self.value = self.max_value
|
||||
self.value_changed.emit()
|
||||
if self.min_value is None or self.max_value is None:
|
||||
self.value = value
|
||||
return
|
||||
|
||||
if self.min_value <= value <= self.max_value:
|
||||
self.value = value
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Value {value} is not in the range of {self.min_value} to {self.max_value}. This should have been caught earlier."
|
||||
|
@ -237,31 +237,50 @@ class FunctionOption(Option):
|
|||
class TableOption(Option):
|
||||
"""A table option has rows and columns and can be used to store a table of values.
|
||||
|
||||
The table option acts as a 'meta' option, which means that we can add different types of options to the table as rows.
|
||||
The table option acts as a 'meta' option, which means that we can add different types of options to the table as columns.
|
||||
Associated with every row we can add a number of different values.
|
||||
The number of rows can be adjusted at runtime.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, value = None) -> None:
|
||||
def __init__(self, name: str, value=None) -> None:
|
||||
"""Initializes the table option."""
|
||||
super().__init__(name, value)
|
||||
|
||||
self.options = []
|
||||
self.columns = []
|
||||
self.n_rows = 0
|
||||
|
||||
def set_value(self, value):
|
||||
"""Sets the value of the option."""
|
||||
self.value = value
|
||||
def add_column(self, column_name: str, option: Option, default_value) -> None:
|
||||
"""Adds an option to the table as column.
|
||||
|
||||
def add_option(self, option: Option) -> None:
|
||||
"""Adds an option to the table.
|
||||
Options are added as columns.
|
||||
|
||||
Args:
|
||||
option (Option): The option to add.
|
||||
option (Option): The class of the option to add.
|
||||
column_name (str): The name of the column.
|
||||
default_value: The default value of the column.
|
||||
"""
|
||||
self.options.append(option)
|
||||
column = self.Column(column_name, option, default_value, self.n_rows)
|
||||
# Add the column to the table
|
||||
self.columns.append(column)
|
||||
|
||||
def set_n_rows(self, n_rows : int) -> None:
|
||||
def set_value(self, values: list) -> None:
|
||||
"""Sets the value of the option.
|
||||
|
||||
Args:
|
||||
values: The values of the different options in the table.
|
||||
"""
|
||||
for i, column in enumerate(values):
|
||||
self.columns[i].set_row_values(column)
|
||||
|
||||
def get_value(self) -> list:
|
||||
"""Gets the value of the option.
|
||||
|
||||
Returns:
|
||||
list: The values of the different options in the table.
|
||||
"""
|
||||
return [column.get_values() for column in self.columns]
|
||||
|
||||
def set_n_rows(self, n_rows: int) -> None:
|
||||
"""Sets the number of rows in the table.
|
||||
|
||||
Args:
|
||||
|
@ -269,6 +288,24 @@ class TableOption(Option):
|
|||
"""
|
||||
self.n_rows = n_rows
|
||||
|
||||
# Now we need to set the number of rows for all the options in the table, the last value is repeated if the number of rows is increased
|
||||
for column in self.columns:
|
||||
column.update_n_rows(n_rows)
|
||||
|
||||
def get_option_by_name(self, name: str) -> Option:
|
||||
"""Gets an option by its name.
|
||||
|
||||
Args:
|
||||
name (str): The name of the option.
|
||||
|
||||
Returns:
|
||||
Option: The option with the given name.
|
||||
"""
|
||||
for option in self.options:
|
||||
if option.name == name:
|
||||
return option
|
||||
raise ValueError(f"Option with name {name} not found")
|
||||
|
||||
def to_json(self):
|
||||
"""Returns a json representation of the option.
|
||||
|
||||
|
@ -294,4 +331,51 @@ class TableOption(Option):
|
|||
obj = cls(data["name"], data["value"])
|
||||
return obj
|
||||
|
||||
class Column:
|
||||
"""Defines a column option for a table option.
|
||||
|
||||
Args:
|
||||
name (str): The name of the option.
|
||||
type (type): The type of the option.
|
||||
default_value: The default value of the option.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, type, default_value, n_rows: int) -> None:
|
||||
"""Initializes the column option."""
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.default_value = default_value
|
||||
|
||||
self.options = []
|
||||
|
||||
def update_n_rows(self, n_rows: int) -> None:
|
||||
"""Updates the number of rows in the column.
|
||||
|
||||
Args:
|
||||
n_rows (int): The number of rows.
|
||||
"""
|
||||
if len(self.options) < n_rows:
|
||||
self.options.extend(
|
||||
self.type(self.name, self.default_value)
|
||||
for i in range(n_rows - len(self.options))
|
||||
)
|
||||
elif len(self.options) > n_rows:
|
||||
self.options = self.options[:n_rows]
|
||||
|
||||
def set_row_values(self, values: list) -> None:
|
||||
"""Sets the values of the options in the column.
|
||||
|
||||
Args:
|
||||
values: The values of the options in the column.
|
||||
"""
|
||||
for i, value in enumerate(values):
|
||||
self.options[i].set_value(value)
|
||||
|
||||
def get_values(self) -> list:
|
||||
"""Gets the values of the options in the column.
|
||||
|
||||
Returns:
|
||||
list: The values of the options in the column.
|
||||
"""
|
||||
return [option.value for option in self.options]
|
||||
|
|
|
@ -15,7 +15,8 @@ class PhaseTable:
|
|||
def __init__(self, quackseq):
|
||||
"""Initializes the phase table."""
|
||||
self.quackseq = quackseq
|
||||
self.readout_scheme = ReadoutScheme(self)
|
||||
# Set phase array to default value
|
||||
self.phase_array = np.array([])
|
||||
self.generate_phase_array()
|
||||
|
||||
def generate_phase_array(self):
|
||||
|
@ -186,9 +187,6 @@ class PhaseTable:
|
|||
# First set the phase array
|
||||
self.phase_array = phase_array
|
||||
|
||||
# Then update the readout scheme (always reset it)
|
||||
self.readout_scheme.update_readout_scheme()
|
||||
|
||||
@property
|
||||
def phase_array(self) -> np.array:
|
||||
"""The phase array of the sequence."""
|
||||
|
@ -201,64 +199,11 @@ class PhaseTable:
|
|||
@property
|
||||
def n_phase_cycles(self) -> int:
|
||||
"""The number of phase cycles in the sequence."""
|
||||
# Calculate the number of phase cycles
|
||||
self.generate_phase_array()
|
||||
return self.phase_array.shape[0]
|
||||
|
||||
@property
|
||||
def n_parameters(self) -> int:
|
||||
"""The number of TX pulse parameters in the sequence."""
|
||||
return self.phase_array.shape[1]
|
||||
|
||||
|
||||
class ReadoutScheme:
|
||||
"""Readout Scheme for the phase table.
|
||||
|
||||
The rows are the phase cycles of the sequence.
|
||||
|
||||
The columns have two different types of options:
|
||||
- The phase value of the phase cycle.
|
||||
- The function that is applied to the phase cycle. Usually this is just +1, -1 or 0.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, phase_table: PhaseTable) -> None:
|
||||
"""Initializes the ReadoutOption."""
|
||||
self.phase_table = phase_table
|
||||
|
||||
def update_readout_scheme(self):
|
||||
"""Update the readout scheme of the sequence. Whenever the phase array is updated it will be reset."""
|
||||
|
||||
self.readout_scheme = np.zeros(
|
||||
(self.phase_table.n_phase_cycles, self.phase_table.n_parameters)
|
||||
)
|
||||
|
||||
def set_phase_cycle(
|
||||
self, phase_cycle: int, phase_shift: float, function: str
|
||||
) -> None:
|
||||
"""Sets the phase shift and function of a phase cycle.
|
||||
|
||||
Args:
|
||||
phase_cycle (int): The phase cycle.
|
||||
phase_shift (float): The phase shift.
|
||||
function (str): The function.
|
||||
"""
|
||||
self.readout_scheme[phase_cycle] = [phase_shift, function]
|
||||
|
||||
@property
|
||||
def readout_scheme(self) -> np.array:
|
||||
"""The readout scheme of the sequence."""
|
||||
return self._readout_scheme
|
||||
|
||||
@readout_scheme.setter
|
||||
def readout_scheme(self, readout_scheme: list) -> None:
|
||||
"""Sets the readout scheme of the sequence.
|
||||
|
||||
Args:
|
||||
readout_scheme (list): The readout scheme.
|
||||
"""
|
||||
# Sanity check
|
||||
if len(readout_scheme) != self.phase_table.n_phase_cycles:
|
||||
raise ValueError(
|
||||
f"Length of readout scheme ({len(readout_scheme)}) does not match the number of phase cycles ({self.phase_table.n_phase_cycles})"
|
||||
)
|
||||
|
||||
self._readout_scheme = readout_scheme
|
||||
|
|
|
@ -6,11 +6,13 @@ Todo:
|
|||
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import override
|
||||
|
||||
import numpy as np
|
||||
from numpy.core.multiarray import array as array
|
||||
|
||||
from quackseq.options import (
|
||||
TableOption,
|
||||
BooleanOption,
|
||||
FunctionOption,
|
||||
NumericOption,
|
||||
|
@ -81,6 +83,16 @@ class PulseParameter:
|
|||
return option
|
||||
raise ValueError(f"Option with name {name} not found")
|
||||
|
||||
def update_option(self, sequence: "QuackSequence") -> None:
|
||||
"""Generic update option method for pulse parameters.
|
||||
|
||||
This can be implemented by subclasses to update the options of the pulse parameter whenever the parameter is called (e.g. in the GUI).
|
||||
|
||||
Args:
|
||||
sequence (QuackSequence): The sequence to update the options from.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TXPulse(PulseParameter):
|
||||
"""Basic TX Pulse Parameter. It includes options for the relative amplitude, the phase and the pulse shape.
|
||||
|
@ -168,6 +180,8 @@ class RXReadout(PulseParameter):
|
|||
"""
|
||||
|
||||
RX = "Enable RX Readout"
|
||||
READOUT_SCHEME = "Readout Scheme"
|
||||
PHASE = "Phase"
|
||||
|
||||
def __init__(self, name) -> None:
|
||||
"""Initializes the RX Readout PulseParameter.
|
||||
|
@ -177,6 +191,27 @@ class RXReadout(PulseParameter):
|
|||
super().__init__(name)
|
||||
self.add_option(BooleanOption(self.RX, False))
|
||||
|
||||
# Readout Scheme:
|
||||
readout_option = TableOption(self.READOUT_SCHEME)
|
||||
|
||||
# Add Phase Option to Readout Scheme
|
||||
phase_option = NumericOption
|
||||
|
||||
readout_option.add_column(self.PHASE, phase_option, 0)
|
||||
|
||||
# Set number of rows to default value
|
||||
readout_option.set_n_rows(1)
|
||||
|
||||
self.add_option(readout_option)
|
||||
|
||||
@override
|
||||
def update_option(self, sequence: "QuackSequence") -> None:
|
||||
"""Adjusts the number of rows in the table option based on the number of phase cycles in the sequence."""
|
||||
n_phase_cycles = sequence.get_n_phase_cycles()
|
||||
readout_option = self.get_option_by_name(self.READOUT_SCHEME)
|
||||
readout_option.set_n_rows(n_phase_cycles)
|
||||
logger.debug(f"Updated RX Readout option with {n_phase_cycles} rows")
|
||||
|
||||
|
||||
class Gate(PulseParameter):
|
||||
"""Basic PulseParameter for the Gate. It includes an option for the Gate state.
|
||||
|
|
|
@ -242,6 +242,12 @@ class QuackSequence(PulseSequence):
|
|||
|
||||
self.phase_table = PhaseTable(self)
|
||||
|
||||
def update_options(self) -> None:
|
||||
"""Updates the options of the pulse parameters."""
|
||||
for event in self.events:
|
||||
for pulse_parameter in event.parameters.values():
|
||||
pulse_parameter.update_option(self)
|
||||
|
||||
def add_blank_event(self, event_name: str, duration: float) -> Event:
|
||||
"""Adds a blank event to the pulse sequence.
|
||||
|
||||
|
@ -280,6 +286,8 @@ class QuackSequence(PulseSequence):
|
|||
self.set_tx_phase(event, phase)
|
||||
self.set_tx_shape(event, shape)
|
||||
|
||||
self.update_options()
|
||||
|
||||
return event
|
||||
|
||||
def add_readout_event(self, event_name: str, duration: float) -> Event:
|
||||
|
@ -360,6 +368,8 @@ class QuackSequence(PulseSequence):
|
|||
TXPulse.N_PHASE_CYCLES
|
||||
).value = n_phase_cycles
|
||||
|
||||
self.update_options()
|
||||
|
||||
def set_tx_phase_cycle_group(
|
||||
self, event: Event | str, phase_cycle_group: int
|
||||
) -> None:
|
||||
|
@ -376,6 +386,8 @@ class QuackSequence(PulseSequence):
|
|||
TXPulse.PHASE_CYCLE_GROUP
|
||||
).value = phase_cycle_group
|
||||
|
||||
self.update_options()
|
||||
|
||||
# RX Specific functions
|
||||
|
||||
def set_rx(self, event: Event | str, rx: bool) -> None:
|
||||
|
@ -390,30 +402,34 @@ class QuackSequence(PulseSequence):
|
|||
|
||||
event.parameters[self.RX_READOUT].get_option_by_name(RXReadout.RX).value = rx
|
||||
|
||||
def set_rx_readout_scheme(self, event: Event | str, readout_scheme: list) -> None:
|
||||
"""Sets the readout scheme of the receiver.
|
||||
def set_rx_phase(self, event: Event | str, phase: list) -> None:
|
||||
"""Sets the phase of the receiver.
|
||||
|
||||
Args:
|
||||
event (Event | str): The event to set the readout scheme for or the name of the event
|
||||
readout_scheme (list): The readout scheme of the receiver
|
||||
event (Event | str): The event to set the phase for or the name of the event
|
||||
phase (list): The phase of the receiver
|
||||
"""
|
||||
if isinstance(event, str):
|
||||
event = self.get_event_by_name(event)
|
||||
|
||||
# Check that the readout scheme is valid
|
||||
self.phase_table.generate_phase_array()
|
||||
n_cycles = self.phase_table.n_phase_cycles
|
||||
rx_table = event.parameters[self.RX_READOUT].get_option_by_name(RXReadout.READOUT_SCHEME)
|
||||
|
||||
rows = len(readout_scheme)
|
||||
# Get the actual option
|
||||
phase_option = rx_table.get_option_by_name(RXReadout.PHASE)
|
||||
|
||||
if rows != n_cycles:
|
||||
# Check that the number of phases is the same as the number of phase cycles
|
||||
if len(phase) != self.get_n_phase_cycles():
|
||||
raise ValueError(
|
||||
f"Readout scheme needs to have {n_cycles} cycles, but has {rows}"
|
||||
f"Number of phases ({len(phase)}) needs to be the same as the number of phase cycles ({self.get_n_phase_cycles()})"
|
||||
)
|
||||
|
||||
# Old way - implement the sequence wide readout scheme here
|
||||
#event.parameters[self.RX_READOUT].get_option_by_name(
|
||||
# RXReadout.READOUT_SCHEME
|
||||
#).value = readout_scheme
|
||||
# Set the values
|
||||
phase_option.values = phase
|
||||
|
||||
self.phase_table.readout_scheme.readout_scheme = readout_scheme
|
||||
def get_n_phase_cycles(self) -> int:
|
||||
"""Returns the number of phase cycles of the pulse sequence.
|
||||
|
||||
Returns:
|
||||
int: The number of phase cycles
|
||||
"""
|
||||
return self.phase_table.n_phase_cycles
|
|
@ -26,6 +26,6 @@ def create_COMPFID():
|
|||
# No phase shifiting of the receive data but weighting of -1 for the 45 degree pulse, +1 for the 135 degree pulse, -1 for the 225 degree pulse and +1 for the 315 degree pulse
|
||||
readout_scheme = [0, 180, 0, 180]
|
||||
|
||||
COMPFID.set_rx_readout_scheme("rx", readout_scheme)
|
||||
COMPFID.set_rx_phase("rx", readout_scheme)
|
||||
|
||||
return COMPFID
|
||||
|
|
|
@ -29,6 +29,6 @@ def create_SEPC() -> QuackSequence:
|
|||
# Readout scheme for phase cycling TX pulses have the scheme 0 90 180 270
|
||||
readout_scheme = [0, 90, 180, 270]
|
||||
|
||||
sepc.set_rx_readout_scheme("rx", readout_scheme)
|
||||
sepc.set_rx_phase("rx", readout_scheme)
|
||||
|
||||
return sepc
|
||||
|
|
Loading…
Reference in a new issue