PreSamplex

class samplomatic.pre_samplex.PreSamplex(graph: PyDiGraph[PreNode, PreEdge] | None = None, qubit_map: dict[Qubit, int] | None = None, dangling: dict[int, set[int]] | None = None, optional_dangling: dict[int, set[int]] | None = None, cregs: list[ClassicalRegister] | None = None, pauli_lindblad_map_count: count | None = None, pauli_lindblad_maps: dict[str, int] | None = None, noise_modifiers: dict[str, set[str]] | None = None, basis_changes: dict[str, int] | None = None, twirled_clbits: set[int] | None = None, passthrough_params: list[tuple[int | None, ParameterExpression]] | None = None, forced_copy_node_idxs: set[int] | None = None)[source]

Bases: object

The sampling IR between a boxed-up circuit and a Samplex.

In this IR, as in the lower Samplex, sampling operations are described as a graph. Operations describe actions like sampling from distributions and propagating virtual gates past other gates. However, unlike the lower IR, this IR still maintains the notion of qubit indices, and operations are still declarative rather than procudural.

Parameters:
  • graph – The graph being built.

  • qubit_map – A map from qubits in the source circuit to physical qubit indices.

  • dangling – A map from qubit indices to sets of node indices that are still eligible to, and must, receive edges at a further point in parsing the circuit being built.

  • optional_dangling – A map from qubit indices to sets of node indices that are still eligible to, but don’t have to, receive edges at a further point in parsing the circuit being built.

  • cregs – A list of classical registers in the order that they were added to the circuit.

  • pauli_lindblad_map_count – A count of the total number of Pauli Lindblad maps.

  • pauli_lindblad_maps – A map from unique identifiers of Pauli Lindblad maps to the number of systems the map acts on.

  • basis_changes – A map from unique identifiers of basis changes to the number of subsystems in that basis change.

  • twirled_clbits – A set of all classical bit indices which were previously twirled in the circuit.

  • passthrough_params – List of ParamSpec for parameters which exist in the template but are not generated in the collectors.

  • forced_copy_node_idxs – List of node indices for which copying of registers will be forced. The nodes behave differently for edges with left/right direction. For left direction, the incoming node is checked against forced_copy_node_idxs, while for right direction, the outgoing node is.

Attributes Summary

max_param_idx

The maximum template parameter index over all nodes.

Methods Summary

add_change_basis_node(samplex, ...)

Add basis tranform node to a samplex, mutating it in place.

add_collect(qubits, synth, param_idxs[, ...])

Add or extend a node to collect virtual gates of types allowed by the synth.

add_collect_node(samplex, pre_node_idx, ...)

Add evaluation nodes to a samplex, mutating it in place.

add_collect_z2_to_output_node(samplex, ...)

Add CollectZ2ToOutput to the Samplex graph.

add_combine_node(samplex, pre_node_idx, ...)

Add a node that combines all the predecessor nodes of a given pre-node.

add_dangler(qubit_idxs, node_idx[, dangler_type])

Mark a node as dangling.

add_emit_meas_basis_change(qubits, basis_ref)

Add a node that emits virtual gates left to measure in a basis.

add_emit_noise_left(qubits, noise_ref[, ...])

Add a node that emits virtual gates for noise injection to the left.

add_emit_noise_right(qubits, noise_ref[, ...])

Add a node that emits virtual gates for noise injection to the right.

add_emit_prep_basis_change(qubits, basis_ref)

Add a node that emits virtual gates right to prepare a basis.

add_emit_twirl(qubits, register_type)

Add a node that emits virtual gates left and right of the same type.

add_force_copy_nodes(node_idxs)

Add node indices for which a register will be forced to copy.

add_inject_noise_node(samplex, ...)

Add an inject noise node to a samplex, mutating it in place.

add_propagate(instr, spec)

Add a node that propagates virtual gates through an operation.

add_propagate_node(samplex, ...)

Add evaluation nodes to a samplex, mutating it in place.

add_twirl_sampling_node(samplex, ...)

Add sampling nodes to a samplex, mutating it in place.

add_z2_collect(qubits, clbit_idxs)

Add a node to collect virtual gates to Z2 output.

draw([cols, subgraph_idxs, layout_method])

Draw the graph in this pre-samplex using the plot_graph() method.

enforce_no_propagation(instr)

Make sure the instruction doesn't participate in virtual gate propagation.

finalize()

Finalize the pre-samplex.

find_danglers(match, subsystems)

Look through the dangling nodes and yield matches overlapping on subsystems.

find_then_remove_danglers(match, subsystems)

Extend find_danglers() to also remove dangling nodes.

get_all_danglers()

Return the danglers information

merge_parallel_pre_propagate_nodes()

Merge parallel pre-propagate nodes acting on disjoint subsystems with the same operation.

prune_prenodes_unreachable_from_emission()

Prune all pre-nodes that are unreachable from pre-emit nodes.

qubits_to_indices(qubits)

Convert uniform subsystems on qubits to uniform subsystems on qubit indices.

remap(qubit_map)

Remap the object to a new PreSamplex object.

remove_force_copy_nodes(node_idxs)

Remove node indices for which a register will be forced to copy.

set_all_danglers(dangling, optional_dangling)

Set the danglers information in place

sorted_predecessor_idxs(pre_node_idx, order)

Return the predecessors of a node in edge-sorted order.

subgraphs()

Return a list of disconnected components.

validate_no_rightward_danglers()

Validate that there are no nodes that require termination but are still dangling.

verify_no_twirled_clbits(clbits)

Verify the given classical bits are not twirled, for classical conditions validation.

Attributes Documentation

max_param_idx

The maximum template parameter index over all nodes.

Methods Documentation

add_change_basis_node(samplex: Samplex, pre_basis_idx: int, pre_nodes_to_nodes: dict[int, int], order: dict[int, int], register_names: dict[int, dict[int, str]]) None[source]

Add basis tranform node to a samplex, mutating it in place.

Parameters:
  • samplex – The samplex to add nodes to.

  • pre_basis_idx – The index of the pre-basis node to turn into a basis change node.

  • pre_nodes_to_nodes – A map from pre-node indices to node indices.

  • order – A map from pre-node indices to integers representing their position in a topological sort of the samplex state graph.

  • register_names – A map such that register_names[a][b] is the name of the register implied by the edge (a, b) in the samplex state graph.

add_collect(qubits: Partition[Qubit], synth: Synth, param_idxs: ndarray[tuple[int, int], dtype[int64]], node_idx: int | None = None)[source]

Add or extend a node to collect virtual gates of types allowed by the synth.

If node_idx is None, a new node is added. If node_idx is provided, the PreCollect node at that index will be extended to include the given qubits. Note that the function does not verify the compatibility of the node at node_idx - it is assumed that it’s type is appropriate, and that the synth is the same.

Parameters:
  • qubits – The qubits to collect virtual gates on.

  • synth – The synthesizer to generate gate parameters.

  • param_idxs – The indices of the parameters in the corresponding template.

  • node_idx – The index of the node to be extended to include the provided qubits.

Returns:

The index of the new node in the graph.

add_collect_node(samplex: Samplex, pre_node_idx: int, pre_nodes_to_nodes: dict[int, int], order: dict[int, int], register_names: dict[int, dict[int, str]])[source]

Add evaluation nodes to a samplex, mutating it in place.

Parameters:
  • samplex – The samplex to add nodes to.

  • pre_node_idx – The index of the pre-collect node to turn into a collection node in the samplex.

  • pre_nodes_to_nodes – A map from pre-node indices to node indices.

  • order – A map from pre-node indices to integers representing their position in a topological sort of the pre-samplex graph.

  • register_names – A map such that register_names[a][b] is the name of the register implied by the edge (a, b) in the pre-samplex graph.

add_collect_z2_to_output_node(samplex: Samplex, pre_node_idx: int, pre_nodes_to_nodes: dict[int, int], order: dict[int, int], register_names: dict[int, dict[int, str]])[source]

Add CollectZ2ToOutput to the Samplex graph.

Parameters:
  • samplex – The samplex to add nodes to.

  • pre_node_idx – The index of the PreZ2Collect node to turn into a collection node in the samplex.

  • pre_nodes_to_nodes – A map from pre-node indices to node indices.

  • order – A map from pre-node indices to integers representing their position in a topological sort of the pre-samplex graph.

  • register_names – A map such that register_names[a][b] is the name of the register implied by the edge (a, b) in the pre-samplex graph.

add_combine_node(samplex: Samplex, pre_node_idx: int, pre_nodes_to_nodes: dict[int, int], order: dict[int, int], register_names: dict[int, dict[int, str]], combined_register_name: str, combined_register_type: VirtualType) int[source]

Add a node that combines all the predecessor nodes of a given pre-node.

This function adds a SliceRegisterNode if the given pre-node has a single predecessor, or a CombineRegistersNode if it has multiple predecessors.

Parameters:
  • samplex – The samplex to add nodes to.

  • pre_node_idx – The index of the pre-node whose predecessors are to be combined.

  • pre_nodes_to_nodes – A map from pre-node indices to node indices. The indices of the pre-node’s predecessors must be included.

  • order – A map from pre-node indices to integers representing their position in a topological sort of the pre-samplex graph. The indices of the pre-node and its predecessors must be included.

  • register_names – A map such that register_names[a][b] is the name of the register implied by the edge (a, b) in the pre-samplex graph.

  • combined_register_name – The prefix of the name of the combined node.

  • combined_register_type – The type of register to combine the predecessor registers into.

Returns:

A tuple containing the combine node’s index and the new register name.

add_dangler(qubit_idxs: Iterable[int], node_idx: int, dangler_type: DanglerType = DanglerType.REQUIRED)[source]

Mark a node as dangling.

A dangling node is one that is eligible to be connected to subsequently added nodes.

Parameters:
  • qubit_idxs – The qubits on which the node is dangling.

  • node_idx – The index of the node within the pre-samplex graph.

  • dangler_type – Specifies the behavior of the dangler.

Raises:

SamplexBuildError – If the dangler type is invalid.

add_emit_meas_basis_change(qubits: Partition[Qubit], basis_ref: str) int[source]

Add a node that emits virtual gates left to measure in a basis.

Parameters:
  • qubits – The qubits to emit virtual gates on.

  • basis_ref – Unique identifier of this basis change.

Raises:

SamplexBuildError – If a basis change with the same basis_ref but of different length has already been added.

Returns:

The index of the new node in the graph.

add_emit_noise_left(qubits: Partition[Qubit], noise_ref: str, modifier_ref: str = '') int[source]

Add a node that emits virtual gates for noise injection to the left.

Parameters:
  • qubits – The qubits to emit virtual gates on.

  • noise_ref – Unique identifier of the noise to inject.

  • modifier_ref – Unique identifier for modifiers to apply to this Pauli Lindblad map.

Raises:

SamplexBuildError – If a Pauli Lindblad map with the same noise_ref but of different length has already been added.

Returns:

The index of the new node in the graph.

add_emit_noise_right(qubits: Partition[Qubit], noise_ref: str, modifier_ref: str = '') int[source]

Add a node that emits virtual gates for noise injection to the right.

Parameters:
  • qubits – The qubits to emit virtual gates on.

  • noise_ref – Unique identifier of the noise to inject.

  • modifier_ref – Unique identifier for modifiers to apply to this Pauli Lindblad map.

Raises:

SamplexBuildError – If a Pauli Lindblad map with the same noise_ref but of different length has already been added.

Returns:

The index of the new node in the graph.

add_emit_prep_basis_change(qubits: Partition[Qubit], basis_ref: str) int[source]

Add a node that emits virtual gates right to prepare a basis.

Parameters:
  • qubits – The qubits to emit virtual gates on.

  • basis_ref – Unique identifier of this basis change.

Raises:

SamplexBuildError – If a basis change with the same basis_ref but of different length has already been added.

Returns:

The index of the new node in the graph.

add_emit_twirl(qubits: Partition[Qubit], register_type: VirtualType) int[source]

Add a node that emits virtual gates left and right of the same type.

Parameters:
  • qubits – The qubits to emit virtual gates on.

  • register_type – The type of virtual gate to emit.

Raises:
  • SamplexBuildError – When qubits has overlap with a hanging emit node with a different virtual gate type.

  • SamplexBuildError – When any of the elements of qubits is not dangling.

Returns:

The index of the new node in the graph.

add_force_copy_nodes(node_idxs: Iterable[int])[source]

Add node indices for which a register will be forced to copy.

add_inject_noise_node(samplex: Samplex, pre_inject_idx: int, pre_nodes_to_nodes: dict[int, int], order: dict[int, int], register_names: dict[int, dict[int, str]]) None[source]

Add an inject noise node to a samplex, mutating it in place.

Parameters:
  • samplex – The samplex to add nodes to.

  • pre_inject_idx – The index of the pre-inject noise node to turn into an inject noise node.

  • pre_nodes_to_nodes – A map from pre-node indices to node indices.

  • order – A map from pre-node indices to integers representing their position in a topological sort of the samplex state graph.

  • register_names – A map such that register_names[a][b] is the name of the register implied by the edge (a, b) in the samplex state graph.

add_propagate(instr: CircuitInstruction, spec: InstructionSpec)[source]

Add a node that propagates virtual gates through an operation.

This method deduces which direction to propagate virtual gates by inspecting the previous nodes on the dangling qubits that overlap with the instruction’s qubits.

Parameters:
  • instr – The circuit instruction to propagate through.

  • spec – The specification for how to propagate with the instruction.

Raises:

SamplexBuildError – If the qubits of instr have partial overlap with dangling qubits of the pre-samplex.

Returns:

The index of the new node in the graph.

add_propagate_node(samplex: Samplex, pre_propagate_idx: int, pre_nodes_to_nodes: dict[int, int], order: dict[int, int], register_names: dict[int, dict[int, str]])[source]

Add evaluation nodes to a samplex, mutating it in place.

Parameters:
  • samplex – The samplex to add nodes to.

  • pre_propagate_idx – The index of the pre-propagate node to turn into nodes in the samplex.

  • pre_nodes_to_nodes – A map from pre-node indices to node indices.

  • order – A map from pre-node indices to integers representing their position in a topological sort of the pre-samplex graph.

  • register_names – A map such that register_names[a][b] is the name of the register implied by the edge (a, b) in the pre-samplex graph.

Raises:

SamplexBuildError – If a pre-propagate contains an unsupported operation.

add_twirl_sampling_node(samplex: Samplex, pre_emit_idx: int, pre_nodes_to_nodes: dict[int, int], order: dict[int, int], register_names: dict[int, dict[int, str]])[source]

Add sampling nodes to a samplex, mutating it in place.

Parameters:
  • samplex – The samplex to add nodes to.

  • pre_emit_idx – The index of the pre-emit node to turn into a twirl sampling node.

  • pre_nodes_to_nodes – A map from pre-node indices to node indices.

  • order – A map from pre-node indices to integers representing their position in a topological sort of the pre-samplex graph.

  • register_names – A map such that register_names[a][b] is the name of the register implied by the edge (a, b) in the pre-samplex graph.

add_z2_collect(qubits: Partition[Qubit], clbit_idxs: Sequence[int]) int[source]

Add a node to collect virtual gates to Z2 output.

Parameters:
  • qubits – The qubits to collect virtual gates on.

  • clbit_idxs – The indices of the clbits measured to (in the same order as qubits).

Raises:
  • SamplexBuildError if number of qubits doesn't match number of clbits

  • SamplexBuildError if not all measured qubits receive emissions

Returns:

The index of the new node in the graph.

draw(cols: int = 2, subgraph_idxs: None | int | Sequence[int] = None, layout_method: LayoutPresets | LayoutMethod = 'auto') Figure[source]

Draw the graph in this pre-samplex using the plot_graph() method.

Parameters:
  • cols – The number of columns in the returned figure.

  • subgraph_idxs – The indices of the subgraphs to include in the plot, or None to include all of the subgraphs.

  • layout_method – A predefined layout method by name, or a callable implementing a layout.

Returns:

A Plotly figure.

enforce_no_propagation(instr: CircuitInstruction)[source]

Make sure the instruction doesn’t participate in virtual gate propagation.

We check to see if there are left-to-right danglers, and error if they exist. We remove right-to-left danglers, which will cause an error later when an emission can’t reach a collection on its left.

Parameters:

instr – The circuit instruction that can’t be propagated through.

Raises:

SamplexBuildError – If instr involves active left-to-right danglers.

finalize()[source]

Finalize the pre-samplex.

Raises:

SamplexBuildError – If there are any emission nodes that are not collected.

find_danglers(match: DanglerMatch, subsystems: Partition[int]) Iterator[tuple[int, Partition[int]]][source]

Look through the dangling nodes and yield matches overlapping on subsystems.

Note

The match function is only called on those nodes that overlap the given subsystems.

Parameters:
  • match – A DanglerMatch object specifying the conditions on the danglers.

  • subsystems – Subsystems of the virtual registers we are interested in.

Yields:

Pairs node_idx, intersecting_subsystems representing a matching node along with the subsystems on which it overlaps with the provided subsystems.

find_then_remove_danglers(match: DanglerMatch, subsystems: Partition[int]) Iterator[tuple[int, Partition[int]]][source]

Extend find_danglers() to also remove dangling nodes.

Note

Nodes are removed only after all of them have been yielded.

Parameters:
  • match – A DanglerMatch object specifying the conditions on the danglers.

  • subsystems – Subsystems of the virtual registers we are interested in.

Yields:

Pairs node_idx, intersecting_subsystems representing a matching node along with the subsystems on which it overlaps with the provided subsystems.

get_all_danglers() tuple[dict[int, set[int]], dict[int, set[int]]][source]

Return the danglers information

merge_parallel_pre_propagate_nodes()[source]

Merge parallel pre-propagate nodes acting on disjoint subsystems with the same operation.

Note

Given a list of topological generations of the nodes in a graph, a gate acting in parallel on \(N\) disjoint subsystems appears as \(N\) pre-propagate nodes that are part of the same generation, and have identical directions and operation names, and have predecessors in common.

prune_prenodes_unreachable_from_emission()[source]

Prune all pre-nodes that are unreachable from pre-emit nodes.

qubits_to_indices(qubits: Partition[Qubit]) Partition[int][source]

Convert uniform subsystems on qubits to uniform subsystems on qubit indices.

remap(qubit_map: dict[Qubit, int]) PreSamplex[source]

Remap the object to a new PreSamplex object.

Parameters:

qubit_map – A new map from qubits in the source circuit to physical qubit indices.

Returns:

A PreSamplex object which is identical, except for qubit_map.

remove_force_copy_nodes(node_idxs: Iterable[int])[source]

Remove node indices for which a register will be forced to copy.

set_all_danglers(dangling: dict[int, set[int]], optional_dangling: dict[int, set[int]])[source]

Set the danglers information in place

sorted_predecessor_idxs(pre_node_idx: int, order: dict[int, int]) list[int][source]

Return the predecessors of a node in edge-sorted order.

Edge-sorted order
  • places all nodes connected by LEFT edges before RIGHT edges, and

  • the LEFT edges connected to twirl emissions before other emissions,
    • the twirl emissions sorted according to reverse order,

    • the others according to order,

  • while the RIGHT place twirl emissions after other emissions, both individually sorted according to order.

This order scheme is designed so that it corresponds to circuit-temporal precedence of predecessors. For example, a pre-collector will have inbound edges marked both left and right. We need to know in which order to multiply them together. This method is that order.

Parameters:
  • pre_node_idx – The pre-node to get the predecessors of.

  • order – A dictionary specifying an integer for at least the predecessors of pre_node_idx. These integers are referenced in the second two bullets above.

Returns:

The predecessors of pre_node_idx in edge-sorted order.

subgraphs() list[PyDiGraph[PreNode, PreEdge]][source]

Return a list of disconnected components.

validate_no_rightward_danglers()[source]

Validate that there are no nodes that require termination but are still dangling.

Optional danglers are ignored.

Raises:

SamplexBuildError – If any nodes are expecting collections.

verify_no_twirled_clbits(clbits: list[int])[source]

Verify the given classical bits are not twirled, for classical conditions validation.

Parameters:

clbits – list of classical bits indices.

Raises:

SamplexBuildError – if any classical bit is twirled.