mirror of https://github.com/Qiskit/qiskit.git
updated state tomography, added visualizations library, added qi library
- state tomography supports least squares fitting, and wizard method - qi contains partial trace, state_fidelity, and in future will contain other common quantum info functions
This commit is contained in:
parent
8a3279791f
commit
dd146c7924
|
@ -0,0 +1,241 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2017 IBM RESEARCH. All Rights Reserved.
|
||||
#
|
||||
# This file is intended only for use during the USEQIP Summer School 2017.
|
||||
# Do not distribute.
|
||||
# It is provided without warranty or conditions of any kind, either express or
|
||||
# implied.
|
||||
# An open source version of this file will be included in QISKIT-DEV-PY
|
||||
# reposity in the future. Keep an eye on the Github repository for updates!
|
||||
# https://github.com/IBM/qiskit-sdk-py
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
A collection of useful quantum information functions.
|
||||
|
||||
Author: Christopher J. Wood <cjwood@us.ibm.com>
|
||||
|
||||
Currently this file is very sparse. More functions will be added in
|
||||
the future.
|
||||
"""
|
||||
|
||||
|
||||
import numpy as np
|
||||
from scipy.linalg import sqrtm
|
||||
from tools.pauli import pauli_group
|
||||
|
||||
|
||||
###############################################################
|
||||
# State manipulation.
|
||||
###############################################################
|
||||
|
||||
|
||||
def partial_trace(state, sys, dims=None):
|
||||
"""
|
||||
Partial trace over subsystems of multi-partite matrix.
|
||||
|
||||
Note that subsystems are ordered as rho012 = rho0(x)rho1(x)rho2.
|
||||
|
||||
Args:
|
||||
state (NxN matrix_like): a matrix
|
||||
sys (list(int): a list of subsystems (starting from 0) to trace over
|
||||
dims (list(int), optional): a list of the dimensions of the subsystems.
|
||||
If this is not set it will assume all subsystems are qubits.
|
||||
|
||||
Returns:
|
||||
A matrix with the appropriate subsytems traced over.
|
||||
"""
|
||||
# convert op to density matrix
|
||||
rho = np.array(state)
|
||||
if rho.ndim == 1:
|
||||
rho = outer(rho) # convert state vector to density mat
|
||||
|
||||
# compute dims if not specified
|
||||
if dims is None:
|
||||
n = int(np.log2(len(rho)))
|
||||
dims = [2 for i in range(n)]
|
||||
if len(rho) != 2 ** n:
|
||||
print("ERRROR!")
|
||||
else:
|
||||
dims = list(dims)
|
||||
|
||||
# reverse sort trace sys
|
||||
if isinstance(sys, int):
|
||||
sys = [sys]
|
||||
else:
|
||||
sys = sorted(sys, reverse=True)
|
||||
|
||||
# trace out subsystems
|
||||
for j in sys:
|
||||
# get trace dims
|
||||
dpre = dims[:j]
|
||||
dpost = dims[j+1:]
|
||||
dim1 = int(np.prod(dpre))
|
||||
dim2 = int(dims[j])
|
||||
dim3 = int(np.prod(dpost))
|
||||
# dims with sys-j removed
|
||||
dims = dpre + dpost
|
||||
# do the trace over j
|
||||
rho = __trace_middle(rho, dim1, dim2, dim3)
|
||||
return rho
|
||||
|
||||
|
||||
def __trace_middle(op, dim1=1, dim2=1, dim3=1):
|
||||
"""
|
||||
Partial trace over middle system of tripartite state.
|
||||
|
||||
Args:
|
||||
op (NxN matrix_like): a tri-partite matrix
|
||||
dim1: dimension of the first subsystem
|
||||
dim2: dimension of the second (traced over) subsystem
|
||||
dim3: dimension of the third subsystem
|
||||
|
||||
Returns:
|
||||
A (D,D) matrix where D = dim1 * dim3
|
||||
"""
|
||||
|
||||
op = op.reshape(dim1, dim2, dim3, dim1, dim2, dim3)
|
||||
d = dim1 * dim3
|
||||
return op.trace(axis1=1, axis2=4).reshape(d, d)
|
||||
|
||||
|
||||
def vectorize(rho, basis='col'):
|
||||
"""Flatten an operator to a vector in a specified basis.
|
||||
|
||||
Args:
|
||||
rho (ndarray): a density matrix.
|
||||
basis (str): the basis to vectorize in. Allowed values are
|
||||
- 'col' (default) flattens to column-major vector.
|
||||
- 'row' flattens to row-major vector.
|
||||
- 'pauli'flattens in the n-qubit Pauli basis.
|
||||
|
||||
Returns:
|
||||
ndarray: the resulting vector.
|
||||
|
||||
"""
|
||||
if basis == 'col':
|
||||
return rho.flatten(order='F')
|
||||
elif basis == 'row':
|
||||
return rho.flatten(order='C')
|
||||
elif basis == 'pauli':
|
||||
num = int(np.log2(len(rho))) # number of qubits
|
||||
if len(rho) != 2**num:
|
||||
print('error input state must be n-qubit state')
|
||||
vals = list(map(lambda x: np.real(np.trace(np.dot(x.to_matrix(), rho))),
|
||||
pauli_group(num)))
|
||||
return np.array(vals)
|
||||
|
||||
|
||||
def devectorize(vec, basis='col'):
|
||||
"""Devectorize a vectorized square matrix.
|
||||
|
||||
Args:
|
||||
vec (ndarray): a vectorized density matrix.
|
||||
basis (str): the basis to vectorize in. Allowed values are
|
||||
- 'col' (default): flattens to column-major vector.
|
||||
- 'row': flattens to row-major vector.
|
||||
- 'pauli': flattens in the n-qubit Pauli basis.
|
||||
|
||||
Returns:
|
||||
ndarray: the resulting matrix.
|
||||
|
||||
"""
|
||||
d = int(np.sqrt(vec.size)) # the dimension of the matrix
|
||||
if len(vec) != d*d:
|
||||
print('error input is not a vectorized square matrix')
|
||||
|
||||
if basis == 'col':
|
||||
return vec.reshape(d, d, order='F')
|
||||
elif basis == 'row':
|
||||
return vec.reshape(d, d, order='C')
|
||||
elif basis == 'pauli':
|
||||
num = int(np.log2(d)) # number of qubits
|
||||
if d != 2 ** num:
|
||||
print('error input state must be n-qubit state')
|
||||
pbasis = np.array([p.to_matrix() for p in pauli_group(2)]) / num**2
|
||||
return np.tensordot(vec, pbasis, axes=1)
|
||||
|
||||
def chop(op, epsilon=1e-10):
|
||||
"""
|
||||
Truncate small values of a complex array.
|
||||
|
||||
Args:
|
||||
op (array_like): array to truncte small values.
|
||||
epsilon (float): threshold.
|
||||
|
||||
Returns:
|
||||
A new operator with small values set to zero.
|
||||
"""
|
||||
op.real[abs(op.real) < epsilon] = 0.0
|
||||
op.imag[abs(op.imag) < epsilon] = 0.0
|
||||
return op
|
||||
|
||||
def outer(v1, v2=None):
|
||||
"""
|
||||
Construct the outer product of two vectors.
|
||||
|
||||
The second vector argument is optional, if absent the projector
|
||||
of the first vector will be returned.
|
||||
|
||||
Args:
|
||||
v1 (ndarray): the first vector.
|
||||
v2 (ndarray): the (optional) second vector.
|
||||
|
||||
Returns:
|
||||
The matrix |v1><v2|.
|
||||
|
||||
"""
|
||||
if v2 is None:
|
||||
u = np.array(v1).conj()
|
||||
else:
|
||||
u = np.array(v2).conj()
|
||||
return np.outer(v1, u)
|
||||
|
||||
|
||||
###############################################################
|
||||
# Measures.
|
||||
###############################################################
|
||||
|
||||
|
||||
def state_fidelity(state1, state2):
|
||||
"""Return the state fidelity between two quantum states.
|
||||
|
||||
Either input may be a state vector, or a density matrix.
|
||||
|
||||
Args:
|
||||
state1: a quantum state vector or density matrix.
|
||||
state2: a quantum state vector or density matrix.
|
||||
|
||||
Returns:
|
||||
The state fidelity F(state1, state2).
|
||||
|
||||
"""
|
||||
# convert input to numpy arrays
|
||||
s1 = np.array(state1)
|
||||
s2 = np.array(state2)
|
||||
|
||||
# fidelity of two state vectors
|
||||
if s1.ndim == 1 and s2.ndim == 1:
|
||||
return np.abs(np.dot(s2.conj(), s1))
|
||||
# fidelity of vector and density matrix
|
||||
elif s1.ndim == 1:
|
||||
# psi = s1, rho = s2
|
||||
return np.sqrt(np.abs(np.dot(s1.conj(), np.dot(s2, s1))))
|
||||
elif s2.ndim == 1:
|
||||
# psi = s2, rho = s1
|
||||
return np.sqrt(np.abs(np.dot(s2.conj(), np.dot(s1, s2))))
|
||||
# fidelity of two density matrices
|
||||
else:
|
||||
(s1sq, err1) = sqrtm(s1, disp=False)
|
||||
(s2sq, err2) = sqrtm(s2, disp=False)
|
||||
return np.linalg.norm(np.dot(s1sq, s2sq), ord='nuc')
|
||||
|
||||
|
||||
###############################################################
|
||||
# Other.
|
||||
###############################################################
|
||||
|
||||
|
||||
def is_pos_def(x):
|
||||
return np.all(np.linalg.eigvals(x) > 0)
|
|
@ -17,468 +17,280 @@ from Smolin, Gambetta, Smith Phys. Rev. Lett. 108, 070502 (arXiv: 1106.5458)
|
|||
|
||||
Author: Christopher J. Wood <cjwood@us.ibm.com>
|
||||
Jay Gambetta
|
||||
Andrew Cross
|
||||
|
||||
TODO: Process tomography, SDP fitting
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
from functools import reduce
|
||||
from scipy import linalg as la
|
||||
import matplotlib.pyplot as plt
|
||||
from mpl_toolkits.mplot3d import proj3d
|
||||
from matplotlib.patches import FancyArrowPatch
|
||||
from tools.pauli import pauli_group, pauli_singles
|
||||
from re import match
|
||||
from itertools import product
|
||||
|
||||
from qiskit import QuantumProgram
|
||||
from tools.qi import vectorize, devectorize, outer
|
||||
|
||||
|
||||
###############################################################
|
||||
# Plotting tools
|
||||
# NEW CIRCUIT GENERATION
|
||||
###############################################################
|
||||
|
||||
|
||||
class Arrow3D(FancyArrowPatch):
|
||||
"""Standard 3D arrow."""
|
||||
|
||||
def __init__(self, xs, ys, zs, *args, **kwargs):
|
||||
"""Create arrow."""
|
||||
FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs)
|
||||
self._verts3d = xs, ys, zs
|
||||
|
||||
def draw(self, renderer):
|
||||
"""Draw the arrow."""
|
||||
xs3d, ys3d, zs3d = self._verts3d
|
||||
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
|
||||
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
|
||||
FancyArrowPatch.draw(self, renderer)
|
||||
"""
|
||||
Basis should be specified as a dictionary {'X': [X0, X1], 'Y': [Y0, Y1], 'Z': [Z0, Z1]}
|
||||
where X0 is the projector onto the 0 outcome state of 'X'
|
||||
"""
|
||||
|
||||
|
||||
# COMPLEMENT = {'1': '0', '0': '1'}
|
||||
def build_state_tomography_circuits(Q_program, name, qubits, qreg, creg):
|
||||
"""
|
||||
"""
|
||||
labels = __add_meas_circuits(Q_program, name, qubits, qreg, creg)
|
||||
print('>> created state tomography circuits for "%s"' % name)
|
||||
return labels
|
||||
|
||||
|
||||
# Make private for now, since fit method not yet implemented
|
||||
def __build_process_tomography_circuits(Q_program, name, qubits, qreg, creg):
|
||||
"""
|
||||
"""
|
||||
# add preparation circuits
|
||||
preps = __add_prep_circuits(Q_program, name, qubits, qreg, creg)
|
||||
# add measurement circuits for each prep circuit
|
||||
labels = []
|
||||
for circ in preps:
|
||||
labels += __add_meas_circuits(Q_program, circ, qubits, qreg, creg)
|
||||
# delete temp prep output
|
||||
del Q_program._QuantumProgram__quantum_program['circuits'][circ]
|
||||
print('>> created process tomography circuits for "%s"' % name)
|
||||
return labels
|
||||
|
||||
|
||||
# def compliment(value):
|
||||
# """Swap 1 and 0 in a vector."""
|
||||
# return ''.join(COMPLEMENT[x] for x in value)
|
||||
def __tomo_dicts(qubits, basis=None):
|
||||
"""Helper function.
|
||||
|
||||
|
||||
def plot_bloch_vector(bloch, title=""):
|
||||
"""Plot the Bloch sphere.
|
||||
|
||||
Plot a sphere, axes, the Bloch vector, and its projections onto each axis.
|
||||
Build a dictionary assigning a basis element to a qubit.
|
||||
|
||||
Args:
|
||||
bloch (list[double]): array of three elements where [<x>, <y>,<z>]
|
||||
title (str): a string that represents the plot title
|
||||
qubit (int): the qubit to add
|
||||
tomos (list[dict]): list of tomo_dicts to add to
|
||||
basis (list[str], optional): basis to use. If not specified
|
||||
the default is ['X', 'Y', 'Z']
|
||||
|
||||
Returns:
|
||||
none: plot is shown with matplotlib to the screen
|
||||
|
||||
a new list of tomo_dict
|
||||
"""
|
||||
# Set arrow lengths
|
||||
arlen = 1.3
|
||||
|
||||
# Plot semi-transparent sphere
|
||||
u = np.linspace(0, 2 * np.pi, 100)
|
||||
v = np.linspace(0, np.pi, 100)
|
||||
x = np.outer(np.cos(u), np.sin(v))
|
||||
y = np.outer(np.sin(u), np.sin(v))
|
||||
z = np.outer(np.ones(np.size(u)), np.cos(v))
|
||||
|
||||
fig = plt.figure(figsize=(6, 6))
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
ax.set_aspect("equal")
|
||||
ax.plot_surface(x, y, z, color=(.5, .5, .5), alpha=0.1)
|
||||
|
||||
# Plot arrows (axes, Bloch vector, its projections)
|
||||
xa = Arrow3D([0, arlen], [0, 0], [0, 0], mutation_scale=20, lw=1,
|
||||
arrowstyle="-|>", color=(.5, .5, .5))
|
||||
ya = Arrow3D([0, 0], [0, arlen], [0, 0], mutation_scale=20, lw=1,
|
||||
arrowstyle="-|>", color=(.5, .5, .5))
|
||||
za = Arrow3D([0, 0], [0, 0], [0, arlen], mutation_scale=20, lw=1,
|
||||
arrowstyle="-|>", color=(.5, .5, .5))
|
||||
a = Arrow3D([0, bloch[0]], [0, bloch[1]], [0, bloch[2]], mutation_scale=20,
|
||||
lw=2, arrowstyle="simple", color="k")
|
||||
bax = Arrow3D([0, bloch[0]], [0, 0], [0, 0], mutation_scale=20, lw=2,
|
||||
arrowstyle="-", color="r")
|
||||
bay = Arrow3D([0, 0], [0, bloch[1]], [0, 0], mutation_scale=20, lw=2,
|
||||
arrowstyle="-", color="g")
|
||||
baz = Arrow3D([0, 0], [0, 0], [0, bloch[2]], mutation_scale=20, lw=2,
|
||||
arrowstyle="-", color="b")
|
||||
arrowlist = [xa, ya, za, a, bax, bay, baz]
|
||||
for arr in arrowlist:
|
||||
ax.add_artist(arr)
|
||||
|
||||
# Rotate the view
|
||||
ax.view_init(30, 30)
|
||||
|
||||
# Annotate the axes, shifts are ad-hoc for this (30, 30) view
|
||||
xp, yp, _ = proj3d.proj_transform(arlen, 0, 0, ax.get_proj())
|
||||
plt.annotate("x", xy=(xp, yp), xytext=(-3, -8), textcoords='offset points',
|
||||
ha='right', va='bottom')
|
||||
xp, yp, _ = proj3d.proj_transform(0, arlen, 0, ax.get_proj())
|
||||
plt.annotate("y", xy=(xp, yp), xytext=(6, -5), textcoords='offset points',
|
||||
ha='right', va='bottom')
|
||||
xp, yp, _ = proj3d.proj_transform(0, 0, arlen, ax.get_proj())
|
||||
plt.annotate("z", xy=(xp, yp), xytext=(2, 0), textcoords='offset points',
|
||||
ha='right', va='bottom')
|
||||
|
||||
plt.title(title)
|
||||
plt.show()
|
||||
|
||||
|
||||
def plot_state_city(rho, title=""):
|
||||
"""Plot the cityscape of quantum state.
|
||||
|
||||
Plot two 3d bargraphs (two dimenstional) of the mixed state rho
|
||||
|
||||
Args:
|
||||
rho (np.array[[complex]]): array of dimensions 2**n x 2**nn complex
|
||||
numbers
|
||||
title (str): a string that represents the plot title
|
||||
|
||||
Returns:
|
||||
none: plot is shown with matplotlib to the screen
|
||||
|
||||
"""
|
||||
num = int(np.log2(len(rho)))
|
||||
if basis is None:
|
||||
basis = ['X', 'Y', 'Z']
|
||||
elif isinstance(basis, dict):
|
||||
basis = basis.keys()
|
||||
|
||||
# get the real and imag parts of rho
|
||||
datareal = np.real(rho)
|
||||
dataimag = np.imag(rho)
|
||||
|
||||
# get the labels
|
||||
column_names = [bin(i)[2:].zfill(num) for i in range(2**num)]
|
||||
row_names = [bin(i)[2:].zfill(num) for i in range(2**num)]
|
||||
|
||||
lx = len(datareal[0]) # Work out matrix dimensions
|
||||
ly = len(datareal[:, 0])
|
||||
xpos = np.arange(0, lx, 1) # Set up a mesh of positions
|
||||
ypos = np.arange(0, ly, 1)
|
||||
xpos, ypos = np.meshgrid(xpos+0.25, ypos+0.25)
|
||||
|
||||
xpos = xpos.flatten()
|
||||
ypos = ypos.flatten()
|
||||
zpos = np.zeros(lx*ly)
|
||||
|
||||
dx = 0.5 * np.ones_like(zpos) # width of bars
|
||||
dy = dx.copy()
|
||||
dzr = datareal.flatten()
|
||||
dzi = dataimag.flatten()
|
||||
|
||||
fig = plt.figure(figsize=(8, 8))
|
||||
ax1 = fig.add_subplot(2, 1, 1, projection='3d')
|
||||
ax1.bar3d(xpos, ypos, zpos, dx, dy, dzr, color="g", alpha=0.5)
|
||||
ax2 = fig.add_subplot(2, 1, 2, projection='3d')
|
||||
ax2.bar3d(xpos, ypos, zpos, dx, dy, dzi, color="g", alpha=0.5)
|
||||
|
||||
ax1.set_xticks(np.arange(0.5, lx+0.5, 1))
|
||||
ax1.set_yticks(np.arange(0.5, ly+0.5, 1))
|
||||
ax1.axes.set_zlim3d(-1.0, 1.0001)
|
||||
ax1.set_zticks(np.arange(-1, 1, 0.5))
|
||||
ax1.w_xaxis.set_ticklabels(row_names, fontsize=12, rotation=45)
|
||||
ax1.w_yaxis.set_ticklabels(column_names, fontsize=12, rotation=-22.5)
|
||||
# ax1.set_xlabel('basis state', fontsize=12)
|
||||
# ax1.set_ylabel('basis state', fontsize=12)
|
||||
ax1.set_zlabel("Real[rho]")
|
||||
|
||||
ax2.set_xticks(np.arange(0.5, lx+0.5, 1))
|
||||
ax2.set_yticks(np.arange(0.5, ly+0.5, 1))
|
||||
ax2.axes.set_zlim3d(-1.0, 1.0001)
|
||||
ax2.set_zticks(np.arange(-1, 1, 0.5))
|
||||
ax2.w_xaxis.set_ticklabels(row_names, fontsize=12, rotation=45)
|
||||
ax2.w_yaxis.set_ticklabels(column_names, fontsize=12, rotation=-22.5)
|
||||
# ax2.set_xlabel('basis state', fontsize=12)
|
||||
# ax2.set_ylabel('basis state', fontsize=12)
|
||||
ax2.set_zlabel("Imag[rho]")
|
||||
plt.title(title)
|
||||
plt.show()
|
||||
|
||||
|
||||
def plot_state_paulivec(rho, title=""):
|
||||
"""Plot the paulivec representation of a quantum state.
|
||||
|
||||
Plot a bargraph of the mixed state rho over the pauli matricies
|
||||
|
||||
Args:
|
||||
rho (np.array[[complex]]): array of dimensions 2**n x 2**nn complex
|
||||
numbers
|
||||
title (str): a string that represents the plot title
|
||||
|
||||
Returns:
|
||||
none: plot is shown with matplotlib to the screen
|
||||
|
||||
"""
|
||||
num = int(np.log2(len(rho)))
|
||||
labels = list(map(lambda x: x.to_label(), pauli_group(num)))
|
||||
values = list(map(lambda x: np.real(np.trace(np.dot(x.to_matrix(), rho))),
|
||||
pauli_group(num)))
|
||||
numelem = len(values)
|
||||
ind = np.arange(numelem) # the x locations for the groups
|
||||
width = 0.5 # the width of the bars
|
||||
fig, ax = plt.subplots()
|
||||
ax.grid(zorder=0)
|
||||
ax.bar(ind, values, width, color='seagreen')
|
||||
|
||||
# add some text for labels, title, and axes ticks
|
||||
ax.set_ylabel('Expectation value', fontsize=12)
|
||||
ax.set_xticks(ind)
|
||||
ax.set_yticks([-1, -0.5, 0, 0.5, 1])
|
||||
ax.set_xticklabels(labels, fontsize=12, rotation=70)
|
||||
ax.set_xlabel('Pauli', fontsize=12)
|
||||
ax.set_ylim([-1, 1])
|
||||
plt.title(title)
|
||||
plt.show()
|
||||
|
||||
|
||||
def n_choose_k(n, k):
|
||||
"""Return the number of combinations for n choose k.
|
||||
|
||||
Args:
|
||||
n (int): the total number of options .
|
||||
k (int): The number of elements.
|
||||
|
||||
Returns:
|
||||
int: returns the binomial coefficient
|
||||
|
||||
"""
|
||||
if n == 0:
|
||||
return 0
|
||||
if not tomos:
|
||||
return [{qubit: b} for b in basis]
|
||||
else:
|
||||
return reduce(lambda x, y: x * y[0] / y[1],
|
||||
zip(range(n - k + 1, n + 1),
|
||||
range(1, k + 1)), 1)
|
||||
|
||||
|
||||
def lex_index(n, k, lst):
|
||||
"""Return the lex index of a combination..
|
||||
|
||||
Args:
|
||||
n (int): the total number of options .
|
||||
k (int): The number of elements.
|
||||
lst
|
||||
|
||||
Returns:
|
||||
int: returns int index for lex order
|
||||
|
||||
ret = []
|
||||
for t in tomos:
|
||||
for b in basis:
|
||||
t[qubit] = b
|
||||
ret.append(t.copy())
|
||||
return ret
|
||||
"""
|
||||
assert len(lst) == k, "list should have length k"
|
||||
comb = list(map(lambda x: n - 1 - x, lst))
|
||||
dualm = sum([n_choose_k(comb[k - 1 - i], i + 1) for i in range(k)])
|
||||
return int(dualm)
|
||||
if isinstance(qubits, int):
|
||||
qubits = [qubits]
|
||||
if basis is None:
|
||||
basis = ['X', 'Y', 'Z']
|
||||
elif isinstance(basis, dict):
|
||||
basis = basis.keys()
|
||||
|
||||
return [dict(zip(qubits, b)) for b in product(basis, repeat=len(qubits))]
|
||||
|
||||
|
||||
def bit_string_index(s):
|
||||
"""Return the index of a string of 0s and 1s."""
|
||||
n = len(s)
|
||||
k = s.count("1")
|
||||
assert s.count("0") == n - k, "s must be a string of 0 and 1"
|
||||
ones = [pos for pos, char in enumerate(s) if char == "1"]
|
||||
return lex_index(n, k, ones)
|
||||
|
||||
|
||||
def phase_to_color_wheel(complex_number):
|
||||
"""Map a phase of a complexnumber to a color in (r,g,b).
|
||||
|
||||
complex_number is phase is first mapped to angle in the range
|
||||
[0, 2pi] and then to a color wheel with blue at zero phase.
|
||||
def __add_meas_circuits(Q_program, name, qubits, qreg, creg, prep=None):
|
||||
"""Helper function.
|
||||
"""
|
||||
angles = np.angle(complex_number)
|
||||
angle_round = int(((angles + 2 * np.pi) % (2 * np.pi))/np.pi*6)
|
||||
# print(angleround)
|
||||
color_map = {0: (0, 0, 1), # blue,
|
||||
1: (0.5, 0, 1), # blue-violet
|
||||
2: (1, 0, 1), # violet
|
||||
3: (1, 0, 0.5), # red-violet,
|
||||
4: (1, 0, 0), # red
|
||||
5: (1, 0.5, 0), # red-oranage,
|
||||
6: (1, 1, 0), # orange
|
||||
7: (0.5, 1, 0), # orange-yellow
|
||||
8: (0, 1, 0), # yellow,
|
||||
9: (0, 1, 0.5), # yellow-green,
|
||||
10: (0, 1, 1), # green,
|
||||
11: (0, 0.5, 1) # green-blue,
|
||||
}
|
||||
return color_map[angle_round]
|
||||
|
||||
if isinstance(qreg, str):
|
||||
qreg = Q_program.get_quantum_registers(qreg)
|
||||
if isinstance(creg, str):
|
||||
creg = Q_program.get_classical_registers(creg)
|
||||
orig = Q_program.get_circuit(name)
|
||||
|
||||
labels = []
|
||||
|
||||
def plot_state_qsphere(rho):
|
||||
"""Plot the qsphere representation of a quantum state."""
|
||||
num = int(np.log2(len(rho)))
|
||||
# get the eigenvectors and egivenvalues
|
||||
we, stateall = la.eigh(rho)
|
||||
for i in range(2**num):
|
||||
# start with the max
|
||||
probmix = we.max()
|
||||
prob_location = we.argmax()
|
||||
if probmix > 0.001:
|
||||
print("The " + str(i) + "th eigenvalue = " + str(probmix))
|
||||
# get the max eigenvalue
|
||||
state = stateall[:, prob_location]
|
||||
loc = np.absolute(state).argmax()
|
||||
# get the element location closes to lowest bin representation.
|
||||
for j in range(2**num):
|
||||
test = np.absolute(np.absolute(state[j])
|
||||
- np.absolute(state[loc]))
|
||||
if test < 0.001:
|
||||
loc = j
|
||||
break
|
||||
# remove the global phase
|
||||
angles = (np.angle(state[loc]) + 2 * np.pi) % (2 * np.pi)
|
||||
angleset = np.exp(-1j*angles)
|
||||
# print(state)
|
||||
# print(angles)
|
||||
state = angleset*state
|
||||
# print(state)
|
||||
state.flatten()
|
||||
# start the plotting
|
||||
fig = plt.figure(figsize=(10, 10))
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
ax.axes.set_xlim3d(-1.0, 1.0)
|
||||
ax.axes.set_ylim3d(-1.0, 1.0)
|
||||
ax.axes.set_zlim3d(-1.0, 1.0)
|
||||
ax.set_aspect("equal")
|
||||
ax.axes.grid(False)
|
||||
# Plot semi-transparent sphere
|
||||
u = np.linspace(0, 2 * np.pi, 25)
|
||||
v = np.linspace(0, np.pi, 25)
|
||||
x = np.outer(np.cos(u), np.sin(v))
|
||||
y = np.outer(np.sin(u), np.sin(v))
|
||||
z = np.outer(np.ones(np.size(u)), np.cos(v))
|
||||
ax.plot_surface(x, y, z, rstride=1, cstride=1, color='k',
|
||||
alpha=0.05, linewidth=0)
|
||||
# wireframe
|
||||
# Get rid of the panes
|
||||
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
||||
ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
||||
ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
||||
for dic in __tomo_dicts(qubits):
|
||||
|
||||
# Get rid of the spines
|
||||
ax.w_xaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
|
||||
ax.w_yaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
|
||||
ax.w_zaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
|
||||
# Get rid of the ticks
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
ax.set_zticks([])
|
||||
# Construct meas circuit name
|
||||
label = '_meas'
|
||||
for qubit, op in dic.items():
|
||||
label += op + str(qubit)
|
||||
circ = Q_program.create_circuit(label, [qreg], [creg])
|
||||
|
||||
d = num
|
||||
for i in range(2**num):
|
||||
# get x,y,z points
|
||||
element = bin(i)[2:].zfill(num)
|
||||
weight = element.count("1")
|
||||
zvalue = -2 * weight / d + 1
|
||||
number_of_divisions = n_choose_k(d, weight)
|
||||
weight_order = bit_string_index(element)
|
||||
# if weight_order >= number_of_divisions / 2:
|
||||
# com_key = compliment(element)
|
||||
# weight_order_temp = bit_string_index(com_key)
|
||||
# weight_order = np.floor(
|
||||
# number_of_divisions / 2) + weight_order_temp + 1
|
||||
angle = (weight_order) * 2 * np.pi / number_of_divisions
|
||||
xvalue = np.sqrt(1 - zvalue**2) * np.cos(angle)
|
||||
yvalue = np.sqrt(1 - zvalue**2) * np.sin(angle)
|
||||
ax.plot([xvalue], [yvalue], [zvalue],
|
||||
markerfacecolor=(.5, .5, .5),
|
||||
markeredgecolor=(.5, .5, .5),
|
||||
marker='o', markersize=10, alpha=1)
|
||||
# get prob and angle - prob will be shade and angle color
|
||||
prob = np.real(np.dot(state[i], state[i].conj()))
|
||||
colorstate = phase_to_color_wheel(state[i])
|
||||
a = Arrow3D([0, xvalue], [0, yvalue], [0, zvalue],
|
||||
mutation_scale=20, alpha=prob, arrowstyle="-",
|
||||
color=colorstate, lw=10)
|
||||
ax.add_artist(a)
|
||||
# add weight lines
|
||||
for weight in range(d + 1):
|
||||
theta = np.linspace(-2 * np.pi, 2 * np.pi, 100)
|
||||
z = -2 * weight / d + 1
|
||||
r = np.sqrt(1 - z**2)
|
||||
x = r * np.cos(theta)
|
||||
y = r * np.sin(theta)
|
||||
ax.plot(x, y, z, color=(.5, .5, .5))
|
||||
# add center point
|
||||
ax.plot([0], [0], [0], markerfacecolor=(.5, .5, .5),
|
||||
markeredgecolor=(.5, .5, .5), marker='o', markersize=10,
|
||||
alpha=1)
|
||||
plt.show()
|
||||
we[prob_location] = 0
|
||||
else:
|
||||
break
|
||||
# add gates to circuit
|
||||
for qubit, op in dic.items():
|
||||
circ.barrier(qreg[qubit])
|
||||
if op == "X":
|
||||
circ.h(qreg[qubit])
|
||||
elif op == "Y":
|
||||
circ.sdg(qreg[qubit])
|
||||
circ.h(qreg[qubit])
|
||||
if prep:
|
||||
circ.barrier(qreg[qubit])
|
||||
else:
|
||||
circ.measure(qreg[qubit], creg[qubit])
|
||||
# add circuit to QuantumProgram
|
||||
Q_program.add_circuit(name+label, orig + circ)
|
||||
# add label to output
|
||||
labels.append(name+label)
|
||||
# delete temp circuit
|
||||
del Q_program._QuantumProgram__quantum_program['circuits'][label]
|
||||
|
||||
return labels
|
||||
|
||||
def plot_state(rho, method='city'):
|
||||
"""Plot the quantum state."""
|
||||
num = int(np.log2(len(rho)))
|
||||
# Need updating to check its a matrix
|
||||
if method == 'city':
|
||||
plot_state_city(rho)
|
||||
elif method == "paulivec":
|
||||
plot_state_paulivec(rho)
|
||||
elif method == "qsphere":
|
||||
plot_state_qsphere(rho)
|
||||
elif method == "bloch":
|
||||
for i in range(num):
|
||||
bloch_state = list(map(lambda x: np.real(np.trace(
|
||||
np.dot(x.to_matrix(), rho))),
|
||||
pauli_singles(i, num)))
|
||||
plot_bloch_vector(bloch_state, "qubit " + str(i))
|
||||
else:
|
||||
print("No method given")
|
||||
def __add_prep_circuits(Q_program, name, qubits, qreg, creg):
|
||||
"""Helper function.
|
||||
"""
|
||||
|
||||
if isinstance(qreg, str):
|
||||
qreg = Q_program.get_quantum_registers(qreg)
|
||||
if isinstance(creg, str):
|
||||
creg = Q_program.get_classical_registers(creg)
|
||||
orig = Q_program.get_circuit(name)
|
||||
|
||||
labels = []
|
||||
for dic in __tomo_dicts(qubits):
|
||||
|
||||
# Construct meas circuit name
|
||||
label_p = '_prep' # prepare in positive eigenstate
|
||||
label_m = '_prep' # prepare in negative eigenstate
|
||||
for qubit, op in dic.items():
|
||||
label_p += op + 'p' + str(qubit)
|
||||
label_m+= op + 'm' + str(qubit)
|
||||
circ_p = Q_program.create_circuit(label_p, [qreg], [creg])
|
||||
circ_m = Q_program.create_circuit(label_m, [qreg], [creg])
|
||||
|
||||
# add gates to circuit
|
||||
for qubit, op in dic.items():
|
||||
if op == "X":
|
||||
circ_p.h(qreg[qubit])
|
||||
circ_m.x(qreg[qubit])
|
||||
circ_m.h(qreg[qubit])
|
||||
elif op == "Y":
|
||||
circ_p.h(qreg[qubit])
|
||||
circ_p.s(qreg[qubit])
|
||||
circ_m.x(qreg[qubit])
|
||||
circ_m.h(qreg[qubit])
|
||||
circ_m.s(qreg[qubit])
|
||||
elif op == "Z":
|
||||
circ_m.x(qreg[qubit])
|
||||
# add circuit to QuantumProgram
|
||||
Q_program.add_circuit(name+label_p, circ_p + orig)
|
||||
Q_program.add_circuit(name+label_m, circ_m + orig)
|
||||
# add label to output
|
||||
labels += [name+label_p, name+label_m]
|
||||
# delete temp circuit
|
||||
del Q_program._QuantumProgram__quantum_program['circuits'][label_p]
|
||||
del Q_program._QuantumProgram__quantum_program['circuits'][label_m]
|
||||
|
||||
return labels
|
||||
|
||||
###############################################################
|
||||
# Constructing tomographic measurement circuits
|
||||
# Tomography circuit labels
|
||||
###############################################################
|
||||
|
||||
def __tomo_labels(name, qubits, basis=None, subscript=None):
|
||||
"""Helper function.
|
||||
"""
|
||||
if subscript is None:
|
||||
subscript = ''
|
||||
labels = []
|
||||
for dic in __tomo_dicts(qubits, basis):
|
||||
label = ''
|
||||
for qubit, op in dic.items():
|
||||
label += op + subscript + str(qubit)
|
||||
labels.append(name+label)
|
||||
return labels
|
||||
|
||||
def build_keys_helper(keys, qubit):
|
||||
"""Return array of measurement strings ['Xj', 'Yj', 'Zj'] for qubit=j."""
|
||||
tmp = []
|
||||
for k in keys:
|
||||
for b in ["X", "Y", "Z"]:
|
||||
tmp.append(k + b + str(qubit))
|
||||
return tmp
|
||||
def state_tomography_labels(name, qubits, basis=None):
|
||||
"""
|
||||
"""
|
||||
return __tomo_labels(name + '_meas', qubits, basis)
|
||||
|
||||
|
||||
# Make private for now, since fit method not yet implemented
|
||||
def __process_tomography_labels(name, qubits, basis=None):
|
||||
"""
|
||||
"""
|
||||
preps = __tomo_labels(name + '_prep', qubits, basis, subscript='p')
|
||||
preps += __tomo_labels(name + '_prep', qubits, basis, subscript='m')
|
||||
return reduce(lambda acc, c: acc + __tomo_labels(c + '_meas', qubits, basis), preps, [])
|
||||
|
||||
###############################################################
|
||||
# Tomography measurement outcomes
|
||||
###############################################################
|
||||
|
||||
# Note: So far only for state tomography
|
||||
|
||||
# Default Pauli basis
|
||||
__default_basis = { 'X': [np.array([[0.5, 0.5], [0.5, 0.5]]), np.array([[0.5, -0.5], [-0.5, 0.5]])],
|
||||
'Y': [np.array([[0.5, -0.5j], [0.5j, 0.5]]), np.array([[0.5, 0.5j], [-0.5j, 0.5]])],
|
||||
'Z': [np.array([[1, 0], [0, 0]]), np.array([[0, 0], [0, 1]])]}
|
||||
|
||||
|
||||
def build_tomo_keys(circuit, qubit_list):
|
||||
def __counts_keys(n):
|
||||
"""helper function.
|
||||
"""
|
||||
For input circuit string returns an array of all measurement circits orded in
|
||||
lexicographic order from last qubit to first qubit.
|
||||
Example:
|
||||
qubit_list = [0]: [circuitX0, circuitY0, circuitZ0].
|
||||
qubit_list = [0,1]: [circuitX1X0, circuitX1Y0, circuitX1Z0, circuitY1X0,..., circuitZ1Z0].
|
||||
"""
|
||||
keys = [circuit]
|
||||
for j in sorted(qubit_list, reverse=True):
|
||||
keys = build_keys_helper(keys, j)
|
||||
return keys
|
||||
return [bin(j)[2:].zfill(n) for j in range(2 ** n)]
|
||||
|
||||
# We need to build circuits in QASM lexical order, not standard!
|
||||
|
||||
def build_tomo_circuit_helper(Q_program, circuits, qreg: str, creg: str, qubit: int):
|
||||
"""
|
||||
Adds measurements for the qubit=j to the input circuits, so if circuits = [c0, c1,...]
|
||||
circuits-> [c0Xj, c0Yj, c0Zj, c1Xj,...]
|
||||
"""
|
||||
for c in circuits:
|
||||
circ = Q_program.get_circuit(c)
|
||||
for b in ["X","Y","Z"]:
|
||||
meas = b+str(qubit)
|
||||
tmp = Q_program.create_circuit(meas, [qreg],[creg])
|
||||
qr = Q_program.get_quantum_registers(qreg)
|
||||
cr = Q_program.get_classical_registers(creg)
|
||||
if b == "X":
|
||||
tmp.u2(0., np.pi, qr[qubit])
|
||||
if b == "Y":
|
||||
tmp.u2(0., np.pi / 2., qr[qubit])
|
||||
tmp.measure(qr[qubit], cr[qubit])
|
||||
Q_program.add_circuit(c+meas, circ + tmp)
|
||||
def __get_basis_ops(tup, basis):
|
||||
return reduce(lambda acc, b: [np.kron(a,j) for a in acc for j in basis[b]], tup, [1])
|
||||
|
||||
def build_tomo_circuits(Q_program, circuit, qreg: str, creg: str, qubit_list):
|
||||
|
||||
def __counts_basis(n, basis):
|
||||
return [dict(zip(__counts_keys(n), __get_basis_ops(key, basis)))
|
||||
for key in product(basis.keys(), repeat=n)]
|
||||
|
||||
|
||||
def marginal_counts(counts, meas_qubits):
|
||||
"""
|
||||
Generates circuits in the input QuantumProgram for implementing complete quantum
|
||||
state tomography of the state of the qubits in qubit_list prepared by circuit.
|
||||
Returns a list of the measurement outcome strings for meas_qubits qubits in an
|
||||
nq qubit system.
|
||||
"""
|
||||
circ = [circuit]
|
||||
for j in sorted(qubit_list, reverse=True):
|
||||
build_tomo_circuit_helper(Q_program, circ, qreg, creg, j)
|
||||
circ = build_keys_helper(circ, j)
|
||||
|
||||
# Extract total number of qubits from count keys
|
||||
nq = len(list(counts.keys())[0])
|
||||
|
||||
# keys for measured qubits only
|
||||
qs = sorted(meas_qubits, reverse=True)
|
||||
|
||||
meas_keys = __counts_keys(len(qs))
|
||||
|
||||
# get regex match strings for suming outcomes of other qubits
|
||||
rgx = [reduce(lambda x, y: (key[qs.index(y)] if y in qs else '\d')+x, range(nq), '') for key in meas_keys]
|
||||
|
||||
# build the return list
|
||||
meas_counts = []
|
||||
for m in rgx:
|
||||
c = 0
|
||||
for key, val in counts.items():
|
||||
if match(m, key):
|
||||
c += val
|
||||
meas_counts.append(c)
|
||||
|
||||
# return as counts dict on measured qubits only
|
||||
return dict(zip(meas_keys, meas_counts))
|
||||
|
||||
|
||||
def state_tomography_data(Q_program, name, meas_qubits, backend=None, basis=None):
|
||||
"""
|
||||
"""
|
||||
if basis is None:
|
||||
basis = __default_basis
|
||||
labels = state_tomography_labels(name, meas_qubits)
|
||||
counts = [marginal_counts(Q_program.get_counts(circ, backend), meas_qubits) for circ in labels]
|
||||
shots = [sum(c.values()) for c in counts]
|
||||
meas_basis = __counts_basis(len(meas_qubits), basis)
|
||||
ret = [{'counts': i, 'meas_basis': j, 'shots': k} for i, j, k in zip(counts, meas_basis, shots)]
|
||||
return ret
|
||||
|
||||
|
||||
###############################################################
|
||||
|
@ -486,18 +298,7 @@ def build_tomo_circuits(Q_program, circuit, qreg: str, creg: str, qubit_list):
|
|||
###############################################################
|
||||
|
||||
|
||||
def vectorize(op):
|
||||
"""Flatten an operator to a column-major vector."""
|
||||
return op.flatten(order='F')
|
||||
|
||||
|
||||
def devectorize(v):
|
||||
"""Devectorize a column-major vectorized square matrix."""
|
||||
d = int(np.sqrt(v.size))
|
||||
return v.reshape(d, d, order='F')
|
||||
|
||||
|
||||
def meas_basis_matrix(meas_basis):
|
||||
def __tomo_basis_matrix(meas_basis):
|
||||
"""Return a matrix of vectorized measurement operators.
|
||||
|
||||
Measurement operators S = sum_j |j><M_j|.
|
||||
|
@ -507,148 +308,91 @@ def meas_basis_matrix(meas_basis):
|
|||
S = np.array([vectorize(m).conj() for m in meas_basis])
|
||||
return S.reshape(n, d)
|
||||
|
||||
def outer(v1, v2=None):
|
||||
"""
|
||||
Returns the matrix |v1><v2| resulting from the outer product of two vectors.
|
||||
"""
|
||||
if v2 is None:
|
||||
u = v1.conj()
|
||||
else:
|
||||
u = v2.conj()
|
||||
return np.outer(v1, u)
|
||||
|
||||
def wizard(rho, normalize_flag = True, epsilon = 0.):
|
||||
def __tomo_linear_inv(freqs, ops, weights=None, trace=1):
|
||||
"""
|
||||
"""
|
||||
# get weights matrix
|
||||
if weights is not None:
|
||||
W = np.array(weights)
|
||||
if W.ndim == 1:
|
||||
W = np.diag(W)
|
||||
|
||||
# Get basis S matrix
|
||||
S = np.array([vectorize(m).conj() for m in ops]).reshape(len(ops), ops[0].size)
|
||||
if weights is not None:
|
||||
S = np.dot(W, S) # W.S
|
||||
|
||||
# get frequencies vec
|
||||
v = np.array(freqs) # |n>
|
||||
if weights is not None:
|
||||
v = np.dot(W, freqs) #W.|n>
|
||||
Sdg = S.T.conj() # S^*.W^*
|
||||
inv = np.linalg.pinv(np.dot(Sdg,S))
|
||||
|
||||
# linear inversion of freqs
|
||||
ret = devectorize(np.dot(inv, np.dot(Sdg, v)))
|
||||
# renormalize to input trace value
|
||||
ret = trace * ret / np.trace(ret)
|
||||
return ret
|
||||
|
||||
|
||||
def __state_leastsq_fit(state_data, weights=None, beta=0.5):
|
||||
"""
|
||||
"""
|
||||
ks = state_data[0]['counts'].keys()
|
||||
|
||||
# Get counts and shots
|
||||
ns = np.array([dat['counts'][k] for dat in state_data for k in ks])
|
||||
shots = np.array([dat['shots'] for dat in state_data for k in ks])
|
||||
|
||||
# convert to freqs
|
||||
freqs = (ns + beta) / (shots + 2 * beta)
|
||||
|
||||
# Use standard least squares fitting weights
|
||||
if weights is None:
|
||||
weights = np.sqrt(shots / (freqs * (1 - freqs)))
|
||||
|
||||
# Get measurement basis ops
|
||||
ops = [dat['meas_basis'][k] for dat in state_data for k in ks]
|
||||
|
||||
return __tomo_linear_inv(freqs, ops, weights, trace=1)
|
||||
|
||||
|
||||
def __wizard(rho, epsilon=0.):
|
||||
"""
|
||||
Maps an operator to the nearest positive semidefinite operator
|
||||
by setting negative eigenvalues to zero and rescaling the positive
|
||||
eigenvalues.
|
||||
See arXiv:1106.5458 [quant-ph]
|
||||
"""
|
||||
#print("Using wizard method to constrain positivity")
|
||||
if normalize_flag:
|
||||
rho = rho / np.trace(rho)
|
||||
dim = len(rho)
|
||||
rho_wizard = np.zeros([dim, dim])
|
||||
v, w = np.linalg.eigh(rho) # v eigenvecrors v[0] < v[1] <...
|
||||
v, w = np.linalg.eigh(rho) # v eigenvecrors v[0] < v[1] <...
|
||||
for j in range(dim):
|
||||
if v[j] < epsilon:
|
||||
tmp = v[j]
|
||||
v[j] = 0.
|
||||
# redistribute loop
|
||||
x = 0.
|
||||
for k in range(j+1,dim):
|
||||
for k in range(j + 1, dim):
|
||||
x += tmp / (dim-(j+1))
|
||||
v[k] = v[k] + tmp / (dim -(j+1))
|
||||
v[k] = v[k] + tmp / (dim - (j+1))
|
||||
for j in range(dim):
|
||||
rho_wizard = rho_wizard + v[j] * outer(w[:,j])
|
||||
rho_wizard = rho_wizard + v[j] * outer(w[:, j])
|
||||
return rho_wizard
|
||||
|
||||
def fit_state(freqs, meas_basis, weights=None, normalize_flag = True, wizard_flag = False):
|
||||
|
||||
def fit_state(state_data, method='wizard', options=None):
|
||||
"""
|
||||
Returns a matrix reconstructed by unconstrained least-squares fitting.
|
||||
"""
|
||||
if weights is None:
|
||||
W = np.eye(len(freqs)) # use uniform weights
|
||||
if method in ['wizard', 'leastsq']:
|
||||
rho = __state_leastsq_fit(state_data)
|
||||
if method == 'wizard':
|
||||
rho = __wizard(rho)
|
||||
else:
|
||||
W = np.array(np.diag(weights))
|
||||
S = np.dot(W, meas_basis_matrix(meas_basis)) # actually W.S
|
||||
v = np.dot(W, freqs) # W|f>
|
||||
v = np.array(np.dot(S.T.conj(), v)) # S^*.W^*W.|f>
|
||||
inv = np.linalg.pinv(np.dot(S.T.conj(), S)) # pseudo inverse of S^*.W^*.W.S
|
||||
v = np.dot(inv, v) # |rho>
|
||||
rho = devectorize(v)
|
||||
if normalize_flag:
|
||||
rho = rho / np.trace(rho)
|
||||
if wizard_flag:
|
||||
#rho_wizard = wizard(rho,normalize_flag)
|
||||
rho = wizard(rho, normalize_flag=normalize_flag)
|
||||
return rho
|
||||
|
||||
|
||||
|
||||
###############################################################
|
||||
# Parsing measurement data for reconstruction
|
||||
###############################################################
|
||||
|
||||
def nqubit_basis(n):
|
||||
"""
|
||||
Returns the measurement basis for n-qubits in the correct order for the
|
||||
meas_outcome_strings function.
|
||||
"""
|
||||
b1 = np.array([
|
||||
np.array([[0.5, 0.5], [0.5, 0.5]]), np.array([[0.5, -0.5], [-0.5, 0.5]]), # Xp, Xm
|
||||
np.array([[0.5, -0.5j], [0.5j, 0.5]]), np.array([[0.5, 0.5j], [-0.5j, 0.5]]), # Yp, Ym
|
||||
np.array([[1, 0], [0, 0]]), np.array([[0, 0], [0, 1]]), # Zp, Zm
|
||||
])
|
||||
if n == 1:
|
||||
return b1
|
||||
else:
|
||||
bnm1 = nqubit_basis(n-1)
|
||||
m = 2**(n-1)
|
||||
d = bnm1.size // m**3
|
||||
return np.kron( bnm1.reshape(d, m, m, m), b1.reshape(3, 2, 2, 2)).reshape(3*d * 2*m, 2*m, 2*m)
|
||||
|
||||
def meas_outcome_strings(nq):
|
||||
"""
|
||||
Returns a list of the measurement outcome strings for nq qubits.
|
||||
"""
|
||||
return [bin(j)[2:].zfill(nq) for j in range(2**nq)]
|
||||
|
||||
def tomo_outcome_strings(meas_qubits,nq=None):
|
||||
"""
|
||||
Returns a list of the measurement outcome strings for meas_qubits qubits in an
|
||||
nq qubit system.
|
||||
"""
|
||||
if nq is None:
|
||||
nq = len(meas_qubits)
|
||||
qs = sorted(meas_qubits, reverse=True)
|
||||
outs = meas_outcome_strings(len(qs))
|
||||
res = [];
|
||||
for s in outs:
|
||||
label = ""
|
||||
for j in range(nq):
|
||||
if j in qs:
|
||||
label = s[qs.index(j)] + label
|
||||
else:
|
||||
label = str(0) + label
|
||||
res.append(label)
|
||||
return res
|
||||
|
||||
def none_to_zero(val):
|
||||
"""
|
||||
Returns 0 if the input argument is None, else it returns the input.
|
||||
"""
|
||||
if val is None:
|
||||
return 0
|
||||
else:
|
||||
return val
|
||||
|
||||
###############################################################
|
||||
# Putting it all together
|
||||
###############################################################
|
||||
def state_tomography(Q_program, tomo_circuits, shots, nq,
|
||||
meas_qubits, method='leastsq_wizard'):
|
||||
"""
|
||||
Returns the reconstructed density matrix.
|
||||
"""
|
||||
m = len(meas_qubits)
|
||||
counts = np.array([none_to_zero(Q_program.get_counts(c).get(s))
|
||||
for c in tomo_circuits
|
||||
for s in tomo_outcome_strings(meas_qubits, nq)])
|
||||
if method == 'leastsq_wizard':
|
||||
return fit_state(counts / shots, nqubit_basis(m), normalize_flag=True, wizard_flag=True)
|
||||
elif method == 'least_sq':
|
||||
return fit_state(counts / shots, nqubit_basis(m), normalize_flag=True, wizard_flag=False)
|
||||
else:
|
||||
print("error: unknown reconstruction method")
|
||||
|
||||
|
||||
def state_fidelity(rho, psi):
|
||||
"""Return the state fidelity.
|
||||
|
||||
F between a density matrix rho and a target pure state psi.
|
||||
"""
|
||||
return np.sqrt(np.abs(np.dot(psi, np.dot(rho, psi))))
|
||||
|
||||
def is_pos_def(x):
|
||||
return np.all(np.linalg.eigvals(x) > 0)
|
||||
# TODO: raise exception for unknown method
|
||||
print('error: method unrecongnized')
|
||||
pass
|
||||
|
||||
return rho
|
|
@ -0,0 +1,413 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2017 IBM RESEARCH. All Rights Reserved.
|
||||
#
|
||||
# This file is intended only for use during the USEQIP Summer School 2017.
|
||||
# Do not distribute.
|
||||
# It is provided without warranty or conditions of any kind, either express or
|
||||
# implied.
|
||||
# An open source version of this file will be included in QISKIT-DEV-PY
|
||||
# reposity in the future. Keep an eye on the Github repository for updates!
|
||||
# https://github.com/IBM/qiskit-sdk-py
|
||||
# =============================================================================
|
||||
|
||||
"""
|
||||
Visualization functions for quantum states.
|
||||
|
||||
Author: Jay Gambetta
|
||||
Andrew Cross
|
||||
Christopher J. Wood
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from functools import reduce
|
||||
from scipy import linalg as la
|
||||
import matplotlib.pyplot as plt
|
||||
from mpl_toolkits.mplot3d import proj3d
|
||||
from matplotlib.patches import FancyArrowPatch
|
||||
from tools.pauli import pauli_group, pauli_singles
|
||||
|
||||
|
||||
###############################################################
|
||||
# Plotting tools
|
||||
###############################################################
|
||||
|
||||
class Arrow3D(FancyArrowPatch):
|
||||
"""Standard 3D arrow."""
|
||||
|
||||
def __init__(self, xs, ys, zs, *args, **kwargs):
|
||||
"""Create arrow."""
|
||||
FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs)
|
||||
self._verts3d = xs, ys, zs
|
||||
|
||||
def draw(self, renderer):
|
||||
"""Draw the arrow."""
|
||||
xs3d, ys3d, zs3d = self._verts3d
|
||||
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
|
||||
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
|
||||
FancyArrowPatch.draw(self, renderer)
|
||||
|
||||
|
||||
def plot_bloch_vector(bloch, title=""):
|
||||
"""Plot the Bloch sphere.
|
||||
|
||||
Plot a sphere, axes, the Bloch vector, and its projections onto each axis.
|
||||
|
||||
Args:
|
||||
bloch (list[double]): array of three elements where [<x>, <y>,<z>]
|
||||
title (str): a string that represents the plot title
|
||||
|
||||
Returns:
|
||||
none: plot is shown with matplotlib to the screen
|
||||
|
||||
"""
|
||||
# Set arrow lengths
|
||||
arlen = 1.3
|
||||
|
||||
# Plot semi-transparent sphere
|
||||
u = np.linspace(0, 2 * np.pi, 100)
|
||||
v = np.linspace(0, np.pi, 100)
|
||||
x = np.outer(np.cos(u), np.sin(v))
|
||||
y = np.outer(np.sin(u), np.sin(v))
|
||||
z = np.outer(np.ones(np.size(u)), np.cos(v))
|
||||
|
||||
fig = plt.figure(figsize=(6, 6))
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
ax.set_aspect("equal")
|
||||
ax.plot_surface(x, y, z, color=(.5, .5, .5), alpha=0.1)
|
||||
|
||||
# Plot arrows (axes, Bloch vector, its projections)
|
||||
xa = Arrow3D([0, arlen], [0, 0], [0, 0], mutation_scale=20, lw=1,
|
||||
arrowstyle="-|>", color=(.5, .5, .5))
|
||||
ya = Arrow3D([0, 0], [0, arlen], [0, 0], mutation_scale=20, lw=1,
|
||||
arrowstyle="-|>", color=(.5, .5, .5))
|
||||
za = Arrow3D([0, 0], [0, 0], [0, arlen], mutation_scale=20, lw=1,
|
||||
arrowstyle="-|>", color=(.5, .5, .5))
|
||||
a = Arrow3D([0, bloch[0]], [0, bloch[1]], [0, bloch[2]], mutation_scale=20,
|
||||
lw=2, arrowstyle="simple", color="k")
|
||||
bax = Arrow3D([0, bloch[0]], [0, 0], [0, 0], mutation_scale=20, lw=2,
|
||||
arrowstyle="-", color="r")
|
||||
bay = Arrow3D([0, 0], [0, bloch[1]], [0, 0], mutation_scale=20, lw=2,
|
||||
arrowstyle="-", color="g")
|
||||
baz = Arrow3D([0, 0], [0, 0], [0, bloch[2]], mutation_scale=20, lw=2,
|
||||
arrowstyle="-", color="b")
|
||||
arrowlist = [xa, ya, za, a, bax, bay, baz]
|
||||
for arr in arrowlist:
|
||||
ax.add_artist(arr)
|
||||
|
||||
# Rotate the view
|
||||
ax.view_init(30, 30)
|
||||
|
||||
# Annotate the axes, shifts are ad-hoc for this (30, 30) view
|
||||
xp, yp, _ = proj3d.proj_transform(arlen, 0, 0, ax.get_proj())
|
||||
plt.annotate("x", xy=(xp, yp), xytext=(-3, -8), textcoords='offset points',
|
||||
ha='right', va='bottom')
|
||||
xp, yp, _ = proj3d.proj_transform(0, arlen, 0, ax.get_proj())
|
||||
plt.annotate("y", xy=(xp, yp), xytext=(6, -5), textcoords='offset points',
|
||||
ha='right', va='bottom')
|
||||
xp, yp, _ = proj3d.proj_transform(0, 0, arlen, ax.get_proj())
|
||||
plt.annotate("z", xy=(xp, yp), xytext=(2, 0), textcoords='offset points',
|
||||
ha='right', va='bottom')
|
||||
|
||||
plt.title(title)
|
||||
plt.show()
|
||||
|
||||
|
||||
def plot_state_city(rho, title=""):
|
||||
"""Plot the cityscape of quantum state.
|
||||
|
||||
Plot two 3d bargraphs (two dimenstional) of the mixed state rho
|
||||
|
||||
Args:
|
||||
rho (np.array[[complex]]): array of dimensions 2**n x 2**nn complex
|
||||
numbers
|
||||
title (str): a string that represents the plot title
|
||||
|
||||
Returns:
|
||||
none: plot is shown with matplotlib to the screen
|
||||
|
||||
"""
|
||||
num = int(np.log2(len(rho)))
|
||||
|
||||
# get the real and imag parts of rho
|
||||
datareal = np.real(rho)
|
||||
dataimag = np.imag(rho)
|
||||
|
||||
# get the labels
|
||||
column_names = [bin(i)[2:].zfill(num) for i in range(2**num)]
|
||||
row_names = [bin(i)[2:].zfill(num) for i in range(2**num)]
|
||||
|
||||
lx = len(datareal[0]) # Work out matrix dimensions
|
||||
ly = len(datareal[:, 0])
|
||||
xpos = np.arange(0, lx, 1) # Set up a mesh of positions
|
||||
ypos = np.arange(0, ly, 1)
|
||||
xpos, ypos = np.meshgrid(xpos+0.25, ypos+0.25)
|
||||
|
||||
xpos = xpos.flatten()
|
||||
ypos = ypos.flatten()
|
||||
zpos = np.zeros(lx*ly)
|
||||
|
||||
dx = 0.5 * np.ones_like(zpos) # width of bars
|
||||
dy = dx.copy()
|
||||
dzr = datareal.flatten()
|
||||
dzi = dataimag.flatten()
|
||||
|
||||
fig = plt.figure(figsize=(8, 8))
|
||||
ax1 = fig.add_subplot(2, 1, 1, projection='3d')
|
||||
ax1.bar3d(xpos, ypos, zpos, dx, dy, dzr, color="g", alpha=0.5)
|
||||
ax2 = fig.add_subplot(2, 1, 2, projection='3d')
|
||||
ax2.bar3d(xpos, ypos, zpos, dx, dy, dzi, color="g", alpha=0.5)
|
||||
|
||||
ax1.set_xticks(np.arange(0.5, lx+0.5, 1))
|
||||
ax1.set_yticks(np.arange(0.5, ly+0.5, 1))
|
||||
ax1.axes.set_zlim3d(-1.0, 1.0001)
|
||||
ax1.set_zticks(np.arange(-1, 1, 0.5))
|
||||
ax1.w_xaxis.set_ticklabels(row_names, fontsize=12, rotation=45)
|
||||
ax1.w_yaxis.set_ticklabels(column_names, fontsize=12, rotation=-22.5)
|
||||
# ax1.set_xlabel('basis state', fontsize=12)
|
||||
# ax1.set_ylabel('basis state', fontsize=12)
|
||||
ax1.set_zlabel("Real[rho]")
|
||||
|
||||
ax2.set_xticks(np.arange(0.5, lx+0.5, 1))
|
||||
ax2.set_yticks(np.arange(0.5, ly+0.5, 1))
|
||||
ax2.axes.set_zlim3d(-1.0, 1.0001)
|
||||
ax2.set_zticks(np.arange(-1, 1, 0.5))
|
||||
ax2.w_xaxis.set_ticklabels(row_names, fontsize=12, rotation=45)
|
||||
ax2.w_yaxis.set_ticklabels(column_names, fontsize=12, rotation=-22.5)
|
||||
# ax2.set_xlabel('basis state', fontsize=12)
|
||||
# ax2.set_ylabel('basis state', fontsize=12)
|
||||
ax2.set_zlabel("Imag[rho]")
|
||||
plt.title(title)
|
||||
plt.show()
|
||||
|
||||
|
||||
def plot_state_paulivec(rho, title=""):
|
||||
"""Plot the paulivec representation of a quantum state.
|
||||
|
||||
Plot a bargraph of the mixed state rho over the pauli matricies
|
||||
|
||||
Args:
|
||||
rho (np.array[[complex]]): array of dimensions 2**n x 2**nn complex
|
||||
numbers
|
||||
title (str): a string that represents the plot title
|
||||
|
||||
Returns:
|
||||
none: plot is shown with matplotlib to the screen
|
||||
|
||||
"""
|
||||
num = int(np.log2(len(rho)))
|
||||
labels = list(map(lambda x: x.to_label(), pauli_group(num)))
|
||||
values = list(map(lambda x: np.real(np.trace(np.dot(x.to_matrix(), rho))),
|
||||
pauli_group(num)))
|
||||
numelem = len(values)
|
||||
ind = np.arange(numelem) # the x locations for the groups
|
||||
width = 0.5 # the width of the bars
|
||||
fig, ax = plt.subplots()
|
||||
ax.grid(zorder=0)
|
||||
ax.bar(ind, values, width, color='seagreen')
|
||||
|
||||
# add some text for labels, title, and axes ticks
|
||||
ax.set_ylabel('Expectation value', fontsize=12)
|
||||
ax.set_xticks(ind)
|
||||
ax.set_yticks([-1, -0.5, 0, 0.5, 1])
|
||||
ax.set_xticklabels(labels, fontsize=12, rotation=70)
|
||||
ax.set_xlabel('Pauli', fontsize=12)
|
||||
ax.set_ylim([-1, 1])
|
||||
plt.title(title)
|
||||
plt.show()
|
||||
|
||||
|
||||
def n_choose_k(n, k):
|
||||
"""Return the number of combinations for n choose k.
|
||||
|
||||
Args:
|
||||
n (int): the total number of options .
|
||||
k (int): The number of elements.
|
||||
|
||||
Returns:
|
||||
int: returns the binomial coefficient
|
||||
|
||||
"""
|
||||
if n == 0:
|
||||
return 0
|
||||
else:
|
||||
return reduce(lambda x, y: x * y[0] / y[1],
|
||||
zip(range(n - k + 1, n + 1),
|
||||
range(1, k + 1)), 1)
|
||||
|
||||
|
||||
def lex_index(n, k, lst):
|
||||
"""Return the lex index of a combination..
|
||||
|
||||
Args:
|
||||
n (int): the total number of options .
|
||||
k (int): The number of elements.
|
||||
lst
|
||||
|
||||
Returns:
|
||||
int: returns int index for lex order
|
||||
|
||||
"""
|
||||
assert len(lst) == k, "list should have length k"
|
||||
comb = list(map(lambda x: n - 1 - x, lst))
|
||||
dualm = sum([n_choose_k(comb[k - 1 - i], i + 1) for i in range(k)])
|
||||
return int(dualm)
|
||||
|
||||
|
||||
def bit_string_index(s):
|
||||
"""Return the index of a string of 0s and 1s."""
|
||||
n = len(s)
|
||||
k = s.count("1")
|
||||
assert s.count("0") == n - k, "s must be a string of 0 and 1"
|
||||
ones = [pos for pos, char in enumerate(s) if char == "1"]
|
||||
return lex_index(n, k, ones)
|
||||
|
||||
|
||||
def phase_to_color_wheel(complex_number):
|
||||
"""Map a phase of a complexnumber to a color in (r,g,b).
|
||||
|
||||
complex_number is phase is first mapped to angle in the range
|
||||
[0, 2pi] and then to a color wheel with blue at zero phase.
|
||||
"""
|
||||
angles = np.angle(complex_number)
|
||||
angle_round = int(((angles + 2 * np.pi) % (2 * np.pi))/np.pi*6)
|
||||
# print(angleround)
|
||||
color_map = {0: (0, 0, 1), # blue,
|
||||
1: (0.5, 0, 1), # blue-violet
|
||||
2: (1, 0, 1), # violet
|
||||
3: (1, 0, 0.5), # red-violet,
|
||||
4: (1, 0, 0), # red
|
||||
5: (1, 0.5, 0), # red-oranage,
|
||||
6: (1, 1, 0), # orange
|
||||
7: (0.5, 1, 0), # orange-yellow
|
||||
8: (0, 1, 0), # yellow,
|
||||
9: (0, 1, 0.5), # yellow-green,
|
||||
10: (0, 1, 1), # green,
|
||||
11: (0, 0.5, 1) # green-blue,
|
||||
}
|
||||
return color_map[angle_round]
|
||||
|
||||
|
||||
def plot_state_qsphere(rho):
|
||||
"""Plot the qsphere representation of a quantum state."""
|
||||
num = int(np.log2(len(rho)))
|
||||
# get the eigenvectors and egivenvalues
|
||||
we, stateall = la.eigh(rho)
|
||||
for i in range(2**num):
|
||||
# start with the max
|
||||
probmix = we.max()
|
||||
prob_location = we.argmax()
|
||||
if probmix > 0.001:
|
||||
print("The " + str(i) + "th eigenvalue = " + str(probmix))
|
||||
# get the max eigenvalue
|
||||
state = stateall[:, prob_location]
|
||||
loc = np.absolute(state).argmax()
|
||||
# get the element location closes to lowest bin representation.
|
||||
for j in range(2**num):
|
||||
test = np.absolute(np.absolute(state[j])
|
||||
- np.absolute(state[loc]))
|
||||
if test < 0.001:
|
||||
loc = j
|
||||
break
|
||||
# remove the global phase
|
||||
angles = (np.angle(state[loc]) + 2 * np.pi) % (2 * np.pi)
|
||||
angleset = np.exp(-1j*angles)
|
||||
# print(state)
|
||||
# print(angles)
|
||||
state = angleset*state
|
||||
# print(state)
|
||||
state.flatten()
|
||||
# start the plotting
|
||||
fig = plt.figure(figsize=(10, 10))
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
ax.axes.set_xlim3d(-1.0, 1.0)
|
||||
ax.axes.set_ylim3d(-1.0, 1.0)
|
||||
ax.axes.set_zlim3d(-1.0, 1.0)
|
||||
ax.set_aspect("equal")
|
||||
ax.axes.grid(False)
|
||||
# Plot semi-transparent sphere
|
||||
u = np.linspace(0, 2 * np.pi, 25)
|
||||
v = np.linspace(0, np.pi, 25)
|
||||
x = np.outer(np.cos(u), np.sin(v))
|
||||
y = np.outer(np.sin(u), np.sin(v))
|
||||
z = np.outer(np.ones(np.size(u)), np.cos(v))
|
||||
ax.plot_surface(x, y, z, rstride=1, cstride=1, color='k',
|
||||
alpha=0.05, linewidth=0)
|
||||
# wireframe
|
||||
# Get rid of the panes
|
||||
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
||||
ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
||||
ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))
|
||||
|
||||
# Get rid of the spines
|
||||
ax.w_xaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
|
||||
ax.w_yaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
|
||||
ax.w_zaxis.line.set_color((1.0, 1.0, 1.0, 0.0))
|
||||
# Get rid of the ticks
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
ax.set_zticks([])
|
||||
|
||||
d = num
|
||||
for i in range(2**num):
|
||||
# get x,y,z points
|
||||
element = bin(i)[2:].zfill(num)
|
||||
weight = element.count("1")
|
||||
zvalue = -2 * weight / d + 1
|
||||
number_of_divisions = n_choose_k(d, weight)
|
||||
weight_order = bit_string_index(element)
|
||||
# if weight_order >= number_of_divisions / 2:
|
||||
# com_key = compliment(element)
|
||||
# weight_order_temp = bit_string_index(com_key)
|
||||
# weight_order = np.floor(
|
||||
# number_of_divisions / 2) + weight_order_temp + 1
|
||||
angle = (weight_order) * 2 * np.pi / number_of_divisions
|
||||
xvalue = np.sqrt(1 - zvalue**2) * np.cos(angle)
|
||||
yvalue = np.sqrt(1 - zvalue**2) * np.sin(angle)
|
||||
ax.plot([xvalue], [yvalue], [zvalue],
|
||||
markerfacecolor=(.5, .5, .5),
|
||||
markeredgecolor=(.5, .5, .5),
|
||||
marker='o', markersize=10, alpha=1)
|
||||
# get prob and angle - prob will be shade and angle color
|
||||
prob = np.real(np.dot(state[i], state[i].conj()))
|
||||
colorstate = phase_to_color_wheel(state[i])
|
||||
a = Arrow3D([0, xvalue], [0, yvalue], [0, zvalue],
|
||||
mutation_scale=20, alpha=prob, arrowstyle="-",
|
||||
color=colorstate, lw=10)
|
||||
ax.add_artist(a)
|
||||
# add weight lines
|
||||
for weight in range(d + 1):
|
||||
theta = np.linspace(-2 * np.pi, 2 * np.pi, 100)
|
||||
z = -2 * weight / d + 1
|
||||
r = np.sqrt(1 - z**2)
|
||||
x = r * np.cos(theta)
|
||||
y = r * np.sin(theta)
|
||||
ax.plot(x, y, z, color=(.5, .5, .5))
|
||||
# add center point
|
||||
ax.plot([0], [0], [0], markerfacecolor=(.5, .5, .5),
|
||||
markeredgecolor=(.5, .5, .5), marker='o', markersize=10,
|
||||
alpha=1)
|
||||
plt.show()
|
||||
we[prob_location] = 0
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def plot_state(rho, method='city'):
|
||||
"""Plot the quantum state."""
|
||||
num = int(np.log2(len(rho)))
|
||||
# Need updating to check its a matrix
|
||||
if method == 'city':
|
||||
plot_state_city(rho)
|
||||
elif method == "paulivec":
|
||||
plot_state_paulivec(rho)
|
||||
elif method == "qsphere":
|
||||
plot_state_qsphere(rho)
|
||||
elif method == "bloch":
|
||||
for i in range(num):
|
||||
bloch_state = list(map(lambda x: np.real(np.trace(
|
||||
np.dot(x.to_matrix(), rho))),
|
||||
pauli_singles(i, num)))
|
||||
plot_bloch_vector(bloch_state, "qubit " + str(i))
|
||||
else:
|
||||
print("No method given")
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue