semi_cr.core.station package

Subpackages

Submodules

semi_cr.core.station.chip module

This module declares a Chip class representing a quantum chip and its associated devices. For now, it’s deserializable from the QCoDes YAML format, but in the future it should hold more chip-related information.

Example of a chip configuration in YAML format:

instruments:
  X002001C1:
    type: semi_cr.core.lab.instrument.chip.Chip
    init:
      devices:
        D1:
          type: semi_cr.core.lab.devices.multiplexer.Multiplexer
          pins:
            T2:
              type: Gate
              pads:
                - QboardII[8]
            T4:
              type: Gate
              pads:
                - QboardII[17]
            B1:
              type: Gate
              pads:
                - QboardII[38]
            B4:
              type: Gate
              pads:
                - QboardII[32]
            RF_In:
              type: RFInput
              pads:
                - QboardII[RF1]
            RF_Out_1:
              type: RFOutput
              pads:
                - QboardII[RF4]
            RF_Out_2:
              type: RFOutput
              pads:
                - QboardII[RF8]
            RF_Out_3:
              type: RFOutput
              pads:
                - QboardII[RF10]
            RF_Out_4:
              type: RFOutput
              pads:
                - QboardII[RF12]
class semi_cr.core.station.chip.Chip(chip_id: 'str', parent_ref: 'NQCPStation', devices: 'list[QuantumDevice]' = <factory>)[source]

Bases: object

chip_id: str
parent_ref: NQCPStation
devices: list[QuantumDevice]
device(device_id: str) → QuantumDevice[source]

Gets a device by name.

Returns:

The device matching device_id.

Raises:

KeyError – If no matching device exists.

classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: NQCPStation) → Chip[source]

Given the parsed yaml of a chip, return a Chip object. Recursively creates QuantumDevice objects.

spec is a tuple: - First element is the identifier of the Chip - Second element is the QCoDes compatible YAML structure underneath it

graph_nodes() → Iterable[GraphNode][source]

Iterates over all devices to return the graph nodes for this chip.

graph_edges() → Iterable[GraphEdge][source]

Iterates over all devices to return any graph edges for this chip.

to_quam() → BaseQuamQD[source]

Exports a representation of the current chip for use with quam-builder, such as the gate tuning web app Torbjørn presented. At the meeting on 2026-09-28 we decided that it should live on our Chip representation, not device or station.

Returns:

A BaseQuamQD representation of the current chip.

semi_cr.core.station.device module

class semi_cr.core.station.device.DevicePin(name: 'str', pin_type: 'str', config: 'Mapping[str, Any]', parent_ref: 'QuantumDevice')[source]

Bases: object

name: str
pin_type: str
config: Mapping[str, Any]
parent_ref: QuantumDevice
property node_id: str
property station
get_ac_driver()[source]
get_dc_driver()[source]
classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: QuantumDevice) → DevicePin[source]

Instantiates a DevicePin (or subclass thereof) depending on the spec. Currently, the specification uses the type property to denote the pin type. When hydrating we’d like to have that reflected in what object is instantiated. So the logic is this:

  1. Extract the pin type

  2. Look up what class is appropriate for that type

  3. Extract that class with the spec, ensure it points back to the QuantumDevice containing it, and return.

graph_nodes() → Iterable[GraphNode][source]

Returns the graph nodes corresponding to this pin.

graph_edges() → Iterable[GraphEdge][source]

Returns graph edges from declared pads to this pin.

class semi_cr.core.station.device.Gate(name: 'str', pin_type: 'str', config: 'Mapping[str, Any]', parent_ref: 'QuantumDevice', role: 'Role | None' = None)[source]

Bases: DevicePin

class Role(*values)[source]

Bases: Enum

Plunger = 1
Barrier = 2
Screening = 3
Cutter = 4
classmethod __contains__(value)

Return True if value is in cls.

value is in cls if: 1) value is a member of cls, or 2) value is the value of one of the cls’s members.

classmethod __getitem__(name)

Return the member matching name.

classmethod __iter__()

Return members in definition order.

classmethod __len__()

Return the number of members (no aliases)

role: Role | None = None
classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: QuantumDevice) → DevicePin

Instantiates a DevicePin (or subclass thereof) depending on the spec. Currently, the specification uses the type property to denote the pin type. When hydrating we’d like to have that reflected in what object is instantiated. So the logic is this:

  1. Extract the pin type

  2. Look up what class is appropriate for that type

  3. Extract that class with the spec, ensure it points back to the QuantumDevice containing it, and return.

get_ac_driver()
get_dc_driver()
graph_edges() → Iterable[GraphEdge]

Returns graph edges from declared pads to this pin.

graph_nodes() → Iterable[GraphNode]

Returns the graph nodes corresponding to this pin.

property node_id: str
property station
name
pin_type
config
parent_ref
class semi_cr.core.station.device.Ohmic(name: 'str', pin_type: 'str', config: 'Mapping[str, Any]', parent_ref: 'QuantumDevice', role: 'Role | None' = None)[source]

Bases: DevicePin

class Role(*values)[source]

Bases: Enum

Source = 1
Drain = 2
classmethod __contains__(value)

Return True if value is in cls.

value is in cls if: 1) value is a member of cls, or 2) value is the value of one of the cls’s members.

classmethod __getitem__(name)

Return the member matching name.

classmethod __iter__()

Return members in definition order.

classmethod __len__()

Return the number of members (no aliases)

role: Role | None = None
classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: QuantumDevice) → DevicePin

Instantiates a DevicePin (or subclass thereof) depending on the spec. Currently, the specification uses the type property to denote the pin type. When hydrating we’d like to have that reflected in what object is instantiated. So the logic is this:

  1. Extract the pin type

  2. Look up what class is appropriate for that type

  3. Extract that class with the spec, ensure it points back to the QuantumDevice containing it, and return.

get_ac_driver()
get_dc_driver()
graph_edges() → Iterable[GraphEdge]

Returns graph edges from declared pads to this pin.

graph_nodes() → Iterable[GraphNode]

Returns the graph nodes corresponding to this pin.

property node_id: str
property station
name
pin_type
config
parent_ref
class semi_cr.core.station.device.RFPin(name: 'str', pin_type: 'str', config: 'Mapping[str, Any]', parent_ref: 'QuantumDevice', role: 'Role | None' = None)[source]

Bases: DevicePin

class Role(*values)[source]

Bases: Enum

RFInput = 1
RFOutput = 2
classmethod __contains__(value)

Return True if value is in cls.

value is in cls if: 1) value is a member of cls, or 2) value is the value of one of the cls’s members.

classmethod __getitem__(name)

Return the member matching name.

classmethod __iter__()

Return members in definition order.

classmethod __len__()

Return the number of members (no aliases)

role: Role | None = None
classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: QuantumDevice) → DevicePin

Instantiates a DevicePin (or subclass thereof) depending on the spec. Currently, the specification uses the type property to denote the pin type. When hydrating we’d like to have that reflected in what object is instantiated. So the logic is this:

  1. Extract the pin type

  2. Look up what class is appropriate for that type

  3. Extract that class with the spec, ensure it points back to the QuantumDevice containing it, and return.

get_ac_driver()
get_dc_driver()
graph_edges() → Iterable[GraphEdge]

Returns graph edges from declared pads to this pin.

graph_nodes() → Iterable[GraphNode]

Returns the graph nodes corresponding to this pin.

property node_id: str
property station
name
pin_type
config
parent_ref
class semi_cr.core.station.device.ReadoutPath(drive: OPX1000LFTerminal, acquire: OPX1000LFTerminal, drive_root: DevicePin, acquire_root: DevicePin)[source]

Bases: object

OPX terminals used to drive and acquire a measurement profile.

The path is derived from a MeasurementProfile and the station graph. It stores terminal objects rather than raw node IDs so callers can convert them directly to OPX references.

drive: OPX1000LFTerminal
acquire: OPX1000LFTerminal
drive_root: DevicePin
acquire_root: DevicePin
class semi_cr.core.station.device.MeasurementMethod(name: str, method_type: str, roles: Mapping[str, Any], config: Mapping[str, Any], parent_ref: MeasurementProfile)[source]

Bases: object

Measurement method declared inside a MeasurementProfile.

A method gives semantic roles to the profile’s pins for one concrete measurement procedure. For example, an S11-like method may use the same pin for rf_source and rf_receiver, while an S21-like method may receive on a different pin.

name: str
method_type: str
roles: Mapping[str, Any]
config: Mapping[str, Any]
parent_ref: MeasurementProfile
classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: MeasurementProfile) → MeasurementMethod[source]

Hydrate a measurement method from a profile config fragment.

Parameters:
  • spec – (method_name, method_config) from the profile’s methods section.

  • parent_ref – The owning measurement profile.

Returns:

A method with role names resolved to hydrated DevicePin objects.

Raises:

KeyError – If a role references a profile pin that does not exist.

property available_roles: list[str]

Semantic role names declared by this method.

role(role_name: str) → Any[source]

Return the hydrated value assigned to role_name in this method.

Role values mirror the config shape, but profile pin references are replaced with hydrated DevicePin objects. A role may therefore be a pin, a list of pins, or a nested mapping containing pins and scalar values.

Parameters:

role_name – Semantic method role name, such as rf_source.

Returns:

The hydrated role value.

Raises:

KeyError – If the method has no role with this name.

get_readout_path(source_role: str = 'rf_source', receiver_role: str = 'rf_receiver') → ReadoutPath[source]

Return the OPX readout path for this measurement method.

Parameters:
  • source_role – Method role that identifies the source pin.

  • receiver_role – Method role that identifies the receiver pin.

Returns:

The OPX drive/acquire terminal pair for this method.

Raises:
  • KeyError – If either role does not exist.

  • LookupError – If either OPX terminal cannot be found.

is_applicable() → bool[source]

Return whether this method’s readout path is resolvable in the current station graph.

semi_cr.core.station.device.hydrate_measurement_role(profile: MeasurementProfile, role_spec: Any) → Any[source]

Hydrate a measurement-method role value.

Strings that match profile pin names are resolved to DevicePin objects. Other strings are kept as scalar values. Lists and mappings are hydrated recursively so nested role structures keep their original shape.

class semi_cr.core.station.device.MeasurementProfile(name: str, profile_type: str, pins: Mapping[str, DevicePin], methods: list[MeasurementMethod], config: Mapping[str, Any], parent_ref: QuantumDevice)[source]

Bases: object

Measurement profile declared on a QuantumDevice.

A profile gives semantic names to device pins for a measurement mode. For example, an RF profile may map rf_in to the device’s RF_IN pin and rf_out to RF_OUT. The profile stores direct references to the hydrated DevicePin objects so downstream code does not need to re-resolve config strings.

name: str
profile_type: str
pins: Mapping[str, DevicePin]
methods: list[MeasurementMethod]
config: Mapping[str, Any]
parent_ref: QuantumDevice
classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: QuantumDevice) → MeasurementProfile[source]

Hydrate a measurement profile from a device config fragment.

Parameters:
  • spec – (profile_name, profile_config) from the device’s measurement_profiles section.

  • parent_ref – The owning quantum device.

Returns:

A measurement profile with profile pin names resolved to hydrated DevicePin objects.

Raises:

KeyError – If a profile references a device pin that does not exist on parent_ref.

property available_pins: list[str]

Semantic pin names declared by this profile.

property available_methods: list[str]

Names of measurement methods declared by this profile.

pin(pin_name: str) → DevicePin[source]

Return the device pin assigned to pin_name in this profile.

Parameters:

pin_name – Semantic pin name inside the profile, such as rf_in.

Returns:

The hydrated DevicePin referenced by the profile.

Raises:

KeyError – If the profile has no pin with this name.

method(method_name: str) → MeasurementMethod[source]

Return a measurement method by name.

Parameters:

method_name – Name of the method, such as vna_s11.

Returns:

The matching MeasurementMethod.

Raises:

KeyError – If no method with this name exists.

get_readout_path(method_name: str, source_role: str = 'rf_source', receiver_role: str = 'rf_receiver') → ReadoutPath[source]

Return the OPX readout path for a measurement method.

Parameters:
  • method_name – Name of the measurement method, such as vna_s11.

  • source_role – Method role that identifies the source profile pin.

  • receiver_role – Method role that identifies the receiver profile pin.

Returns:

The OPX drive/acquire terminal pair for this method.

Raises:
  • KeyError – If the method or roles do not exist.

  • LookupError – If either OPX terminal cannot be found.

class semi_cr.core.station.device.QuantumDevice(device_id: 'str', qcodes_type: 'str', parent_ref: 'Chip', pins: 'list[DevicePin]' = <factory>, measurement_profiles: 'list[MeasurementProfile]' = <factory>)[source]

Bases: object

device_id: str
qcodes_type: str
parent_ref: Chip
pins: list[DevicePin]
measurement_profiles: list[MeasurementProfile]
gate(gate_id: str) → Gate[source]

Return a gate pin by name.

Parameters:

gate_id – Name of the gate pin.

Returns:

The matching Gate.

Raises:

ValueError – If no gate with this name exists.

pin(pin_id: str) → DevicePin[source]

Return a device pin by name.

Parameters:

pin_id – Name of the pin.

Returns:

The matching DevicePin or subclass.

Raises:

KeyError – If no pin with this name exists.

property available_measurement_profiles: list[str]

Names of measurement profiles declared on this device.

measurement_profile(profile_id: str) → MeasurementProfile[source]

Return a measurement profile by name.

Parameters:

profile_id – Name of the profile, such as rf.

Returns:

The matching MeasurementProfile.

Raises:

KeyError – If no profile with this name exists.

property gates: list[Gate]

Returns the list of Gates for a QuantumDevice. Gates are a special case of DevicePin so we check if members of the pins list are in fact subclasses of Gate.

classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: Chip) → QuantumDevice[source]

Quantum devices are described in the QCoDeS configuration schema in the same way whether they are chips of our own design, or special-case things like for instance a tank inductor. In the general case we inflate the devices such that all pins become graph nodes that are responsible for their own edge relations (if any).

Special cases like the TankInductor are registered in DEVICE_CLASS_BY_TYPE if they require special treatment during hydration.

Returns:

A QuantumDevice instance containing references to a number of DevicePin instances.

graph_nodes() → Iterable[GraphNode][source]

Iterates over all pins to return the graph nodes for this device.

graph_edges() → Iterable[GraphEdge][source]

Iterates over all pins to return any graph edges for this device.

class semi_cr.core.station.device.TankInductor(device_id: str, qcodes_type: str, parent_ref: Chip, pins: list[DevicePin] = <factory>, measurement_profiles: list[MeasurementProfile] = <factory>)[source]

Bases: QuantumDevice

A special case of a QuantumDevice including edges between the graph nodes on its periphery, to model its actual conductance.

graph_edges() → Iterable[GraphEdge][source]

Iterates over all pins to return any graph edges for this device.

property available_measurement_profiles: list[str]

Names of measurement profiles declared on this device.

classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec, parent_ref: Chip) → QuantumDevice

Quantum devices are described in the QCoDeS configuration schema in the same way whether they are chips of our own design, or special-case things like for instance a tank inductor. In the general case we inflate the devices such that all pins become graph nodes that are responsible for their own edge relations (if any).

Special cases like the TankInductor are registered in DEVICE_CLASS_BY_TYPE if they require special treatment during hydration.

Returns:

A QuantumDevice instance containing references to a number of DevicePin instances.

gate(gate_id: str) → Gate

Return a gate pin by name.

Parameters:

gate_id – Name of the gate pin.

Returns:

The matching Gate.

Raises:

ValueError – If no gate with this name exists.

property gates: list[Gate]

Returns the list of Gates for a QuantumDevice. Gates are a special case of DevicePin so we check if members of the pins list are in fact subclasses of Gate.

graph_nodes() → Iterable[GraphNode]

Iterates over all pins to return the graph nodes for this device.

measurement_profile(profile_id: str) → MeasurementProfile

Return a measurement profile by name.

Parameters:

profile_id – Name of the profile, such as rf.

Returns:

The matching MeasurementProfile.

Raises:

KeyError – If no profile with this name exists.

pin(pin_id: str) → DevicePin

Return a device pin by name.

Parameters:

pin_id – Name of the pin.

Returns:

The matching DevicePin or subclass.

Raises:

KeyError – If no pin with this name exists.

device_id
qcodes_type
parent_ref
pins
measurement_profiles

semi_cr.core.station.drivers module

class semi_cr.core.station.drivers.DCDriver[source]

Bases: ABC

abstractmethod set_voltage(voltage: VoltageLike) → None[source]

Set the voltage of the DC driver.

get_voltage() → VoltageLike[source]

Get the current set voltage of the DC driver. This is a readback of a DAC, not a measurement.

get_voltage_parameter(name: str = 'voltage') → qcodes.Parameter[source]

Convenience method to allow QCoDeS control of the voltage on this terminal.

Parameters:

name – QCoDeS parameter name; must be a valid identifier.

semi_cr.core.station.graph module

semi_cr.core.station.graph.graph_attributes(config: Mapping[str, Any]) → dict[str, Any][source]

Return the explicit graph metadata declared in a YAML config section.

Configuration keys are intentionally not copied wholesale: only values under attributes become graph metadata. This keeps constructor settings such as type and slot out of the graph namespace.

class semi_cr.core.station.graph.GraphNode(id: 'str', obj: 'object', kind: 'str', attrs: 'Mapping[str, Any]'=<factory>)[source]

Bases: object

id: str
obj: object
kind: str
attrs: Mapping[str, Any]
class semi_cr.core.station.graph.Conduction(*values)[source]

Bases: Flag

DC = 1
AC = 2
__contains__(other)

Returns True if self has at least the same flags set as other.

__iter__()

Returns flags in definition order.

classmethod __getitem__(name)

Return the member matching name.

class semi_cr.core.station.graph.GraphEdge(id: 'str', source: 'str', target: 'str', kind: 'str', attrs: 'Mapping[str, Any]'=<factory>, conducts: 'Conduction' = <Conduction.DC|AC: 3>)[source]

Bases: object

id: str
source: str
target: str
kind: str
attrs: Mapping[str, Any]
conducts: Conduction = 3
class semi_cr.core.station.graph.Routable(*args, **kwargs)[source]

Bases: Protocol

Use this protocol for anything that can add nodes or edges to the station graph. The idea is that devices export themselves as nodes at least. If they have internal edges that need to exist for the routing to work, they can supply those, too.

graph_nodes() → Iterable[GraphNode][source]
graph_edges() → Iterable[GraphEdge][source]
class semi_cr.core.station.graph.Connector(name: str, connections: ~collections.abc.Mapping[str, list[str]], connection_attributes: ~collections.abc.Mapping[str, ~collections.abc.Mapping[str, ~typing.Any]] = <factory>)[source]

Bases: object

Implements a non-QCoDeS variant of semi_cr.core.lab.instrument.connector.Connector, a class representing a junction or set of junctions between two instruments in our setup.

The class is thought to be instantiated using the from_instrument_spec class method, and exposes the Routable protocol.

name: str
connections: Mapping[str, list[str]]
connection_attributes: Mapping[str, Mapping[str, Any]]
graph_nodes() → Iterable[GraphNode][source]

Returns graph nodes for this connector’s terminals.

graph_edges() → Iterable[GraphEdge][source]

Emits graph edges from the connections stored on the Connector element.

classmethod from_instrument_spec(spec: QCoDeSInstrumentSpec) → Connector[source]
semi_cr.core.station.graph.build_node_aliases(graph: Graph) → dict[str, str][source]

In some config references there seems to be a convention that e.g. tank inductors can be referred to by concatenating the chip ID with the device ID, such as B083004D010033 where B083004D01 is the chip part and 0033 is the device part. When configurations refer to endpoints like this the graph resolver gets confused. So for each device pin we add an alias that we can use when looking up the node to make edges later on.

semi_cr.core.station.graph.resolve_endpoint_reference(reference: str, aliases: Mapping[str, str]) → str[source]

Resolve an edge endpoint reference to a canonical graph node ID.

Config strings like foo[bar] are ambiguous: they may refer to a modeled device pin, or to a wiring terminal. Device-pin aliases are tried first. If no alias matches, the string is treated as a terminal reference. Existing lab:// node IDs are already canonical and are returned unchanged.

class semi_cr.core.station.graph.StationGraph(graph: 'nx.Graph' = <factory>, aliases: 'dict[str, str]'=<factory>)[source]

Bases: object

graph: Graph
aliases: dict[str, str]
opx_source_for_pin(pin: DevicePin) → object | None[source]

Return the OPX source terminal reachable from a device pin.

Parameters:

pin – Device pin object with a node_id.

Returns:

The first reachable OPX source terminal, or None.

opx_receiver_for_pin(pin: DevicePin) → object | None[source]

Return the OPX receiver terminal reachable from a device pin.

Parameters:

pin – Device pin object with a node_id.

Returns:

The first reachable OPX receiver terminal, or None.

opx_terminal_for_pin(pin: DevicePin, role: object) → object | None[source]

Return the first OPX terminal with role reachable from a device pin.

Driver lookup is rooted at the pin and walks the station topology outward. Matching is based on the graph node’s obj relation, not graph metadata.

Parameters:
  • pin – Device pin object with a node_id.

  • role – OPX1000LFTerminal.Role member to match.

Returns:

The first matching OPX terminal object, or None.

dc_driver_for_pin(pin: DevicePin) → DCDriver | None[source]

Gets the DC driver for a given pin.

It does this by extracting the subgraph that only contains DC-carrying edges, and find a path in that one from the pin to any DCDriver.

reachable_from_pin(pin: DevicePin) → Iterator[object][source]
reachable_from_pin(pin: DevicePin, object_type: type[T], where: Callable[[T], bool] | None = None) → Iterator[T]

Iterate over matching hydrated objects reachable from a device pin.

This returns values stored in graph-node obj metadata, not NetworkX node IDs or node metadata. The starting pin and nodes without an obj value are excluded. Omitting object_type returns all other reachable hydrated objects.

Parameters:
  • pin – Device pin from which to start the graph traversal.

  • object_type – Type of object to return. Defaults to object.

  • where – Optional predicate applied to objects of object_type.

Returns:

Matching objects in breadth-first traversal order.

classmethod from_routable(routable: Routable) → StationGraph[source]

Creates a station graph from a Routable object, usually NQCPStation.

Ordering matters: all nodes are created before edges. Edge endpoints may use different names for the same node, so we need the full node set before we can resolve those names to canonical node IDs.

For example, a device pin may be referred to as “B083004D010033[RF_IN]” in one place and “B083004D01/0033[RF_IN]” in another. Before adding the edges exported by the Routable, we build aliases for all device pins and use them to resolve edge sources and targets.

conducting(signal: Conduction) → StationGraph[source]

Returns a subgraph of the station graph containing only the edges that conduct the specified signal type.

semi_cr.core.station.helpers module

semi_cr.core.station.helpers.print_station_map(station: NQCPStation)[source]

semi_cr.core.station.hydration module

Hydration module for the NQCP station configuration. Hydration is what turns our configuration into more “live”-like objects, such as Chip and Connector. It is akin to the QCoDeS way of creating a rich object from the configuration, only here we declare a _handler_ per type, treating the types as regular strings instead of the QCoDeS way of pointing to some class. That allows the two approaches to coexist.

class semi_cr.core.station.hydration.UnloadedSpec(name: 'str', type: 'str', config: 'Mapping[str, Any]')[source]

Bases: object

name: str
type: str
config: Mapping[str, Any]
class semi_cr.core.station.hydration.HydratedStationObjects(chips: 'list[Chip]' = <factory>, connectors: 'list[Connector]' = <factory>, instruments: 'list[LabInstrument]' = <factory>, unloaded_specs: 'list[UnloadedSpec]' = <factory>)[source]

Bases: object

chips: list[Chip]
connectors: list[Connector]
instruments: list[LabInstrument]
unloaded_specs: list[UnloadedSpec]
exception semi_cr.core.station.hydration.MissingStaticLoaderError[source]

Bases: Exception

add_note()

Exception.add_note(note) – add a note to the exception

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

semi_cr.core.station.hydration.load_chip(spec: InstrumentSpec, station: NQCPStation) → Chip[source]
semi_cr.core.station.hydration.load_connector(spec: InstrumentSpec, station: NQCPStation) → Connector[source]
semi_cr.core.station.hydration.load_opx1000(spec: InstrumentSpec, station: NQCPStation) → OPX1000Blueprint[source]
semi_cr.core.station.hydration.load_spirack(spec: InstrumentSpec, station: NQCPStation) → SPIRackBlueprint[source]
semi_cr.core.station.hydration.load_dmm(spec: InstrumentSpec, station: NQCPStation) → DMMBlueprint[source]
semi_cr.core.station.hydration.hydrate_station_config(config_bundle: ConfigBundle, station: NQCPStation) → HydratedStationObjects[source]
semi_cr.core.station.hydration.hydrate_instrument_spec(spec: InstrumentSpec, station: NQCPStation) → object[source]

semi_cr.core.station.lifecycle module

Independent, retryable attachment state for live laboratory instruments.

class semi_cr.core.station.lifecycle.AttachmentState(*values)[source]

Bases: StrEnum

DETACHED = 'detached'
ATTACHING = 'attaching'
ATTACHED = 'attached'
DETACHING = 'detaching'
FAILED = 'failed'
__new_member__(*values)

values must already be of type str

encode(encoding='utf-8', errors='strict')

Encode the string using the codec registered for encoding.

encoding

The encoding in which to encode the string.

errors

The error handling scheme to use for encoding errors. The default is ‘strict’ meaning that encoding errors raise a UnicodeEncodeError. Other possible values are ‘ignore’, ‘replace’ and ‘xmlcharrefreplace’ as well as any other name registered with codecs.register_error that can handle UnicodeEncodeErrors.

replace(old, new, count=-1, /)

Return a copy with all occurrences of substring old replaced by new.

count

Maximum number of occurrences to replace. -1 (the default value) means replace all occurrences.

If the optional argument count is given, only the first count occurrences are replaced.

split(sep=None, maxsplit=-1)

Return a list of the substrings in the string, using sep as the separator string.

sep

The separator used to split the string.

When set to None (the default value), will split on any whitespace character (including n r t f and spaces) and will discard empty strings from the result.

maxsplit

Maximum number of splits. -1 (the default value) means no limit.

Splitting starts at the front of the string and works to the end.

Note, str.split() is mainly useful for data that has been intentionally delimited. With natural text that includes punctuation, consider using the regular expression module.

rsplit(sep=None, maxsplit=-1)

Return a list of the substrings in the string, using sep as the separator string.

sep

The separator used to split the string.

When set to None (the default value), will split on any whitespace character (including n r t f and spaces) and will discard empty strings from the result.

maxsplit

Maximum number of splits. -1 (the default value) means no limit.

Splitting starts at the end of the string and works to the front.

join(iterable, /)

Concatenate any number of strings.

The string whose method is called is inserted in between each given string. The result is returned as a new string.

Example: ‘.’.join([‘ab’, ‘pq’, ‘rs’]) -> ‘ab.pq.rs’

capitalize()

Return a capitalized version of the string.

More specifically, make the first character have upper case and the rest lower case.

casefold()

Return a version of the string suitable for caseless comparisons.

title()

Return a version of the string where each word is titlecased.

More specifically, words start with uppercased characters and all remaining cased characters have lower case.

center(width, fillchar=' ', /)

Return a centered string of length width.

Padding is done using the specified fill character (default is a space).

count(sub[, start[, end]]) → int

Return the number of non-overlapping occurrences of substring sub in string S[start:end]. Optional arguments start and end are interpreted as in slice notation.

expandtabs(tabsize=8)

Return a copy where all tab characters are expanded using spaces.

If tabsize is not given, a tab size of 8 characters is assumed.

find(sub[, start[, end]]) → int

Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation.

Return -1 on failure.

partition(sep, /)

Partition the string into three parts using the given separator.

This will search for the separator in the string. If the separator is found, returns a 3-tuple containing the part before the separator, the separator itself, and the part after it.

If the separator is not found, returns a 3-tuple containing the original string and two empty strings.

index(sub[, start[, end]]) → int

Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation.

Raises ValueError when the substring is not found.

ljust(width, fillchar=' ', /)

Return a left-justified string of length width.

Padding is done using the specified fill character (default is a space).

lower()

Return a copy of the string converted to lowercase.

lstrip(chars=None, /)

Return a copy of the string with leading whitespace removed.

If chars is given and not None, remove characters in chars instead.

rfind(sub[, start[, end]]) → int

Return the highest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation.

Return -1 on failure.

rindex(sub[, start[, end]]) → int

Return the highest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation.

Raises ValueError when the substring is not found.

rjust(width, fillchar=' ', /)

Return a right-justified string of length width.

Padding is done using the specified fill character (default is a space).

rstrip(chars=None, /)

Return a copy of the string with trailing whitespace removed.

If chars is given and not None, remove characters in chars instead.

rpartition(sep, /)

Partition the string into three parts using the given separator.

This will search for the separator in the string, starting at the end. If the separator is found, returns a 3-tuple containing the part before the separator, the separator itself, and the part after it.

If the separator is not found, returns a 3-tuple containing two empty strings and the original string.

splitlines(keepends=False)

Return a list of the lines in the string, breaking at line boundaries.

Line breaks are not included in the resulting list unless keepends is given and true.

strip(chars=None, /)

Return a copy of the string with leading and trailing whitespace removed.

If chars is given and not None, remove characters in chars instead.

swapcase()

Convert uppercase characters to lowercase and lowercase characters to uppercase.

translate(table, /)

Replace each character in the string using the given translation table.

table

Translation table, which must be a mapping of Unicode ordinals to Unicode ordinals, strings, or None.

The table must implement lookup/indexing via __getitem__, for instance a dictionary or list. If this operation raises LookupError, the character is left untouched. Characters mapped to None are deleted.

upper()

Return a copy of the string converted to uppercase.

startswith(prefix[, start[, end]]) → bool

Return True if S starts with the specified prefix, False otherwise. With optional start, test S beginning at that position. With optional end, stop comparing S at that position. prefix can also be a tuple of strings to try.

endswith(suffix[, start[, end]]) → bool

Return True if S ends with the specified suffix, False otherwise. With optional start, test S beginning at that position. With optional end, stop comparing S at that position. suffix can also be a tuple of strings to try.

removeprefix(prefix, /)

Return a str with the given prefix string removed if present.

If the string starts with the prefix string, return string[len(prefix):]. Otherwise, return a copy of the original string.

removesuffix(suffix, /)

Return a str with the given suffix string removed if present.

If the string ends with the suffix string and that suffix is not empty, return string[:-len(suffix)]. Otherwise, return a copy of the original string.

isascii()

Return True if all characters in the string are ASCII, False otherwise.

ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.

islower()

Return True if the string is a lowercase string, False otherwise.

A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.

isupper()

Return True if the string is an uppercase string, False otherwise.

A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.

istitle()

Return True if the string is a title-cased string, False otherwise.

In a title-cased string, upper- and title-case characters may only follow uncased characters and lowercase characters only cased ones.

isspace()

Return True if the string is a whitespace string, False otherwise.

A string is whitespace if all characters in the string are whitespace and there is at least one character in the string.

isdecimal()

Return True if the string is a decimal string, False otherwise.

A string is a decimal string if all characters in the string are decimal and there is at least one character in the string.

isdigit()

Return True if the string is a digit string, False otherwise.

A string is a digit string if all characters in the string are digits and there is at least one character in the string.

isnumeric()

Return True if the string is a numeric string, False otherwise.

A string is numeric if all characters in the string are numeric and there is at least one character in the string.

isalpha()

Return True if the string is an alphabetic string, False otherwise.

A string is alphabetic if all characters in the string are alphabetic and there is at least one character in the string.

isalnum()

Return True if the string is an alpha-numeric string, False otherwise.

A string is alpha-numeric if all characters in the string are alpha-numeric and there is at least one character in the string.

isidentifier()

Return True if the string is a valid Python identifier, False otherwise.

Call keyword.iskeyword(s) to test whether string s is a reserved identifier, such as “def” or “class”.

isprintable()

Return True if the string is printable, False otherwise.

A string is printable if all of its characters are considered printable in repr() or if it is empty.

zfill(width, /)

Pad a numeric string with zeros on the left, to fill a field of the given width.

The string is never truncated.

format(*args, **kwargs) → str

Return a formatted version of S, using substitutions from args and kwargs. The substitutions are identified by braces (‘{’ and ‘}’).

format_map(mapping) → str

Return a formatted version of S, using substitutions from mapping. The substitutions are identified by braces (‘{’ and ‘}’).

static maketrans()

Return a translation table usable for str.translate().

If there is only one argument, it must be a dictionary mapping Unicode ordinals (integers) or characters to Unicode ordinals, strings or None. Character keys will be then converted to ordinals. If there are two arguments, they must be strings of equal length, and in the resulting dictionary, each character in x will be mapped to the character at the same position in y. If there is a third argument, it must be a string, whose characters will be mapped to None in the result.

__dir__()

Returns public methods and other interesting attributes.

class semi_cr.core.station.lifecycle.InstrumentSnapshot(name: 'str', state: 'AttachmentState', error: 'str | None', revision: 'int')[source]

Bases: object

name: str
state: AttachmentState
error: str | None
revision: int
class semi_cr.core.station.lifecycle.InstrumentHandle(name: str, attach: Callable[[], ResourceT], detach: Callable[[ResourceT], None])[source]

Bases: Generic

Own one live resource without allowing its failure to escape startup.

Calls can be synchronous for scripts/tests or run on daemon threads for a GUI. A generation counter makes an attach result stale when detach is requested while the connection attempt is still in flight.

property snapshot: InstrumentSnapshot
property resource: ResourceT | None
property has_resource: bool

Whether a resource still needs releasing, including after a failed detach.

require_resource() → ResourceT[source]
attach() → InstrumentSnapshot[source]
detach() → InstrumentSnapshot[source]
attach_async() → bool[source]
detach_async() → bool[source]
mark_failed(error: Exception | str) → InstrumentSnapshot[source]

Invalidate a live resource after a runtime communication failure.

wait(timeout: float | None = None) → None[source]

Wait for workers that exist when this method is called.

semi_cr.core.station.manifest module

class semi_cr.core.station.manifest.NQCPStationManifest(manifest_path: Path)[source]

Bases: object

Helper class for loading a station manifest. The main value proposition is that we don’t leak to the callers how the manifest is encoded (TOML) or where it’s stored.

The TOML manifest station.toml has three sections of interest; wiring, electronics, and device. Each section has a files key that lists the configuration files to load for that section. Besides that it carries a few keys at the top level, like name.

The device section contains a list of files describing individual chips (and the devices on these chips). Moreover it has a loaded_chip_id key that indicates which chip is currently loaded in the station.

manifest_path: Path
basedir: Path
manifest: dict[str, Any]
get_config_bundle() → ConfigBundle[source]
property name: str
property loaded_chip_id: str

In the manifest, the loaded chip ID is stored under the device section. This property provides a convenient way to access it.

semi_cr.core.station.measurement_context module

class semi_cr.core.station.measurement_context.NQCPStationMeasurementContext(station: NQCPStation)[source]

Bases: object

NQCPStation makes an object like this for wrapping a measurement of some type. It connects to the relevant instruments. In the future this would also be the place for some telemetry or automated aggregation of data.

station: NQCPStation
connect(instrument_name: str) → Instrument[source]

Just s stub for now: Connect to a live instrument by name.

This method is intended to be used within the context of a with statement, ensuring that the instrument is properly managed and disconnected when no longer needed.

semi_cr.core.station.plotting module

class semi_cr.core.station.plotting.StationGraphPlotter(station_graph: StationGraph)[source]

Bases: object

Plots a StationGraph in columns: device pins on the left, sources towards the right.

Usage:

StationGraphPlotter(station.graph).plot()
StationGraphPlotter(station.graph).chip_and_drivers().merge_boards().plot()  # debug severed AC/DC paths
station_graph: StationGraph
chip_and_drivers() → StationGraphPlotter[source]

Keep only wiring connected to a device pin or to an AC/DC driver.

A driver whose wiring never reaches the chip ends up in the last column, so a severed path shows as a pin marked missing AC/DC on the left and a dangling driver on the right.

merge_boards() → StationGraphPlotter[source]

Draw each board channel and the connector pins it is traced to as a single row.

A board’s own connectors are recognised by name: fbrd_1_ffc_J2 belongs to board fbrd_1. The traces between a board and its connectors reshuffle line order, which causes crossings no layout can remove, so every set of nodes joined by board traces is contracted into one node. It keeps the board-side node’s ID (and data) and lists the IDs it absorbed under merged.

pin_status(node_id: str) → tuple[bool, bool][source]

Whether a device pin reaches an AC (OPX source) and a DC driver, by the same lookup the pins use.

groups() → dict[str, list[str]][source]

Node IDs grouped by parent, e.g. lab://terminal/opx1000_1/LF_1 or lab://pin/TestChip/D1.

Groups and their members are in _discovery_rank() order.

columns() → list[list[str]][source]

Group names per column, laid out so every wire between groups points from the chip towards the electronics.

Groups are ordered by how close they get to a device pin (in hops), and every wire is directed from the closer group to the farther one. Each group then goes one column right of the furthest group wired into it (longest-path layering), so no wire runs vertically or backwards. Wiring not connected to the chip is ordered by closeness to a driver instead and right-aligned, so a severed path shows as a stub hanging off the electronics side.

crossings(pos: Mapping[str, tuple[float, float]]) → int[source]

Count edge crossings between adjacent columns for a layout. Edges skipping a column are not counted.

layout(sweeps: int = 10) → dict[str, tuple[float, float]][source]

Compute node positions in columns(), with each group drawn as a block of consecutive nodes.

Crossings are reduced with the layered (Sugiyama) barycenter heuristic, applied to groups as blocks: each column is re-sorted by the mean height of its neighbours in the column before it (left-to-right sweep) and then after it (right-to-left sweep). Groups move as a whole, and nodes are re-sorted only inside their group. The layout with the fewest crossings() is returned.

Parameters:

sweeps – Number of left-to-right plus right-to-left sweep pairs.

Returns:

Mapping from node ID to (x, y).

plot() → go.Figure[source]

Draw each parent group as a labelled box, with edges coloured by kind (physical/device).

Hover a node for its full ID and, for device pins, whether it reaches an AC/DC driver. Call .show() on the result to open it in a browser (config={"scrollZoom": True} enables mouse-wheel zoom).

show(path: str | None = None) → str[source]

Write plot() to an HTML file and open it in the browser.

Click a node to highlight all wiring connected to it (e.g. a pin’s path to its drivers) and fade the rest; double-click to reset. Scroll to zoom, drag to pan.

Parameters:

path – Where to write the HTML. Defaults to a temporary file.

Returns:

The path written.

semi_cr.core.station.station module

class semi_cr.core.station.station.NQCPStation(manifest: str | Path | NQCPStationManifest)[source]

Bases: object

Sets up a station handler, given a manifest. In the constructor we establish an object-oriented model of the station constituents, meaning instruments, wiring and the quantum device model as well.

manifest: NQCPStationManifest
config: ConfigBundle
chips: list[Chip]
connectors: list[Connector]
instruments: list[LabInstrument]
unloaded_specs: list[UnloadedSpec]
graph: StationGraph
property available_chips: list[Chip]
property chip: Chip

Returns the configuration of the currently loaded chip

get_chip(chip_id: str | None = None) → Chip[source]

Gets a chip by name.

Returns:

The chip matching chip_id, or the loaded chip when omitted.

Raises:

KeyError – If no matching chip exists.

property device: QuantumDevice
get_device(device_id: str | None = None) → QuantumDevice[source]

Gets a device on the loaded chip.

Returns:

The named device, or the only device on the loaded chip when omitted.

Raises:

ValueError – If device_id is omitted and the loaded chip has multiple devices.

get_gates(device_id: str | None = None) → list[Gate][source]

Gets gates on the station.

Returns:

All gates, or gates on device_id when provided.

get_instruments_of_type(instrument_type: type[StaticInstrumentT], *, name: str | None = None) → list[StaticInstrumentT][source]

Gets static instruments matching instrument_type and optional name.

get_instrument_of_type(instrument_type: type[StaticInstrumentT], *, name: str | None = None) → StaticInstrumentT[source]

Gets exactly one static instrument matching instrument_type and optional name.

Raises:

LookupError – If zero or multiple instruments match.

gate(gate_name: str) → Gate[source]

Gets a gate by name. This is kind of a placeholder method because it kind of glosses over the fact that a station may contain more than one chip with more than one device, and so a gate name can’t be considered a unique property.

Returns:

The gate matching the requested name

Raises:

KeyError if the gate doesn’t exist

Raises:

ValueError if there is more than one gate with that name

classmethod available_stations() → dict[str, NQCPStationManifest][source]

Finds setup manifests under semi_cr.setup.

classmethod get(name: str) → NQCPStation[source]

Load a station description without connecting to hardware.

graph_nodes() → Iterable[GraphNode][source]

Iterates over loaded objects to return graph nodes for this station.

graph_edges() → Iterable[GraphEdge][source]

Iterates over loaded objects to return graph edges for this station.

semi_cr.core.station.units module

type semi_cr.core.station.units.VoltageLike = float | Quantity | str
semi_cr.core.station.units.as_volts(value: VoltageLike) → float[source]

Converts to volts. Strings are parsed by pint, e.g. “20 mV”; bare numbers are taken as volts.