Samplex

class samplomatic.samplex.Samplex[source]

Bases: object

Represents a probability distribution over circuit parameters and other fields.

Samplex is the core type of the samplomatic library. It is a portmanteau of “sampling complex”.

In abstract terms, a samplex represents a parametric probability distribution over the parameters of some template circuit, as well as other array-valued fields to use in post-processing data collected from executing the bound template circuit. In practical terms, it implements a sample() method that draws from this distribution to produce a collection of arrays that include at least an array of (random) angles valid for the template circuit. For example, a samplex instance might implement Pauli twirling on all the layers of some base circuit including final measurements, so that the angles returned by sampling from it correspond to Pauli gates composed with whatever single-qubit gates exist in the base circuit, along with bool arrays that describe how to invert those measurement results flipped by measurement twirling. However, its semantics are not limited to this, and its structure is highly extensible: new group types, new distributions, new virtual gate propagation techniques can be added to the library.

Internally, a samplex uses a directed acyclic graph (DAG) representation that describes the sampling process procedurally. Each node of the graph interacts with a collection of VirtualRegisters, and connections between nodes denote register dependency: if \(A \rightarrow B\), then \(B\) requires \(A\) to have acted on the registers before it is allowed to act. This implies that the flow of the graph is not temporal with respect to execution like the DAG of a quantum circuit, but instead flows from nodes responsible for generating randomizations to nodes responsible for rendering the processed randomizations as outputs. There are three types of nodes:

  • SamplingNodes are responsible for instantiating new registers from inputs,

  • EvaluationNodes are responsible for transforming, combining, and removing registers,

  • CollectionNodes are responsible reading registers and writing to output values of sample().

Samplexes are typically generated by passing a base circuit with annotated box instructions to the build() function. Generating one manually is possible, though tedious, and the user is then also responsible for constructing a compatible parametric quantum circuit to use as the template circuit. The following example shows a simple circuit built into a samplex and template pair using the build() function, and plots the resulting DAG of the samplex. The three different types of nodes described above are denoted by different shapes.

>>> from samplomatic import build, Twirl
>>> from qiskit import QuantumCircuit
>>>
>>> circuit = QuantumCircuit(2)
>>> with circuit.box([Twirl()]):
...    circuit.cz(0, 1)
>>> with circuit.box([Twirl()]):
...     circuit.measure_all()
>>>
>>> template, samplex = build(circuit)
>>> samplex.draw()

Although the DAG is the main component of the data model this object, other information is also stored:

  • Samplexes themselves can be parametric (see add:attr:`~parameters) and evaluate parameter expressions, all of which is stored by the samplex. For example, when constructed via build(), a samplex will accept values for all of the parameters accepted by the base circuit.

  • Input and output specifications that describe exactly what values are expected as input at sample() time, and what values will be returned, see inputs() and outputs().

  • Noise model requirements that specify what types of noise models are compatible with the sampling needs of a samplex.

Attributes Summary

num_parameters

The number of parameters expected at sampling time.

parameters

The sorted parameters expecting values at sampling time.

Methods Summary

add_edge(a, b)

Add an edge to the samplex graph.

add_input(specification)

Add a sampling input to this samplex.

add_node(node)

Add a node to the samplex graph.

add_output(specification)

Add a sampling output to this samplex.

append_parameter_expression(expression)

Add a parameter expression to the samplex.

draw([cols, subgraph_idxs, layout_method])

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

finalize()

Signal that all nodes and edges have been added, and determine node traversal order.

inputs()

Return an object that specifies and helps build the required inputs of sample().

outputs()

Return an object that specifies the promised outputs of sample().

sample(samplex_input[, num_randomizations, ...])

Sample.

set_passthrough_params(passthrough_params)

Set the mapping for passthrough parameters.

Attributes Documentation

num_parameters

The number of parameters expected at sampling time.

parameters

The sorted parameters expecting values at sampling time.

Methods Documentation

add_edge(a: int, b: int) int[source]

Add an edge to the samplex graph.

Parameters:
  • a – The node index of the source node.

  • b – The node index of the destination node.

Returns:

The integer index of the added edge.

add_input(specification: Specification)[source]

Add a sampling input to this samplex.

Parameters:

specification – A specification of the input name and type.

add_node(node: Node) int[source]

Add a node to the samplex graph.

Parameters:

node – The node to add.

Returns:

The integer index of the added node.

add_output(specification: Specification)[source]

Add a sampling output to this samplex.

Parameters:

specification – A specification of the ouput name and type.

append_parameter_expression(expression: ParameterExpression) int[source]

Add a parameter expression to the samplex.

An expression needs to be added to a samplex before a node can be added that references it. An entry will be inserted into parameters in sorted order for every new parameter appearing in the given parameter expression.

Parameters:

expression – A parameter or parameter expression.

Returns:

An index that parametric nodes can use to referenced the evaluated expression.

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

Draw the graph in this 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 graph.

finalize() Self[source]

Signal that all nodes and edges have been added, and determine node traversal order.

Raises:

SamplexError – If node dependency conflicts are discovered.

Returns:

The same instance, for chaining.

inputs() TensorInterface[source]

Return an object that specifies and helps build the required inputs of sample().

Returns:

The input interface for this samplex.

outputs() TensorInterface[source]

Return an object that specifies the promised outputs of sample().

Returns:

The output interface for this samplex.

sample(samplex_input: TensorInterface, num_randomizations: int = 1, rng: int | SeedSequence | Generator | None = None, keep_registers: bool = False, max_workers: int | None = None) SamplexOutput[source]

Sample.

The following example builds a simple circuit with two parameters into a samplex to demonstrate calling this method. In particular, note that required inputs to the samplex nodes, which includes both type and array shape information, are listed by inputs(), and need to be bound with values.

>>> from samplomatic import build, Twirl
>>> from qiskit.circuit import QuantumCircuit, Parameter
>>>
>>> # quickly make a circuit and build it to construct an example simplex
>>> circuit = QuantumCircuit(2)
>>> with circuit.box([Twirl()]):
...     circuit.rx(Parameter("a"), 0)
...     circuit.rx(Parameter("b"), 0)
...     circuit.cz(0, 1)
>>> with circuit.box([Twirl()]):
...     circuit.measure_all()
>>>
>>> template, samplex = build(circuit)
>>>
>>> # query the samplex for the expected inputs at sample time, and note that we
>>> # are required to pass a length-2 vector of floats for 'a' and 'b' (alphabetical)
>>> print(inputs := samplex.inputs())
TensorInterface(<
    - 'parameter_values' <float64[2]>: Input parameter values to use during sampling.
>)
>>> # after binding required values, we can sample 123 randomizations
>>> samplex.sample(
...     inputs.bind(parameter_values=[0.1, 0.2]),
...     num_randomizations=123,
... )
SamplexOutput({'measurement_flips.meas': ..., 'parameter_values': ...})
Parameters:
  • samplex_input – The inputs required to generate samples for this samplex. See inputs().

  • num_randomizations – The number of randomizations to sample.

  • keep_registers – Whether to keep the virtual registers used during sampling and include them in the output under the metadata key "registers".

  • rng – An integer for seeding a randomness generator, a generator itself, or None to use the default generator owned by the module.

  • max_workers – The maximum number of threads that can be used to execute the parallel execution of sampling, evaluation, and collection nodes.

set_passthrough_params(passthrough_params: list[tuple[int | None, ParameterExpression]]) int[source]

Set the mapping for passthrough parameters.

Some parameters are not influenced by virtual gate propagation and are not set by collection nodes. These parameters are only mapped from the original circuit parameters, to the template circuit parameters. This function sets the mapping for these parameters.

Parameters:

passthrough_paramsParamSpec for the passthrough parameters.

Returns: The maximum template parameter index in passthrough_params.