try:
from matplotlib import animation
import matplotlib.pyplot as plt
from matplotlib import get_backend
from mpl_toolkits.mplot3d import Axes3D
plt.switch_backend('TKAgg')
HAS_MATPLOTLIB = True
except:
HAS_MATPLOTLIB = False
pass
import numpy as np
from numpy import pi
from scipy import sparse
from .quantum_state_plot import state_to_density_matrix
from pyqpanda import circuit_layer
from math import sin, cos, acos, sqrt
from .bloch import Bloch
import matplotlib
[文档]
def count_pauli(i):
"""
Counts the number of set bits (1s) in the binary representation of the integer `i`.
This method utilizes bitwise operations to efficiently count the number of Pauli
spin flips, which is a concept relevant in quantum computing when analyzing quantum
states.
Args:
i (int):
The integer whose set bits are to be counted.
Returns:
int:
The count of set bits in the binary representation of `i`.
Note:
The function is optimized for performance using bitwise manipulation and is
specifically designed for use within the pyQPanda package, which facilitates
quantum computing programming and simulation.
"""
i = i - ((i >> 1) & 0x55555555)
i = (i & 0x33333333) + ((i >> 2) & 0x33333333)
return (((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) & 0xffffffff) >> 24
[文档]
class pauli(object):
def __init__(self, z, x):
[文档]
def to_matrix(self):
_x, _z = self.x, self.z
n = 2**len(_x)
twos_array = 1 << np.arange(len(_x))
xs = np.array(_x).dot(twos_array)
zs = np.array(_z).dot(twos_array)
rows = np.arange(n+1, dtype=np.uint)
columns = rows ^ xs
global_factor = (-1j)**np.dot(np.array(_x, dtype=np.uint), _z)
data = global_factor*(-1)**np.mod(count_pauli(zs & rows), 2)
matrix = sparse.csr_matrix((data, columns, rows), shape=(n, n))
return matrix.toarray()
[文档]
def get_single_paulis(num_qubits, index):
"""
Generates a list of single-qubit Pauli operators for a specified number of qubits.
Each Pauli operator corresponds to the X, Y, or Z Pauli gate, and is represented as a tuple of two boolean arrays:
- The first array indicates the qubit indices where the Z component of the Pauli operator is non-zero.
- The second array indicates the qubit indices where the X component of the Pauli operator is non-zero.
Args:
num_qubits (int):
The total number of qubits in the quantum system.
index (int):
The index of the qubit for which the Pauli operator is to be generated.
Returns:
list of tuples:
A list containing tuples, each representing a single-qubit Pauli operator.
Raises:
RuntimeError:
If an invalid Pauli gate label ('Y' or 'I') is encountered.
The function is designed to be used within the pyQPanda package for quantum circuit simulations and quantum computations.
"""
labels = ['X', 'Y', 'Z']
single_pauli = []
for label in labels:
z = np.zeros(len(label), dtype=np.bool)
x = np.zeros(len(label), dtype=np.bool)
for i, char in enumerate(label):
if char == 'X':
x[-i - 1] = True
elif char == 'Z':
z[-i - 1] = True
elif char == 'Y':
z[-i - 1] = True
x[-i - 1] = True
elif char != 'I':
raise RuntimeError("Pauli error")
tmp = pauli(z, x)
pauli_z = np.zeros(num_qubits, dtype=np.bool)
pauli_x = np.zeros(num_qubits, dtype=np.bool)
pauli_z[index] = tmp.z[0]
pauli_x[index] = tmp.x[0]
single_pauli.append(pauli(pauli_z, pauli_x))
return single_pauli
[文档]
def plot_bloch_vector(bloch, title="bloch", axis_obj=None, fig_size=None):
"""
Visualizes the evolution of a quantum circuit on a Bloch sphere.
Args:
circuit (object):
The quantum circuit to be visualized.
trace (bool):
Flag to indicate whether to display the path of the state vector.
saveas (str):
The filename to save the animation. If None, the animation is displayed.
fps (int):
The frames per second for the animation.
secs_per_gate (float):
The duration in seconds for each gate operation.
Returns:
None:
The function does not return a value; it generates a visualization.
Raises:
ImportError:
If Matplotlib is not installed.
RuntimeError:
If the circuit contains no gates that can be visualized on a Bloch sphere.
"""
if not HAS_MATPLOTLIB:
raise ImportError('Must have Matplotlib installed')
if fig_size is None:
fig_size = (5, 5)
bloch_obj = Bloch(axes=axis_obj)
bloch_obj.add_vectors(bloch)
bloch_obj.render(title=title)
if axis_obj is None:
fig = bloch_obj.fig
fig.set_size_inches(fig_size[0], fig_size[1])
if get_backend() in ['module://ipykernel.pylab.backend_inline',
'nbAgg']:
plt .close(fig)
plt.show()
return fig
return None
[文档]
def plot_bloch_multivector(state, title='', fig_size=None):
"""
Visualizes a quantum state on a Bloch sphere.
Args:
state (list):
The quantum state vector.
title (str):
The title for the plot.
fig_size (tuple):
The size of the figure in inches.
Returns:
matplotlib.figure.Figure:
The created figure with the Bloch plot.
Raises:
ImportError:
If Matplotlib is not installed.
Description:
This function converts a quantum state vector into a Bloch sphere representation.
It generates a figure showing the Bloch vector for each qubit in the state vector.
The figure size can be customized, and the plot includes a title. If the backend
is inline or notebook, the figure is automatically closed after display.
"""
if not HAS_MATPLOTLIB:
raise ImportError('Must have Matplotlib installed')
# get density matrix from quantum state vector
state = state_to_density_matrix(state)
print(state)
num = int(np.log2(len(state)))
width, height = plt.figaspect(1/num)
fig = plt.figure(figsize=(width, height))
for qubit in range(num):
axis_obj = fig.add_subplot(1, num, qubit + 1, projection='3d')
pauli_singles = get_single_paulis(num, qubit)
bloch_state = list(
map(lambda x: np.real(np.trace(np.dot(x.to_matrix(), state))),
pauli_singles))
plot_bloch_vector(bloch_state, "qubit " + str(qubit), axis_obj=axis_obj,
fig_size=fig_size)
fig.suptitle(title, fontsize=16)
if get_backend() in ['module://ipykernel.pylab.backend_inline',
'nbAgg']:
plt.close(fig)
plt.show()
return fig
[文档]
def normalize(v, tolerance=0.00001):
"""
Normalizes a given vector to have a magnitude of 1, using a specified tolerance for
determining the vector's magnitude. The vector is expected to be a tuple of numbers.
Args:
v (tuple):
The vector to be normalized.
tolerance (float, optional):
The tolerance level within which the vector's magnitude
is considered to be 1. Defaults to 0.00001.
Returns:
np.array:
The normalized vector as a NumPy array.
"""
mag2 = sum(n * n for n in v)
if abs(mag2 - 1.0) > tolerance:
mag = sqrt(mag2)
v = tuple(n / mag for n in v)
return np.array(v)
[文档]
class gate_node(object):
def __init__(self, name, theta):
[文档]
class PlotVector:
def __init__(self):
self._val = None
@staticmethod
[文档]
def from_axisangle(theta, v):
v = normalize(v)
new_quaternion = PlotVector()
new_quaternion._axisangle_to_q(theta, v)
return new_quaternion
@staticmethod
[文档]
def from_value(value):
new_quaternion = PlotVector()
new_quaternion._val = value
return new_quaternion
def _axisangle_to_q(self, theta, v):
x = v[0]
y = v[1]
z = v[2]
w = cos(theta/2.)
x = x * sin(theta/2.)
y = y * sin(theta/2.)
z = z * sin(theta/2.)
self._val = np.array([w, x, y, z])
def __mul__(self, b):
if isinstance(b, PlotVector):
return self._multiply_with_quaternion(b)
elif isinstance(b, (list, tuple, np.ndarray)):
if len(b) != 3:
raise RuntimeError(
"Input vector has invalid length {0}".format(len(b)))
return self._multiply_with_vector(b)
else:
raise RuntimeError(
"Multiplication with unknown type {0}".format(type(b)))
def _multiply_with_quaternion(self, q_2):
w_1, x_1, y_1, z_1 = self._val
w_2, x_2, y_2, z_2 = q_2._val
w = w_1 * w_2 - x_1 * x_2 - y_1 * y_2 - z_1 * z_2
x = w_1 * x_2 + x_1 * w_2 + y_1 * z_2 - z_1 * y_2
y = w_1 * y_2 + y_1 * w_2 + z_1 * x_2 - x_1 * z_2
z = w_1 * z_2 + z_1 * w_2 + x_1 * y_2 - y_1 * x_2
result = PlotVector.from_value(np.array((w, x, y, z)))
return result
def _multiply_with_vector(self, v):
q_2 = PlotVector.from_value(np.append((0.0), v))
return (self * q_2 * self.get_conjugate())._val[1:]
[文档]
def get_conjugate(self):
w, x, y, z = self._val
result = PlotVector.from_value(np.array((w, -x, -y, -z)))
return result
def __repr__(self):
theta, v = self.get_axisangle()
return "(({0}; {1}, {2}, {3}))".format(theta, v[0], v[1], v[2])
[文档]
def get_axisangle(self):
w, v = self._val[0], self._val[1:]
theta = acos(w) * 2.0
return theta, normalize(v)
[文档]
def tolist(self):
return self._val.tolist()
[文档]
def vector_norm(self):
_, v = self.get_axisangle()
return np.linalg.norm(v)
[文档]
def bloch_plot_dict(frames_per_gate):
"""
Generates a dictionary of PlotVector instances representing quantum states on the Bloch sphere.
Each key in the dictionary corresponds to a specific quantum state or operation, and the value is a tuple
containing the state name, the PlotVector object representing the state, and an associated color.
Args:
frames_per_gate (int):
The number of frames per gate used to define the rotation of quantum states on the
Bloch sphere. This parameter affects the angle of rotation for each state.
Returns:
dict:
A dictionary where each key is a string representing a quantum state or operation, and the value
is a tuple with the state name, a PlotVector object, and its corresponding color.
The function is intended for use within the pyQPanda package, which is designed for programming quantum computers,
enabling quantum circuit simulation and operation via quantum virtual machines or quantum cloud services.
"""
bloch_dict = dict()
bloch_dict['x'] = ('x', PlotVector.from_axisangle(
np.pi / frames_per_gate, [1, 0, 0]), '#1a8bbc')
bloch_dict['y'] = ('y', PlotVector.from_axisangle(
np.pi / frames_per_gate, [0, 1, 0]), '#c02ecc')
bloch_dict['z'] = ('z', PlotVector.from_axisangle(
np.pi / frames_per_gate, [0, 0, 1]), '#db7734')
bloch_dict['s'] = ('s', PlotVector.from_axisangle(np.pi / 2 / frames_per_gate,
[0, 0, 1]), '#9b59b6')
bloch_dict['sdg'] = ('sdg', PlotVector.from_axisangle(-np.pi / 2 / frames_per_gate, [0, 0, 1]),
'#8e44ad')
bloch_dict['h'] = ('h', PlotVector.from_axisangle(np.pi / frames_per_gate, normalize([1, 0, 1])),
'#a96386')
bloch_dict['t'] = ('t', PlotVector.from_axisangle(np.pi / 4 / frames_per_gate, [0, 0, 1]),
'#e74c3c')
bloch_dict['tdg'] = ('tdg', PlotVector.from_axisangle(-np.pi / 4 / frames_per_gate, [0, 0, 1]),
'#c0392b')
return bloch_dict
[文档]
def traversal_circuit(circuit):
"""
Traverse a quantum circuit and construct a list of quantum gates based on the circuit's layers.
Args:
circuit (object):
A quantum circuit object that contains the structure and operations of the quantum circuit.
Returns:
list:
A list of quantum gates represented as objects, constructed from the input circuit.
Raises:
RuntimeError:
If the input circuit is empty or contains unsupported gates, or if it does not support multiple qubits.
The function is designed to work within the pyQPanda package, which is utilized for programming quantum computers.
It processes the quantum circuit to convert it into a list of quantum gates that can be further used for simulation or
execution on quantum virtual machines or quantum cloud services.
"""
origin_gates = ['H', 'X', 'Y', 'Z', 'RX', 'RY', 'RZ', 'S', 'T', 'U1']
layers = circuit_layer(circuit)[0]
if 0 == len(layers):
raise RuntimeError("empty circuit")
def get_layer_qubit_addr(node):
return node.m_target_qubits[0].get_phy_addr()
trans = 180. / pi
cir = []
tar_qubit = layers[0][0].m_target_qubits[0].get_phy_addr()
for layer in layers:
if get_layer_qubit_addr(layer[0]) != tar_qubit:
raise RuntimeError("only one qubit circuits are supported")
if layer[0].m_name not in origin_gates:
raise RuntimeError("un supported gate".format(gate[0].name))
if layer[0].m_name == 'H':
cir.append(gate_node('h', 0))
if layer[0].m_name == 'X':
cir.append(gate_node('x', 0))
if layer[0].m_name == 'Y':
cir.append(gate_node('y', 0))
if layer[0].m_name == 'Z':
cir.append(gate_node('z', 0))
if layer[0].m_name == 'U1':
cir.append(gate_node('u1', np.round(layer[0].m_params[0] * trans)))
if layer[0].m_name == 'RX':
cir.append(gate_node('rx', np.round(layer[0].m_params[0] * trans)))
if layer[0].m_name == 'RY':
cir.append(gate_node('ry', np.round(layer[0].m_params[0] * trans)))
if layer[0].m_name == 'RZ':
cir.append(gate_node('rz', np.round(layer[0].m_params[0] * trans)))
if layer[0].m_name == 'T' and (1 - layer[0].m_is_dagger):
cir.append(gate_node('t', 0))
if layer[0].m_name == 'T' and layer[0].m_is_dagger:
cir.append(gate_node('tdg', 0))
if layer[0].m_name == 'S' and (1 - layer[0].m_is_dagger):
cir.append(gate_node('s', 0))
if layer[0].m_name == 'S' and layer[0].m_is_dagger:
cir.append(gate_node('sdg', 0))
return cir
[文档]
def plot_bloch_circuit(circuit,
trace=True,
saveas=None,
fps=20,
secs_per_gate=1):
"""
Visualizes the evolution of a quantum circuit on a Bloch sphere.
Args:
circuit (object):
The quantum circuit to be visualized.
trace (bool):
Flag to indicate whether to display the path of the state vector.
saveas (str):
The filename to save the animation. If None, the animation is displayed.
fps (int):
The frames per second for the animation.
secs_per_gate (float):
The duration in seconds for each gate operation.
Returns:
None:
The function does not return a value; it generates a visualization.
Raises:
ImportError:
If Matplotlib is not installed.
RuntimeError:
If the circuit contains no gates that can be visualized on a Bloch sphere.
"""
if not HAS_MATPLOTLIB:
raise ImportError("Must have Matplotlib installed.")
frames_per_gate = fps
time_between_frames = (secs_per_gate * 1000) / fps
plot_cir = traversal_circuit(circuit)
plot_dict = bloch_plot_dict(frames_per_gate)
simple_gates = ['h', 'x', 'y', 'z', 's', 'sdg', 't', 'tdg']
list_of_circuit_gates = []
for gate in plot_cir:
if gate.name in simple_gates:
list_of_circuit_gates.append(plot_dict[gate.name])
else:
theta = gate.theta
rad = np.deg2rad(theta)
if gate.name == 'rx':
quaternion = PlotVector.from_axisangle(
rad / frames_per_gate, [1, 0, 0])
list_of_circuit_gates.append(
('rx:'+str(theta), quaternion, '#f39c12'))
elif gate.name == 'ry':
quaternion = PlotVector.from_axisangle(
rad / frames_per_gate, [0, 1, 0])
list_of_circuit_gates.append(
('ry:'+str(theta), quaternion, '#0c93bf'))
elif gate.name == 'rz':
quaternion = PlotVector.from_axisangle(
rad / frames_per_gate, [0, 0, 1])
list_of_circuit_gates.append(
('rz:'+str(theta), quaternion, '#a06aad'))
elif gate.name == 'u1':
quaternion = PlotVector.from_axisangle(
rad / frames_per_gate, [0, 0, 1])
list_of_circuit_gates.append(
('u1:'+str(theta), quaternion, '#69a45a'))
if len(list_of_circuit_gates) == 0:
raise RuntimeError("Nothing to visualize.")
starting_pos = normalize(np.array([0, 0, 1]))
view=[-60,30]
fig = plt.figure(figsize=(6, 6))
if tuple(int(x) for x in matplotlib.__version__.split(".")) >= (3, 4, 0):
_ax = Axes3D(
fig, azim=view[0], elev=view[1], auto_add_to_figure=False
)
fig.add_axes(_ax)
else:
_ax = Axes3D(
fig,
azim=view[0],
elev=view[1],
)
sphere = Bloch(axes=_ax)
class PlotParams:
def __init__(self):
self.new_vec = []
self.last_gate = -2
self.colors = []
self.pnts = []
plot_parms = PlotParams()
plot_parms.new_vec = starting_pos
def plot_flash(i):
sphere.clear()
gate_counter = (i-1) // frames_per_gate
if gate_counter != plot_parms.last_gate:
plot_parms.pnts.append([[], [], []])
plot_parms.colors.append(list_of_circuit_gates[gate_counter][2])
if i == 0:
sphere.add_vectors(plot_parms.new_vec)
plot_parms.pnts[0][0].append(plot_parms.new_vec[0])
plot_parms.pnts[0][1].append(plot_parms.new_vec[1])
plot_parms.pnts[0][2].append(plot_parms.new_vec[2])
plot_parms.colors[0] = 'r'
sphere.make_sphere()
return _ax
plot_parms.new_vec = list_of_circuit_gates[gate_counter][1] * \
plot_parms.new_vec
plot_parms.pnts[gate_counter+1][0].append(plot_parms.new_vec[0])
plot_parms.pnts[gate_counter+1][1].append(plot_parms.new_vec[1])
plot_parms.pnts[gate_counter+1][2].append(plot_parms.new_vec[2])
sphere.add_vectors(plot_parms.new_vec)
if trace:
for point_set in plot_parms.pnts:
sphere.add_points([point_set[0], point_set[1], point_set[2]])
sphere.vector_color = [list_of_circuit_gates[gate_counter][2]]
sphere.point_color = plot_parms.colors
sphere.point_marker = 'o'
annotation_text = list_of_circuit_gates[gate_counter][0]
annotationvector = [1.4, -0.45, 1.7]
sphere.add_annotation(annotationvector,
annotation_text,
color=list_of_circuit_gates[gate_counter][2],
fontsize=30,
horizontalalignment='left')
sphere.make_sphere()
plot_parms.last_gate = gate_counter
return _ax
def init():
sphere.vector_color = ['r']
return _ax
ani = animation.FuncAnimation(fig, plot_flash,
range(frames_per_gate *
len(list_of_circuit_gates)+1),
init_func=init,
blit=False,
repeat=False,
interval=time_between_frames)
if saveas:
ani.save(saveas, fps=30)
plt.show()
plt.close(fig)
return None