Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stable/0.3: some small fixes, add visualization of Bloch Sphere #124

Merged
merged 5 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/source/apiref/apiref_0.3.5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ API Reference

This page is still under refinement, the contents you see may be incomplete at present.

.. _quantum_circuit:

Quantum Circuit
------------------

Expand All @@ -17,7 +19,7 @@ Quantum Elements
.. hint::
hello

.. autoclass:: quafu.elements.quantum_element.Instruction
.. autoclass:: quafu.elements.Instruction
:members:


Expand Down
31 changes: 14 additions & 17 deletions doc/source/user_guide/quafu_doc_0.3.5/quafu_doc_0_3_5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Set up your Quafu account

If you haven’t have an account, you may register on the
`Quafu <http://quafu.baqis.ac.cn/>`__ website at first. Then you will
find your apitoken ``<your API token>``\ on the ``Dashboard`` page,
find your apitoken ``<your API token>`` on the ``Dashboard`` page,
which is required when you send tasks to ScQ-chips.

By executing the following codes your token will be saved to your local
Expand Down Expand Up @@ -98,12 +98,11 @@ circuit.
gate = qeg.XGate(pos=0)
qc.add_gate(gate)

This is actually what ’\ ``.name(args)`` functions do. You would find
This is actually what ``.name(args)`` functions do. You would find
the second style convenient when build a new circuit from existing one.

For quantum gates Quafu supports, please check the API reference for
```QuantumCircuit`` <apiref/#quafu.QuantumCircuit>`__ or use
python-buitin ``dir()`` method.
For quantum gates Quafu supports, please check the API reference for :ref:`quantum_circuit`
or use python-buitin ``dir()`` method.

.. code:: python

Expand Down Expand Up @@ -136,8 +135,7 @@ Visualize

From ``version=0.3.2``, ``PyQuafu`` provides two similiar ways to
visualize quantum circuits. You can draw the circuit using the
```draw_circuit`` <apiref/#quafu.circuits.quantum_circuit.QuantumCircuit.draw_circuit>`__
method and use ``width`` parameter to adjust the length of the circuit.
``draw_circuit`` method and use ``width`` parameter to adjust the length of the circuit.

.. code:: python

Expand Down Expand Up @@ -224,7 +222,7 @@ First, initialize a Task object
task = Task()

You can configure your task properties using the
```config`` <apiref/#quafu.tasks.tasks.Task.config>`__ method. Here we
``config`` method. Here we
choose the backend (``backend``) as ``ScQ-P18``, the single shots number
(``shots``) as 2000 and compile the circuit on the backend
(``compile``).
Expand Down Expand Up @@ -286,8 +284,7 @@ provide simple circuit similator

If you don’t want to plot the results for basis with zero probabilities,
set the parameter ``full`` in method
```plot_probabilities`` <apiref/#quafu.results.results.SimuResult.plot_probabilities>`__
to False. Note that this parameter is only valid for results returned by
``plot_probabilities`` to False. Note that this parameter is only valid for results returned by
the simulator.

A Subtle detail
Expand Down Expand Up @@ -347,11 +344,11 @@ Measure observables

Quafu provides measuring observables with an executed quantum circuit.
You can input Pauli operators that need to measure expectation values to
the ```submit`` <apiref/#quafu.tasks.tasks.Task.submit>`__ method. For
the ``submit`` <apiref/#quafu.tasks.tasks.Task.submit>`__ method. For
example, you can input [[“XYX”, [0, 1, 2]], [“Z”, [1]]] to calculate the
expectation of operators :math:`\sigma^x_0\sigma^y_1\sigma^x_2` and
:math:`\sigma^z_1`. The
```submit`` <apiref/#quafu.tasks.tasks.Task.submit>`__ method will
``submit`` <apiref/#quafu.tasks.tasks.Task.submit>`__ method will
minimize the executing times of the circuit with different measurement
basis that can calculate all expectations of input operators.

Expand Down Expand Up @@ -387,7 +384,7 @@ First, we initialize a circuit with three Hadamard gate

Next, we set operators that need to be measured to calculate the energy
expectation, and submit the circuit using
```submit`` <apiref/#quafu.tasks.tasks.Task.submit>`__ method
``submit`` method

.. code:: python

Expand Down Expand Up @@ -438,7 +435,7 @@ Submit task asynchronously

In the above examples, we chose opening python kernal and waiting for
the result. You may also set the ``wait=False`` in
```send`` <apiref/#quafu.tasks.tasks.Task.send>`__ function to submit
``send`` function to submit
the task asynchronously. Here we use another example that measures the
qubit decoherence time :math:`T_1` to demonstrate the usage.

Expand All @@ -461,13 +458,13 @@ Prepare parameters of a group of tasks and send the task asynchronously.
res = task.send(q, wait=False, name=name, group="Q3_T1")

Here the ``delay`` options will idle the target qubit ``2`` for a
duration ``t`` in the time unit ``us``\ (microsecond) and do nothing. In
duration ``t`` in the time unit ``us`` (microsecond) and do nothing. In
the send function, we set ``wait`` to false to execute the task
asynchronously, give each task a name by duration time and set all tasks
to a group named Q3_T1.
to a group named "Q3_T1".

Now we can try to retrieve the group of tasks using the
```retrieve_group`` <apiref/#quafu.tasks.tasks.Task.retrieve_group>`__
``retrieve_group``
method.

.. code:: python
Expand Down
2 changes: 1 addition & 1 deletion src/quafu/elements/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def __init__(self, pos):

@property
def named_pos(self):
return self.named_pos
return {'pos': self.pos}

@property
def named_paras(self):
Expand Down
1 change: 1 addition & 0 deletions src/quafu/elements/matrices/mat_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def reorder_matrix(matrix: np.ndarray, pos: List):
tensorm = matrix.reshape([2] * 2 * qnum)
return np.transpose(tensorm, inds).reshape([dim, dim])


def split_matrix(matrix: ndarray):
"""
Evenly split a matrix into 4 sub-matrices.
Expand Down
119 changes: 119 additions & 0 deletions src/quafu/elements/oracle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from abc import ABCMeta
from quafu.elements import QuantumGate, Instruction
from typing import Dict, Iterable, List
import copy


class OracleGateMeta(ABCMeta):
"""
Metaclass to create OracleGate CLASS which is its instance.
"""

def __init__(cls, name, bases, attrs):
for attr_name in ['cls_name', 'gate_structure', 'qubit_num']:
assert attr_name in attrs, f"OracleGateMeta: {attr_name} not found in {attrs}."

# TODO: check if instructions inside gate_structure are valid

super().__init__(name, bases, attrs)
cls.name = attrs.__getitem__('cls_name')
cls.gate_structure = attrs.__getitem__('gate_structure')
cls.qubit_num = attrs.__getitem__('qubit_num')


class OracleGate(QuantumGate): # TODO: Can it be related to OracleGateMeta explicitly?
"""
OracleGate is a gate that can be customized by users.
"""
name = None
gate_structure = []
qubit_num = 0

_named_pos = {}
insides = []

def __init__(self, pos: List, paras=None, label: str = None):
"""
Args:
pos: position of the gate
paras: parameters of the gate # TODO: how to set paras?
label: label when draw or plot
"""
if not self.qubit_num == len(pos):
raise ValueError(f"OracleGate: qubit number {self.qubit_num} does not match pos length {len(pos)}.")
super().__init__(pos=pos, paras=paras)

self.__instantiate_gates__()
self.label = label if label is not None else self.name

@property
def matrix(self):
# TODO: this should be finished according to usage in simulation
# to avoid store very large matrix
raise NotImplemented

@property
def named_pos(self) -> Dict:
return {'pos': self.pos}

@property
def named_paras(self) -> Dict:
# TODO: how to manage paras and the names?
return self._named_pos

def to_qasm(self):
# TODO: this is similar to QuantumCircuit.to_qasm
raise NotImplemented

def __instantiate_gates__(self) -> None:
"""
Instantiate the gate structure through coping ins and bit mapping.
"""
bit_mapping = {i: p for i, p in enumerate(self.pos)}

def map_pos(pos):
if isinstance(pos, int):
return bit_mapping[pos]
elif isinstance(pos, Iterable):
return [bit_mapping[p] for p in pos]
else:
raise ValueError

for gate in self.gate_structure:
gate_ = copy.deepcopy(gate)
for key, val in gate.named_pos.items():
setattr(gate_, key, map_pos(val))
setattr(gate_, 'pos', map_pos(gate.pos))
self.insides.append(gate_)


def customize_gate(cls_name: str,
gate_structure: List[Instruction],
qubit_num: int,
):
"""
Helper function to create customized gate class

Args:
cls_name: name of the gate class
gate_structure: a list of instruction INSTANCES
qubit_num: number of qubits of the gate (TODO: extract from gate_structure?)

Returns:
customized gate class

Raises:
ValueError: if gate class already exists
"""
if cls_name in QuantumGate.gate_classes:
raise ValueError(f"Gate class {cls_name} already exists.")

attrs = {'cls_name': cls_name,
'gate_structure': gate_structure, # TODO: translate
'qubit_num': qubit_num,
}

customized_cls = OracleGateMeta(cls_name, (OracleGate,), attrs)
assert issubclass(customized_cls, OracleGate)
QuantumGate.register_gate(customized_cls, cls_name)
return customized_cls
95 changes: 95 additions & 0 deletions src/quafu/visualisation/bloch_sphere.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import matplotlib.pyplot as plt
import numpy as np

"""
Plotting state of single qubit on the Bloch sphere.

TODO:
1. Plot by density matrix, say, from single-qubit sub-system.
2. Plot geometrical representation of quantum operations.
3. Plot a chain of qubits.
"""


def angles_to_xyz(thetas, phis):
"""Transform angles to cartesian coordinates."""
xs = np.sin(thetas) * np.cos(phis)
ys = np.sin(thetas) * np.sin(phis)
zs = np.cos(thetas)
return xs, ys, zs


def xyz_to_angles(xs, ys, zs):
"""Transform cartesian coordinates to angles."""
phis = np.arctan2(ys, xs)
thetas = np.arccos(zs)
return thetas, phis


def hex_to_rgb(hex_color):
"""Transform a hex color code to RGB (normalized float)."""
hex_color = hex_color.lstrip('#')
if len(hex_color) != 6:
raise ValueError("Invalid hex color code")

r = int(hex_color[0:2], 16) / 255
g = int(hex_color[2:4], 16) / 255
b = int(hex_color[4:6], 16) / 255
return r, g, b


def plot_bloch_vector(v_x, v_y, v_z, title=""):
"""
Plot the Bloch vector on the Bloch sphere.

Args:
v_x (float): x coordinate of the Bloch vector.
v_y (float): y coordinate of the Bloch vector.
v_z (float): z coordinate of the Bloch vector.
title (str): title of the plot.

Returns:
ax: matplotlib axes of the Bloch sphere plot.
"""
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# surface of Bloch sphere
theta = np.linspace(0, np.pi, 21)
phi = np.linspace(0, 2 * np.pi, 21)
theta, phi = np.meshgrid(theta, phi)
x, y, z = angles_to_xyz(theta, phi)

surf = ax.plot_surface(x, y, z, color='white', alpha=0.2)
edge_color = hex_to_rgb('#000000') # #ff7f0e
edge_alpha = 0.05
surf.set_edgecolor((edge_color[0], edge_color[1], edge_color[2], edge_alpha))

# coordinate axes inside the sphere span
span = np.linspace(-1.0, 1.0, 2)
ax.plot(span, 0 * span, zs=0, zdir="z", label="X", lw=1, color="black", alpha=0.5)
ax.plot(0 * span, span, zs=0, zdir="z", label="Y", lw=1, color="black", alpha=0.5)
ax.plot(0 * span, span, zs=0, zdir="y", label="Z", lw=1, color="black", alpha=0.5)

# coordinate values
ax.text(1.4, 0, 0, 'x', color='black')
ax.text(0, 1.2, 0, 'y', color='black')
ax.text(0, 0, 1.2, 'z', color='black')

# Bloch vector
ax.quiver(0, 0, 0, v_x, v_y, v_z, color='r')
v_theta, v_phi = xyz_to_angles(v_x, v_y, v_z)

# coordinates value text
ax.text(0, 0, 1.6, 'Bloch vector: ($\\theta=${:.2f}, $\\varphi$={:.2f})'.format(v_theta, v_phi), fontsize=8, color='red')
# ax.text(0, 0, 1.6, 'Bloch vector: ({:.2f}, {:.2f}, {:.2f})'.format(v_x, v_y, v_z), fontsize=8, color='red')

# Set the range of the axes
ax.set_box_aspect([1, 1, 1])
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
ax.view_init(32, 32)
ax.set_axis_off()
ax.set_title(title)
return ax