Results

class trueq.Results(value=None, n_measurements=None, dim=None)

A dictionary-like class which keeps track of measurement results for a single circuit that was run on quantum hardware or a simulator. This is stored as a map from bitstrings to corresponding counts.

Here are some examples:

from trueq import Results

# here we have 3 total measurements in the circuit, meaning we
# have to have a bitstring of length 3
res = Results()
res["000"] = 1000
res["100"] = 5

# unary addition with another (compatible) Results-like object is supported
res += {"100": 5, "110": 100}

res
Results({'000': 1000, '100': 10, '110': 100})
from trueq import Results

# here we have 2 total measurements in the circuit
res = Results(n_measurements=2)

# we can also index this using the decimal representation of the number
# so in the case of 2 total measurements, we can index, 0, 1, 2, 3.
# This only works if either measurements are already present, or
# n_measurements is specified on instantiation.
res[0] = 30
res[3] += 30

res
Results({'00': 30, '11': 30})

Note

This class works for ditstrings as well as bitstrings, for \(d<10\). However, for simplicity, this documentation refers to bitstrings throughout.

Dimension can be specified at instantiation, but this dimension will be silently increased if bitstrings with larger numbers get inserted. For example, putting a bitstring of '002' in will mean the dimension will be at least 3, even if it was only 2 previously.

Parameters:
  • value (dict-like) – A dictionary-like object containing bitstrings and counts, see above examples.

  • n_measurements (int | NoneType) – The number of measurements present. This is inferred if None provided. If not provided, the first measurement added sets this value. Using integer-based indexing of the results will not work unless either this number is specified, or has been inferred through another means.

  • dim (int | NoneType) – The dimension of each subsystem. This number will automatically increase if bitstrings with larger values are used. The default value None will result in the dimension of value if it exists.

Raises:

ValueError – If the dim value provided is greater than 9.

get(bitstring)

Gets the value associated with a given bitstring, if none is found, returns 0.

Bitstrings must match the number of measurements in the Results, this can be checked with n_measurements

Parameters:

bitstring (int | str) – A valid bitstring or int, such as '101' or 5.

Return type:

int

reset()

Resets all counts to 0, but leaves measurement positions intact. Resets last_modified to None.

property last_modified

The timestamp of the last modification to these results, which is automatically set whenever the results are modified. The automatically set value is the current UTC time in the ISO string format. This timestamp can also be set manually, in which case any subsequent changes to the results will not cause this timestamp to automatically change. Results that have never had counts added will have a value of None.

from trueq import Results

r = Results()
print(r.last_modified is None)

# automatically update the timestamp
r["00"] = 5
print(r.last_modified)

# manually update the timestamp
r.last_modified = "2019-10-02T00:00:00.0"
print(r.last_modified)

# we've adjusted it manually, so it is no longer automatically updated
r["01"] = 2
print(r.last_modified)
True
2023-08-28T15:06:33.063051
2019-10-02T00:00:00.0
2019-10-02T00:00:00.0
Type:

str | NoneType

property dim

The system dimension; 2 for qubits, 3 for qutrits, etc.

Type:

int

property vec

The vector representation of the results.

from trueq import Results

res = Results(n_measurements=2)
res[0] = 10
res[3] = 30

res.vec
array([10,  0,  0, 30])
Type:

numpy.ndarray

Raises:

ValueError – If the n_measurements exceeds the MAX_VEC_SYS value, which can be manually adjusted.

property n_measurements

The total number of measurements, i.e. the length of each bitstring.

Type:

int

property n_shots

The total number of shots present.

This is useful in the normalization of the results.

from trueq import Results

res = Results(n_measurements=2)
res[0] = 10
res[3] = 30

res.n_shots
40
Type:

int

normalized(total=1, clip_to_zero=True)

Returns a new Results object obtained by normalizing the values of these results to have a sum equal to total, which is 1 by default. By default, the method clips negative values to zero before the normalization.

from trueq import Results

res = Results({"0110": 15, "0111": 40, "1101": 50, "1111": -5})

print(res.normalized())
Results({'0110': 0.14285714285714288, '0111': 0.380952380952381, '1101': 0.4761904761904762})
Parameters:
  • total (float | int) – The sum of values in the returned results.

  • clip_to_zero (bool) – If True, removes negative-valued outcome counts before the normalization.

Return type:

Results

Raises:

RuntimeError – If these results have values that sum to zero, which includes the case of no results.

sort(by_value=True)

Sorts the keys of the results in-place, either by value or by bitstring.

Parameters:

by_value (bool) – If this is true, sort the results with the largest number of counts at the beginning of the results. If this is false, sort the results by the bitstring value, i.e. '00', '01', '10', '11'.

marginal(positions)

Returns a new Results object obtained by marginalizing this results object over the specified positions. The order of positions is respected, so this method can also be used to create a copy with reordered bitstrings.

from trueq import Results

res = Results({"0110": 15, "0111": 35})

print(res.marginal([1, 2]))
print(res.marginal([0, 3]))
Results({'11': 50})
Results({'00': 15, '01': 35})
Parameters:

positions (Iterable) – A list of bitstring position indexes to keep in the new results object.

Return type:

Results

from_vec(vec)

Sets the bitstring counts from a given vector having a length which is a power of the system dimension.

from trueq import Results

Results().from_vec([0.5, 0, 0, 0.5])
Results({'00': 0.5, '11': 0.5})
Parameters:

vec (Iterable) – A vector whose length is a power of the system dimension.

Returns:

This instance.

Return type:

Results

Raises:
  • ValueError – If the Results has a dimension of None and the dimension discerned from the length of vec is greater than 9.

  • ValueError – If the vec length is incompatible with the number of measurements.

subsample(n_shots, copy=True)

Randomly chooses a subset of shots. This is done uniformly and without replacement.

Parameters:
  • n_shots (int) – The number of shots to subsample, must be no bigger than the number of shots present.

  • copy (bool) – If True, a new copy of the results will be made and returned, otherwise, the operation will happen in-place.

Return type:

Results

Raises:
  • ValueError – If the number of shots to subsample exceeds the number of shots.

  • TypeError – If the Results values are not integers.

tvd(other, multinomial=None, n_samples=2000)

Computes the total variational distance (TVD) between these results and another Results object. Each result object is treated as an empirical probability distribution by dividing each value by the sum of all values. The TVD between two probabality vectors \(p\) and \(q\) is given by

\[\operatorname{tvd}(p,q) = \sum_i |p_i - q_i| / 2.\]
from trueq import Results

r1 = Results({"00": 50, "11": 50})
r2 = Results({"00": 498, "11": 492, "01": 1, "10": 9})
print(r1.tvd(r2))

# here, both results sum to 1 so there is assumed to be no multinomial
# sampling error, and hence the variance is reported at 0.
r1 = Results({"00": 0.5, "11": 0.5})
r2 = Results({"00": 0.498, "11": 0.492, "01": 0.001, "10": 0.009})
print(r1.tvd(r2))
(0.010000000000000005, 0.031026929298298984)
(0.010000000000000005, 0.0)

If one or both of these results are assumed to come from a multinomial distribution sampled from some true underlying probabilties (which will in practice usually be the (noisy) measurement probabilities of some final state of a quantum circuit), then the TVD itself is also a random variable. This function reports an estimate of the variance of the TVD, along with the TVD itself described above, as follows.

First, this result object and/or the other result object are determined to be multinomial samples if n_shots is different than 1. However, this can be overridden by the argument multinomial.

To estimate the variance of the TVD, for this result object and/or the other result object (whichever are classified as multinomial by the above criteria), n_samples samples are drawn from the posterior distribution

\[\operatorname{Dirichlet}(x)\]

where \(x\) is a vector of result counts, and where the prior distribution in this model is the non-informative improper prior \(\operatorname{Dirichlet}(0)\). The standard deviation returned by this method is the standard deviation of the TVD of these samples with the samples or values of the other results object.

Parameters:
  • other (Results) – Another results object to compute the TVD with.

  • multinomial (Iterable) – A pair of boolean values (self_multi, other_multi) which specify whether these results and/or the other results should be treated as multinomial samples, thereby contributing to estimated standard deviation. The default value of None results in the behaviour explained above.

  • n_samples (int) – The number of Dirichlet samples to use in the estimation of std.

Returns:

A pair (tvd, std) where tvd is the TVD and std is an estimate of its standard deviation.

Return type:

tuple

Raises:

ValueError – If the result objects are not compatible with one another, where either the dimension or number of measurements do not match.

decompiled_results(weyls)

Returns a new results object where the outcomes are flipped according to the inverse of the given weyls. That is, we assume the existing results resulted from a circuit where the given weyls were compiled into the circuit before every measurement operator, and this function returns the results as if they were not there.

In the case of qubits, this just means flipping the bit whenever an \(X\) or \(Y\) Pauli was compiled before a measurement.

import trueq as tq

# make a simple circuit with random gates on each qubit followed by
# measurements
circuit = tq.Circuit(
    {0: tq.Gate.random(2), 1: tq.Gate.random(2), 2: tq.Gate.random(2)}
)
circuit.measure_all()

# compile this circuit with random Pauli operators
compiler = tq.Compiler([tq.compilation.CompilePaulis()])
compiled_circuit = compiler.compile(circuit)

print(
    "The circuit was compiled with the following operations:",
    compiled_circuit.key,
)

# create a simulator to compare the original, compiled, and decompiled
# results
sim = tq.Simulator()

original_result = sim.sample(circuit, n_shots=float("inf"))
compiled_result = sim.sample(compiled_circuit, n_shots=float("inf"))

# decompile the result from the compiled circuit
decompiled_result = compiled_result.decompiled_results(
    compiled_circuit.key.compiled_pauli
)

tq.visualization.plot_results(
    original_result,
    compiled_result,
    decompiled_result,
    labels=["Original", "Compiled", "Decompiled"],
)
The circuit was compiled with the following operations: Key(compiled_pauli=Weyls('XYX'))
../_images/results_9_1.png
Parameters:

weyls (Weyls) – A multi-qudit Weyl operator.

Return type:

Results

Raises:
  • ValueError – If the dimension of these results and the provided weyls do not match.

  • ValueError – If the input weyls.n_sys does not match n_measurements of these results.

  • ValueError – If the provided weyls has more than one row.

to_dict()

Returns a dictionary representation of a Results object. This dictionary representation contains only base Python classes.

Return type:

dict

static from_dict(dic)

Returns a Results constructed from a dictionary representation.

Parameters:

dic (dict) – A dictionary used to construct a new Results.

Return type:

Results

plot(sparse_cutoff=True, axis=None, error_bars=False)

Plots a bar graph of these results. See also plot_results() for plotting several Results together, and for additional details.

import trueq as tq

# make a random vector of integers and instantiate new Results
vec = tq.settings.get_rng().integers(0, 100, 32)
tq.Results().from_vec(vec).plot()
../_images/results_10_0.png
Parameters:
  • sparse_cutoff (bool | float) – Optional cutoff for plotting small values. By default this is True, and will not plot values bigger than \(1/s^2\), where \(s\) is the number of possible ditstrings for \(s>256\). If False, no cutoff is applied. The cutoff can be set manually by inputing a float.

  • axis (matplotlib.Axis | NoneType) – An existing axis to plot on, or None to create a new one.

  • error_bars (bool) – Whether or not to plot shot-noise errorbars.