# -*- coding: utf-8 -*-
# Copyright 2021 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""Tools for MindQuantum eDSL"""
from types import FunctionType, MethodType
import copy
import numpy as np
from projectq.ops import QubitOperator as pq_operator
from openfermion.ops import QubitOperator as of_operator
from mindquantum.core.parameterresolver.parameterresolver import ParameterResolver
from mindquantum.utils.type_value_check import _check_input_type
[docs]def decompose_single_term_time_evolution(term, para):
"""
Decompose a time evolution gate into basic quantum gates.
This function only work for the hamiltonian with only single pauli word.
For example, exp(-i * t * ham), ham can only be a single pauli word, such
as ham = X0 x Y1 x Z2, and at this time, term will be
((0, 'X'), (1, 'Y'), (2, 'Z')). When the evolution time is expressd as
t = a*x + b*y, para would be {'x':a, 'y':b}.
Args:
term (tuple, QubitOperator): the hamiltonian term of just the
evolution qubit operator.
para (Union[dict, numbers.Number]): the parameters of evolution operator.
Returns:
Circuit, a quantum circuit.
Raises:
ValueError: If term has more than one pauli string.
TypeError: If term is not map.
Example:
>>> from mindquantum.core.operators import QubitOperator
>>> from mindquantum.core.circuit import decompose_single_term_time_evolution
>>> ham = QubitOperator('X0 Y1')
>>> circuit = decompose_single_term_time_evolution(ham, {'a':1})
>>> print(circuit)
q0: ─────H───────●───────────────●───────H──────
│ │
q1: ──RX(π/2)────X────RZ(2*a)────X────RX(7π/2)──
"""
from mindquantum import gates as G
from mindquantum.core.circuit import Circuit
from mindquantum.core.parameterresolver import ParameterResolver as PR
from mindquantum.utils.type_value_check import _num_type
if not isinstance(term, tuple):
try:
if len(term.terms) != 1:
raise ValueError("Only work for single term time \
evolution operator, but get {}".format(len(term)))
term = list(term.terms.keys())[0]
except TypeError:
raise TypeError("Not supported type:{}".format(type(term)))
if not isinstance(para, _num_type):
if not isinstance(para, (dict, ParameterResolver)):
raise TypeError(f'para requiers a number or a dict or a ParameterResolver, but get {type(para)}')
para = ParameterResolver(para)
out = []
term = sorted(term)
rxs = []
if not term:
raise ValueError("Get constant hamiltonian, please use GlobalPhase gate and give the obj_qubit by yourself.")
if len(term) == 1: # single pauli operator
if term[0][1] == 'X':
out.append(G.RX(para * 2).on(term[0][0]))
elif term[0][1] == 'Y':
out.append(G.RY(para * 2).on(term[0][0]))
else:
out.append(G.RZ(para * 2).on(term[0][0]))
else:
for index, action in term:
if action == 'X':
out.append(G.H.on(index))
elif action == 'Y':
rxs.append(len(out))
out.append(G.RX(np.pi / 2).on(index))
out.append(G.BarrierGate(False))
for i in range(len(term) - 1):
out.append(G.X.on(term[i + 1][0], term[i][0]))
out.append(G.BarrierGate(False))
if isinstance(para, (dict, PR)):
out.append(G.RZ({i: 2 * j for i, j in para.items()}).on(term[-1][0]))
else:
out.append(G.RZ(2 * para).on(term[-1][0]))
for i in range(len(out) - 1)[::-1]:
if i in rxs:
out.append(G.RX(np.pi * 3.5).on(out[i].obj_qubits))
else:
out.append(out[i])
return Circuit(out)
[docs]def pauli_word_to_circuits(qubitops):
"""
Convert a single pauli word qubit operator to a quantum circuit.
Args:
qubitops (QubitOperator, Hamiltonian): The single pauli word qubit operator.
Returns:
Circuit, a quantum circuit.
Raises:
TypeError: If qubitops is not a QubitOperator or a Hamiltonian.
ValueError: If qubitops is Hamiltonian but not in origin mode.
ValueError: If qubitops has more than one pauliwords.
Examples:
>>> from mindquantum.core import X
>>> from mindquantum.core.operators import QubitOperator
>>> from mindquantum.core.circuit import pauli_word_to_circuits
>>> qubitops = QubitOperator('X0 Y1')
>>> pauli_word_to_circuits(qubitops) + X(1, 0)
q0: ──X────●──
│
q1: ──Y────X──
"""
from mindquantum import gates as G
from mindquantum import operators as ops
from mindquantum.core import Circuit
allow_ops = (pq_operator, of_operator, ops.QubitOperator, ops.Hamiltonian)
if not isinstance(qubitops, allow_ops):
raise TypeError("qubitops require a QubitOperator or a Hamiltonian, but get {}!".format(type(qubitops)))
if isinstance(qubitops, ops.Hamiltonian):
if qubitops.how_to != 0:
raise ValueError("Hamiltonian should be in origin mode.")
qubitops = qubitops.hamiltonian
if len(qubitops.terms) > 1:
raise ValueError("Onle work for QubitOperator with single pauliword!")
gate_map = {'X': G.X, 'Y': G.Y, 'Z': G.Z}
for ops in qubitops.terms.keys():
circ = Circuit()
if ops:
for ind, single_op in ops:
circ += gate_map[single_op].on(ind)
else:
circ += G.I.on(0)
return circ
def _add_ctrl_qubits(circ, ctrl_qubits):
"""Add control qubits on a circuit."""
from mindquantum.core import Circuit
from mindquantum import gates as G
if not isinstance(ctrl_qubits, (int, list)):
raise TypeError("Require a int or a list of int for ctrl_qubits, but get {}!".format(type(ctrl_qubits)))
if isinstance(ctrl_qubits, int):
ctrl_qubits = [ctrl_qubits]
for q in ctrl_qubits:
if q < 0:
raise ValueError("ctrl_qubits should not be negative value, but get {}!".format(q))
circ_out = Circuit()
ctrl_qubits = set(ctrl_qubits)
for gate in circ:
intersection = ctrl_qubits.intersection(set(gate.obj_qubits))
if intersection:
raise ValueError(
f"Qubit {intersection} in ctrl_qubits {ctrl_qubits} already used in obj_qubits of gate {gate}")
curr_ctrl = set(gate.ctrl_qubits)
curr_ctrl = list(curr_ctrl.union(ctrl_qubits))
curr_ctrl.sort()
new_gate = copy.deepcopy(gate)
if not isinstance(gate, (G.Measure, G.BarrierGate)):
new_gate.ctrl_qubits = curr_ctrl
new_gate.generate_description()
circ_out += new_gate
return circ_out
[docs]def controlled(circuit_fn):
"""
Add control qubits on a quantum circuit or a quantum operator (a function
that can generate a quantum circuit)
Args:
circuit_fn (Union[Circuit, FunctionType, MethodType]): A quantum circuit,
or a function that can generate a quantum circuit.
Raises:
TypeError: circuit_fn is not a Circuit or can not return a Circuit.
Returns:
function that can generate a Circuit.
Examples:
>>> from mindquantum.algorithm.library import qft
>>> from mindquantum.core.circuit import controlled
>>> u1 = qft([0, 1])
>>> u2 = controlled(u1)
>>> u3 = controlled(qft)
>>> u3 = u3(2, [0, 1])
>>> u2(2)
q0: ──H────PS(π/2)─────────@──
│ │ │
q1: ──┼───────●───────H────@──
│ │ │ │
q2: ──●───────●───────●────●──
>>> u3
q0: ──H────PS(π/2)─────────@──
│ │ │
q1: ──┼───────●───────H────@──
│ │ │ │
q2: ──●───────●───────●────●──
"""
from mindquantum.core import Circuit
if isinstance(circuit_fn, (FunctionType, MethodType)):
def wrapper(ctrl_qubits, *arg, **keywords):
circ = circuit_fn(*arg, **keywords)
if not isinstance(circ, Circuit):
return controlled(circ)
return _add_ctrl_qubits(circ, ctrl_qubits)
return wrapper
if isinstance(circuit_fn, Circuit):
return lambda ctrl_qubits: _add_ctrl_qubits(circuit_fn, ctrl_qubits)
raise TypeError("Input need a circuit or a function that can generate a circuit.")
[docs]def dagger(circuit_fn):
"""
Get the hermitian dagger of a quantum circuit or a quantum operator (a function
that can generate a quantum circuit)
Args:
circuit_fn (Union[Circuit, FunctionType, MethodType]): A quantum circuit,
or a function that can generate a quantum circuit.
Raises:
TypeError: If circuit_fn is not a Circuit or can not return a Circuit.
Returns:
Circuit or a function that can generate Circuit.
Examples:
>>> from mindquantum.algorithm.library import qft
>>> from mindquantum.core.circuit import dagger
>>> u1 = qft([0, 1])
>>> u2 = dagger(u1)
>>> u3 = dagger(qft)
>>> u3 = u3([0, 1])
>>> u2
q0: ──@─────────PS(-π/2)────H──
│ │
q1: ──@────H───────●───────────
>>> u3
q0: ──@─────────PS(-π/2)────H──
│ │
q1: ──@────H───────●───────────
"""
from mindquantum.core import Circuit
if isinstance(circuit_fn, (FunctionType, MethodType)):
def wrapper(*arg, **keywords):
circ = circuit_fn(*arg, **keywords)
if not isinstance(circ, Circuit):
return dagger(circ)
return circ.hermitian()
return wrapper
if isinstance(circuit_fn, Circuit):
return circuit_fn.hermitian()
raise TypeError("circuit_fn need a circuit or a function that can generate a circuit.")
def _apply_circuit(circ, qubits):
"""Apply a circuit to other different qubits."""
from mindquantum.core import Circuit
old_qubits = set([])
for g in circ:
old_qubits.update(g.obj_qubits)
old_qubits.update(g.ctrl_qubits)
old_qubits = list(old_qubits)
old_qubits.sort()
if len(old_qubits) != len(qubits):
raise ValueError(f"Can not apply a {len(old_qubits)} qubits unit to {len(qubits)} qubits circuit.")
qubits_map = dict(zip(old_qubits, qubits))
out = Circuit()
for g in circ:
g = copy.deepcopy(g)
g.obj_qubits = [qubits_map[i] for i in g.obj_qubits]
g.ctrl_qubits = [qubits_map[i] for i in g.ctrl_qubits]
g.generate_description()
out += g
return out
[docs]def apply(circuit_fn, qubits):
"""
Apply a quantum circuit or a quantum operator (a function
that can generate a quantum circuit) to different qubits.
Args:
circuit_fn (Union[Circuit, FunctionType, MethodType]): A quantum circuit,
or a function that can generate a quantum circuit.
qubits (list[int]): The new qubits that you want to apply.
Raises:
TypeError: If qubits is not a list.
ValueError: If any element of qubits is negative.
TypeError: If circuit_fn is not Circuit or can not return a Circuit.
Returns:
Circuit or a function that can generate a Circuit.
Examples:
>>> from mindquantum.algorithm.library import qft
>>> from mindquantum.core.circuit import apply
>>> u1 = qft([0, 1])
>>> u2 = apply(u1, [1, 0])
>>> u3 = apply(qft, [1, 0])
>>> u3 = u3([0, 1])
>>> u2
q0: ──────────●───────H────@──
│ │
q1: ──H────PS(π/2)─────────@──
>>> u3
q0: ──────────●───────H────@──
│ │
q1: ──H────PS(π/2)─────────@──
"""
from mindquantum.core import Circuit
if not isinstance(qubits, list):
raise TypeError(f"qubits need a list, but get {type(qubits)}!")
if len(qubits) > 1:
for index, q in enumerate(qubits[1:]):
if q < 0 or qubits[index] < 0:
raise ValueError(f"Qubit index can not negative!")
if isinstance(circuit_fn, (FunctionType, MethodType)):
def wrapper(*arg, **keywords):
circ = circuit_fn(*arg, **keywords)
if not isinstance(circ, Circuit):
return apply(circ, qubits)
return _apply_circuit(circ, qubits)
return wrapper
if isinstance(circuit_fn, Circuit):
return _apply_circuit(circuit_fn, qubits)
raise TypeError("circuit_fn need a circuit or a function that can generate a circuit.")
def _add_prefix(circ, prefix):
"""Add prefix to every parameters in circuit."""
from mindquantum.core import Circuit
from mindquantum.core import ParameterResolver as PR
out = Circuit()
for g in circ:
g = copy.deepcopy(g)
if g.parameterized:
pr = PR()
for k, v in g.coeff.items():
pr[f'{prefix}_{k}'] = v
g.coeff = pr
g.generate_description()
out += g
return out
[docs]def add_prefix(circuit_fn, prefix):
"""
Add a prefix on the parameter of a parameterized quantum circuit or a parameterized
quantum operator (a function that can generate a parameterized quantum circuit).
Args:
circuit_fn (Union[Circuit, FunctionType, MethodType]): A quantum circuit,
or a function that can generate a quantum circuit.
prefix (str): The prefix you want to add to every parameters.
Raises:
TypeError: If prefix is not a string.
TypeError: circuit_fn is not a Circuit or can not return a Circuit.
Returns:
Circuit or a function that can generate a Circuit.
Examples:
>>> from mindquantum.algorithm.library import qft
>>> from mindquantum.core.circuit import add_prefix
>>> from mindquantum import RX, H, Circuit
>>> u = lambda qubit: Circuit([H.on(0), RX('a').on(qubit)])
>>> u1 = u(0)
>>> u2 = add_prefix(u1, 'ansatz')
>>> u3 = add_prefix(u, 'ansatz')
>>> u3 = u3(0)
>>> u2
q0: ──H────RX(ansatz_a)──
>>> u3
q0: ──H────RX(ansatz_a)──
"""
from mindquantum.core import Circuit
if not isinstance(prefix, str):
raise TypeError(f"prefix need string, but get {type(prefix)}")
if isinstance(circuit_fn, (FunctionType, MethodType)):
def wrapper(*arg, **keywords):
circ = circuit_fn(*arg, **keywords)
if not isinstance(circ, Circuit):
return add_prefix(circ, prefix)
return _add_prefix(circ, prefix)
return wrapper
if isinstance(circuit_fn, Circuit):
return _add_prefix(circuit_fn, prefix)
raise TypeError("circuit_fn need a circuit or a function that can generate a circuit.")
[docs]def shift(circ, p):
"""
Shift the qubit range of the given circuit.
Args:
circ (circuit): The circuit that you want to do shift operator.
p (int): The qubit distance you want to shift.
Examples:
>>> from mindquantum.core.circuit import shift
>>> from mindquantum.core.circuit import Circuit
>>> circ = Circuit().x(1, 0)
>>> circ
q0: ──●──
│
q1: ──X──
>>> shift(circ, 1)
q1: ──●──
│
q2: ──X──
Returns:
Circuit, the shifted circuit.
"""
from mindquantum.core import Circuit
_check_input_type('circ', Circuit, circ)
_check_input_type('p', int, p)
return apply(circ, [i + p for i in sorted(circ.all_qubits.keys())])
def _change_param_name(circ, name_map):
"""Change the parameter of circuit according to the name map."""
from mindquantum.core import Circuit
from mindquantum.core import ParameterResolver as PR
out = Circuit()
for g in circ:
g = copy.deepcopy(g)
if g.parameterized:
pr = PR()
for k, v in g.coeff.items():
if k not in name_map:
raise KeyError(f"Original parameter {k} not in name_map!")
pr[name_map[k]] = v
g.coeff = pr
g.generate_description()
out += g
return out
[docs]def change_param_name(circuit_fn, name_map):
"""
Change the parameter name of a parameterized quantum circuit or a parameterized
quantum operator (a function that can generate a parameterized quantum circuit).
Args:
circuit_fn (Union[Circuit, FunctionType, MethodType]): A quantum circuit,
or a function that can generate a quantum circuit.
name_map (dict): The parameter name mapping dict.
Raises:
TypeError: If name_map is not a map.
TypeError: If key of name_map is not string.
TypeError: If value of name_map is not string.
TypeError: If circuit_fn is not a Circuit or can not return a Circuit.
Returns:
Circuit or a function that can generate a Circuit.
Examples:
>>> from mindquantum.algorithm.library import qft
>>> from mindquantum.core.circuit import change_param_name
>>> from mindquantum import RX, H, Circuit
>>> u = lambda qubit: Circuit([H.on(0), RX('a').on(qubit)])
>>> u1 = u(0)
>>> u2 = change_param_name(u1, {'a': 'b'})
>>> u3 = change_param_name(u, {'a': 'b'})
>>> u3 = u3(0)
>>> u2
q0: ──H────RX(b)──
>>> u3
q0: ──H────RX(b)──
"""
from mindquantum.core import Circuit
if not isinstance(name_map, dict):
raise TypeError(f"name_map need map, but get {type(name_map)}")
for k, v in name_map.items():
if not isinstance(k, str):
raise TypeError(f"key of name_map need a string, but get {k}, which is {type(k)}")
if not isinstance(v, str):
raise TypeError(f"value of name_map need a string, but get {v}, which is {type(v)}")
if isinstance(circuit_fn, (FunctionType, MethodType)):
def wrapper(*arg, **keywords):
circ = circuit_fn(*arg, **keywords)
if not isinstance(circ, Circuit):
return change_param_name(circ, name_map)
return _change_param_name(circ, name_map)
return wrapper
if isinstance(circuit_fn, Circuit):
return _change_param_name(circuit_fn, name_map)
raise TypeError("circuit_fn need a circuit or a function that can generate a circuit.")
C = controlled
D = dagger
A = apply
AP = add_prefix
CPN = change_param_name