Skip to content

Commit

Permalink
Merge pull request #124 from chensgit169/stable/0.3
Browse files Browse the repository at this point in the history
Stable/0.3: some small fixs, add visualization of Bloch Sphere
  • Loading branch information
chensgit169 committed Dec 17, 2023
2 parents dad3743 + f8242f4 commit b88f629
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 19 deletions.
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

0 comments on commit b88f629

Please sign in to comment.