Source code for mindquantum.core.circuit.utils

# 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."""

import copy
from types import FunctionType, MethodType

import numpy as np

from mindquantum.core.gates.basic import ParameterGate
from mindquantum.utils.type_value_check import _check_input_type

from ..parameterresolver.parameterresolver import ParameterResolver
from .circuit import Circuit, apply


[docs]def decompose_single_term_time_evolution(term, para): # pylint: disable=too-many-branches r""" Decompose a time evolution gate into basic quantum gates. This function only works for the hamiltonian with only single pauli word. For example, :math:`exp^{-it\text{ham}}`, :math:`\text{ham}` can only be a single pauli word, such as :math:`\text{ham}=X_0 Y_1 Z_2`, and at this time, term will be ((0, 'X'), (1, 'Y'), (2, 'Z')). When the evolution time is expressed as :math:`t=ax+by`, 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. Examples: >>> 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) ┠─┨╺╋╸┠─┨ RZ(2*a) ┠─┨╺╋╸┠─┨ RX(7π/2) ┠─── ┗━━━━━━━━━┛ ┗━━━┛ ┗━━━━━━━━━┛ ┗━━━┛ ┗━━━━━━━━━━┛ """ # pylint: disable=import-outside-toplevel,cyclic-import from mindquantum.core import gates from mindquantum.utils.type_value_check import _num_type if not isinstance(term, tuple): try: if len(term.terms) != 1: raise ValueError(f"Only work for single term time evolution operator, but get {len(term)}") term = list(term.terms.keys())[0] except TypeError as exc: raise TypeError(f"Not supported type:{type(term)}") from exc if not isinstance(para, _num_type): if not isinstance(para, (dict, ParameterResolver)): raise TypeError(f'para requires a number or a dict or a ParameterResolver, but get {type(para)}') para = ParameterResolver(para) para = 2 * 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(gates.RX(para).on(term[0][0])) elif term[0][1] == 'Y': out.append(gates.RY(para).on(term[0][0])) else: out.append(gates.RZ(para).on(term[0][0])) else: for index, action in term: if action == 'X': out.append(gates.H.on(index)) elif action == 'Y': rxs.append(len(out)) out.append(gates.RX(np.pi / 2).on(index)) out.append(gates.BarrierGate(False)) for i in range(len(term) - 1): out.append(gates.X.on(term[i + 1][0], term[i][0])) out.append(gates.BarrierGate(False)) out.append(gates.RZ(para).on(term[-1][0])) for i in range(len(out) - 1)[::-1]: if i in rxs: out.append(gates.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 pauli words. 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: ──┨╺╋╸┠───■───── ┗━━━┛ ┃ ┏━━━┓ ┏━┻━┓ q1: ──┨ Y ┠─┨╺╋╸┠─── ┗━━━┛ ┗━━━┛ """ # pylint: disable=import-outside-toplevel from mindquantum import operators as ops from mindquantum.core import gates allow_ops = (ops.QubitOperator, ops.Hamiltonian) if not isinstance(qubitops, allow_ops): raise TypeError(f"qubitops require a QubitOperator or a Hamiltonian, but get {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("Only work for QubitOperator with single pauli word!") gate_map = {'X': gates.X, 'Y': gates.Y, 'Z': gates.Z} for operator in qubitops.terms.keys(): circ = Circuit() if operator: for ind, single_op in operator: circ += gate_map.get(single_op).on(ind) else: circ += gates.I.on(0) return circ
def _add_ctrl_qubits(circ, ctrl_qubits): """Add control qubits on a circuit.""" # pylint: disable=import-outside-toplevel,cyclic-import from mindquantum.core import gates if not isinstance(ctrl_qubits, (int, list)): raise TypeError(f"Require a int or a list of int for ctrl_qubits, but get {type(ctrl_qubits)}!") if isinstance(ctrl_qubits, int): ctrl_qubits = [ctrl_qubits] for qubit in ctrl_qubits: if qubit < 0: raise ValueError(f"ctrl_qubits should not be negative value, but get {qubit}!") circ_out = Circuit() ctrl_qubits_set = set(ctrl_qubits) for gate in circ: intersection = ctrl_qubits_set.intersection(set(gate.obj_qubits)) if intersection: raise ValueError( f"Qubit {intersection} in ctrl_qubits {ctrl_qubits_set} already used in obj_qubits of gate {gate}" ) curr_ctrl = set(gate.ctrl_qubits) curr_ctrl = list(curr_ctrl.union(ctrl_qubits_set)) curr_ctrl.sort() new_gate = copy.deepcopy(gate) if not isinstance(gate, (gates.Measure, gates.BarrierGate)): new_gate.ctrl_qubits = curr_ctrl 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. Returns: function that can generate a Circuit. Raises: TypeError: `circuit_fn` is not a Circuit or can not return 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: ────■────────■────────■───■─── """ 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. Returns: Circuit or a function that can generate Circuit. Raises: TypeError: If `circuit_fn` is not a Circuit or can not return a 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 ┠───────■────────────── ┗━━━┛ """ 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 _add_prefix_or_suffix(circ: Circuit, fix: str, is_prefix: bool): """Add prefix to every parameters in circuit.""" out = Circuit() gate: ParameterGate for gate in circ: if gate.parameterized: new_prs = [] for coeff in gate.get_parameters(): origin_encoder = coeff.encoder_parameters origin_req_grad = coeff.requires_grad_parameters pr = ParameterResolver(coeff.const) for k, v in dict(coeff).items(): new_name = '' if is_prefix: new_name = f'{fix}_{k}' else: new_name = f'{k}_{fix}' pr[new_name] = v if k in origin_encoder: pr.encoder_part(new_name) if k in origin_req_grad: pr.requires_grad_part(new_name) new_prs.append(pr) out += gate(*new_prs).on(gate.obj_qubits, gate.ctrl_qubits) else: out += copy.deepcopy(gate) return out def add_prefix_or_suffix(circuit_fn, fix: str, is_prefix: bool): """Add prefix or suffix of the parameters of quantum circuit.""" if not isinstance(fix, str): raise TypeError(f"prefix or suffix need string, but get {type(fix)}") if isinstance(circuit_fn, (FunctionType, MethodType)): def wrapper(*arg, **keywords): circ = circuit_fn(*arg, **keywords) if not isinstance(circ, Circuit): return add_prefix_or_suffix(circ, fix, is_prefix) return _add_prefix_or_suffix(circ, fix, is_prefix) return wrapper if isinstance(circuit_fn, Circuit): return _add_prefix_or_suffix(circuit_fn, fix, is_prefix) raise TypeError("circuit_fn need a circuit or a function that can generate a circuit.")
[docs]def add_prefix(circuit_fn, prefix: str): """ 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. Returns: Circuit or a function that can generate a Circuit. Raises: TypeError: If `prefix` is not a string. TypeError: `circuit_fn` is not a Circuit or can not return 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) ┠─── ┗━━━┛ ┗━━━━━━━━━━━━━━┛ """ return add_prefix_or_suffix(circuit_fn, prefix, True)
[docs]def add_suffix(circuit_fn, suffix: str): """ Add a suffix 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. suffix (str): The suffix you want to add to every parameters. Returns: Circuit or a function that can generate a Circuit. Raises: TypeError: If suffix is not a string. TypeError: circuit_fn is not a Circuit or can not return a Circuit. Examples: >>> from mindquantum.algorithm.library import qft >>> from mindquantum.core.circuit import add_suffix >>> from mindquantum import RX, H, Circuit >>> u = lambda qubit: Circuit([H.on(0), RX('a').on(qubit)]) >>> u1 = u(0) >>> u2 = add_suffix(u1, '1') >>> u3 = add_suffix(u, '1') >>> u3 = u3(0) >>> u2 ┏━━━┓ ┏━━━━━━━━━┓ q0: ──┨ H ┠─┨ RX(a_1) ┠─── ┗━━━┛ ┗━━━━━━━━━┛ >>> u3 ┏━━━┓ ┏━━━━━━━━━┓ q0: ──┨ H ┠─┨ RX(a_1) ┠─── ┗━━━┛ ┗━━━━━━━━━┛ """ return add_prefix_or_suffix(circuit_fn, suffix, False)
[docs]def shift(circ, inc): """ Shift the qubit range of the given circuit. Args: circ (circuit): The circuit that you want to do shift operator. inc (int): The qubit distance you want to shift. Examples: >>> from mindquantum.core.circuit import shift, Circuit >>> circ = Circuit().x(1, 0) >>> circ q0: ────■───── ┏━┻━┓ q1: ──┨╺╋╸┠─── ┗━━━┛ >>> shift(circ, 1) q1: ────■───── ┏━┻━┓ q2: ──┨╺╋╸┠─── ┗━━━┛ Returns: Circuit, the shifted circuit. """ _check_input_type('circ', Circuit, circ) _check_input_type('p', int, inc) return apply(circ, [i + inc 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.""" out = Circuit() gate: ParameterGate for gate in circ: if gate.parameterized: new_prs = [] for coeff in gate.get_parameters(): origin_encoder = coeff.encoder_parameters origin_req_grad = coeff.requires_grad_parameters pr = ParameterResolver(coeff.const) for k, v in dict(coeff).items(): if k not in name_map: raise KeyError(f"Original parameter {k} not in name_map!") pr[name_map[k]] = v if k in origin_encoder: pr.encoder_part(name_map[k]) if k in origin_req_grad: pr.requires_grad_part(name_map[k]) new_prs.append(pr) out += gate(*new_prs).on(gate.obj_qubits, gate.ctrl_qubits) else: out += copy.deepcopy(gate) 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. Returns: Circuit or a function that can generate a Circuit. 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. Examples: >>> from mindquantum.algorithm.library import qft >>> from mindquantum.core.circuit import change_param_name, Circuit >>> from mindquantum.core.gates import RX, H >>> 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) ┠─── ┗━━━┛ ┗━━━━━━━┛ """ 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.")
[docs]def as_encoder(circuit_fn): """ Conversion decorator of a circuit to an encoder circuit. Args: circuit_fn (Union[Circuit, FunctionType, MethodType]): A Circuit or a callable function that can return a Circuit. Returns: Function, if `circuit_fn` is a callable function that will return a Circuit. Circuit, if `circuit_fn` is already a Circuit. Examples: >>> from mindquantum.core.circuit import as_encoder, Circuit >>> from mindquantum.core.gates import RX >>> @as_encoder ... def create_circuit(): ... circ = Circuit() ... circ += RX('a').on(0) ... return circ >>> circ = create_circuit() >>> circ.encoder_params_name ['a'] >>> circ.as_ansatz() >>> circ.encoder_params_name [] >>> circ = as_encoder(circ) >>> circ.encoder_params_name ['a'] """ if isinstance(circuit_fn, (FunctionType, MethodType)): def gene_circ(*args, **kwargs): circ = circuit_fn(*args, **kwargs) if not isinstance(circ, Circuit): raise ValueError(f"The callable circuit_fn should return a Circuit, but get {type(circ)}") circ.as_encoder() return circ return gene_circ if isinstance(circuit_fn, Circuit): new_circ = copy.deepcopy(circuit_fn) return new_circ.as_encoder() raise TypeError(f"circuit_fn need a circuit or a function that can generate a circuit, but get {type(circuit_fn)}.")
[docs]def as_ansatz(circuit_fn): """ Conversion decorator of a circuit to an ansatz circuit. Args: circuit_fn (Union[Circuit, FunctionType, MethodType]): A Circuit or a callable function that can return a Circuit. Returns: Function, if `circuit_fn` is a callable function that will return a Circuit. Circuit, if `circuit_fn` is already a Circuit. Examples: >>> from mindquantum.core.circuit import as_ansatz, Circuit >>> from mindquantum.core.gates import RX >>> @as_ansatz ... def create_circuit(): ... circ = Circuit() ... circ += RX('a').on(0) ... return circ >>> circ = create_circuit() >>> circ.ansatz_params_name ['a'] >>> circ.as_encoder() >>> circ.ansatz_params_name [] >>> circ = as_ansatz(circ) >>> circ.ansatz_params_name ['a'] """ if isinstance(circuit_fn, (FunctionType, MethodType)): def gene_circ(*args, **kwargs): circ = circuit_fn(*args, **kwargs) if not isinstance(circ, Circuit): raise ValueError(f"The callable circuit_fn should return a Circuit, but get {type(circ)}") circ.as_ansatz() return circ return gene_circ if isinstance(circuit_fn, Circuit): new_circ = copy.deepcopy(circuit_fn) return new_circ.as_ansatz() raise TypeError(f"circuit_fn need a circuit or a function that can generate a circuit, but get {type(circuit_fn)}.")
C = controlled D = dagger AP = add_prefix CPN = change_param_name