Source code for silq.instrument_interfaces.interface

from typing import Union, List, Dict, Any, Tuple
import logging

from qcodes import Instrument
from qcodes.utils import validators as vals

from silq.pulses.pulse_sequences import PulseSequence
from silq.pulses.pulse_types import Pulse


logger = logging.getLogger(__name__)


[docs]class Channel: """Instrument channel, specified in :class:`InstrumentInterface` A channel usually corresponds to a physical channel in the instrument, such as an input/output channel, triggering channel, etc. Args: instrument_name: Name of instrument. name: Channel name, usually specified on the instrument. id: Channel id, usually zero-based index. input: Channel is an input channel. output: Channel is an output channel. input_trigger: Channel is used as instrument trigger. input_TTL: Channel input signal must be TTL output_TTL: Channel output signal is TTL with (low, high) voltage invert: Channel signal is inverted: on is low signal, off is high signal """ def __init__(self, instrument_name: str, name: str, id: int = None, input: bool = False, output: bool = False, input_trigger: bool = False, input_TTL: bool = False, output_TTL: Tuple[float, float] = False, invert: bool = False): self.instrument = instrument_name self.name = name self.id = id self.input = input self.output = output self.input_trigger = input_trigger self.input_TTL = input_TTL self.output_TTL = output_TTL self.invert = invert def __repr__(self): output_str = f"Channel {self.name} (id={self.id})" if self.input: output_str += ', input' if self.output: output_str += ', output' if self.input_trigger: output_str += ', input_trigger' if self.input_TTL: output_str += ', input_TTL' if self.output_TTL is not None: output_str += f', output_TTL: {self.output_TTL}' if self.invert: output_str += ', invert' return output_str def __eq__(self, other): if isinstance(other, self.__class__): return self.__dict__ == other.__dict__ else: return False def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash(tuple(sorted(self.__dict__.items())))
[docs]class InstrumentInterface(Instrument): """ Interface between the :class:`.Layout` and instruments When a :class:`.PulseSequence` is targeted in the :class:`.Layout`, the pulses are directed to the appropriate interface. Each interface is responsible for translating all pulses directed to it into instrument commands. During the actual measurement, the instrument's operations will correspond to that required by the pulse sequence. The interface also contains a list of all available channels in the instrument. Args: instrument_name: name of instrument for which this is an interface Note: For a given instrument, its associated interface can be found using :func:`get_instrument_interface` """ def __init__(self, instrument_name: str, **kwargs): # TODO: pass along actual instrument instead of name super().__init__(name=instrument_name + '_interface', **kwargs) self.instrument = self.find_instrument(name=instrument_name) self._input_channels = {} self._output_channels = {} self._channels = {} self.pulse_sequence = PulseSequence(allow_untargeted_pulses=False, allow_pulse_overlap=False) self.input_pulse_sequence = PulseSequence( allow_untargeted_pulses=False, allow_pulse_overlap=False) self.add_parameter('instrument_name', set_cmd=None, initial_value=instrument_name, vals=vals.Anything()) self.add_parameter('is_primary', set_cmd=None, initial_value=False, vals=vals.Bool()) self.pulse_implementations = [] def __repr__(self): return f'{self.name} interface'
[docs] def get_channel(self, channel_name: str) -> Channel: """Get channel by its name. Args: channel_name: name of channel Returns: Channel whose name corresponds to channel_name """ return self._channels[channel_name]
[docs] def get_pulse_implementation(self, pulse: Pulse, connections: list = None) -> Union[Pulse, None]: """Get a target implementation of a pulse if it exists. If no implementation can be found for the pulse, or if the pulse properties are out of the implementation's bounds, None is returned. Args: pulse: pulse to be targeted connections: List of all connections in Layout, which might be used by the implementation. Returns: Targeted pulse if it can be implemented. Otherwise None """ for pulse_implementation in self.pulse_implementations: if pulse_implementation.satisfies_requirements(pulse): return pulse_implementation.target_pulse( pulse, interface=self, connections=connections) else: try: pulse_implementation = next( pulse_implementation for pulse_implementation in self.pulse_implementations if pulse_implementation.pulse_class == pulse.__class__) logger.warning(f'Pulse requirements not satisfied.\n' f'Requirements: {pulse_implementation.pulse_requirements}\n' f'Pulse: {repr(pulse)}') except: logger.warning(f'Could not target pulse {repr(pulse)}') return None
[docs] def get_additional_pulses(self, connections) -> List[Pulse]: """Additional pulses needed by instrument after targeting of main pulses Args: connections: List of all connections in the layout Returns: List of additional pulses, empty by default. """ return []
[docs] def initialize(self): """ This method gets called at the start of targeting a pulse sequence Returns: None """ self.pulse_sequence.clear() self.input_pulse_sequence.clear()
[docs] def setup(self, samples: Union[int, None] = None, input_connections: list = [], output_connections: list = [], repeat: bool = True, **kwargs) -> Dict[str, Any]: """Set up instrument after layout has been targeted by pulse sequence. Needs to be implemented in subclass. Args: samples: Number of acquisition samples. If None, it will use the previously set value. input_connections: Input :class:`.Connection` list of instrument, needed by some interfaces to setup the instrument. output_connections: Output :class:`.Connection` list of instrument, needed by some interfaces to setup the instrument. repeat: Repeat the pulse sequence indefinitely. If False, calling :func:`Layout.start` will only run the pulse sequence once. **kwargs: Additional interface-specific kwarg. Returns: setup flags (see :attr:`.Layout.flags`) """ raise NotImplementedError( 'InstrumentInterface.setup should be implemented in a subclass')
[docs] def start(self): """Start instrument Note: Acquisition instruments usually don't need to be started """ raise NotImplementedError( 'InstrumentInterface.start should be implemented in a subclass')
[docs] def stop(self): """Stop instrument""" raise NotImplementedError( 'InstrumentInterface.stop should be implemented in a subclass')