Source code for mindquantum.io.qasm.qcis

# 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.
# ============================================================================
"""QCIS support module."""
# pylint: disable=import-outside-toplevel
import re
from typing import List, Tuple

import numpy as np

from mindquantum.utils import fdopen
from mindquantum.utils.type_value_check import _check_input_type
from mindquantum.core.parameterresolver import ParameterResolver

def _floatable(string) -> bool:
    """Whether the input string can be converted to a float number.
    This function will return `Ture` if the input string is composed only of digits, point, star and slash.

    Examples:
        >>> _floatable(2*a)
        False
        >>> _floatable("2/13")
        True
    """
    num_chars = re.findall(r"([\d\.\+\-\*\/])", string)
    return len(num_chars) == len(string)

def _to_float(string) -> float:
    """convert the input floatable string to a float number.

    Examples:
        >>> _to_float("-12/3.0")
        -4.0
        >>> _to_float("12")
        12.0
    """
    tmp = re.findall(r"([^\/]+)", string)
    if "/" in string:
        res = float(tmp[0]) / float(tmp[1])
    else:
        res = float(tmp[0])
    return res

def _string_to_param(string) -> ParameterResolver:
    """Convert the input string to a ParameterResolver object.

    Examples:
        >>> string0 = "2.0*pi-1.5*π"
        >>> _string_to_param(string0)
        π/2
        >>> string1 = a = "-2√2*a+3.4*b+2.2*3+0.5"
        >>> _string_to_param(string1)
        -2√2*a + 17/5*b + 7.1
    """
    if "√" in string:
        tmp = re.findall(r"(\d+\√\d)", string)
        if tmp: # there are digits before √.
            both = tmp[0].split("√")
            left = _to_float(both[0])
            right = _to_float(both[1])
            string = string.replace(tmp[0], str(left*np.sqrt(right)))
        else: # # there are not digits before √.
            tmp = re.findall(r"√(\d)", string)
            num = _to_float(tmp[0])
            string = string.replace("√" + tmp[0], str(np.sqrt(num)))
    # Add a + before and a space after to the string
    # to make it easier to split it with regular expression.
    if not string.startswith("-"):
        string = "+" + string
    string += " "
    # covert the pi or π to a float number
    string = string.replace("pi", str(np.pi)).replace("π", str(np.pi))
    terms = re.findall(r"(.+?)(?=[\+\-\s])", string)
    data = {}
    const = 0.
    for term in terms:
        tmp = re.findall(r"([^\*]+)", term)
        if len(tmp) == 1:
            tmp = tmp[0]
            if _floatable(tmp):
                const += _to_float(tmp)
            else:
                if tmp[0] == "-":
                    data[tmp[1:]] = -1
                else:
                    data[tmp[1:]] = 1
        else:
            if sum(_floatable(i) for i in tmp) == 2:
                const += _to_float(tmp[0]) * _to_float(tmp[1])
            else:
                if _floatable(tmp[0]):
                    data[tmp[1]] = _to_float(tmp[0])
                else:
                    data[tmp[0]] = _to_float(tmp[1])
    return ParameterResolver(data=data, const=const)

def _failed_from_qcis(gate_name):
    raise NotImplementedError(f"Cannot convert the {gate_name} from QCIS to gate.")

def _failed_to_qcis(gate):
    raise NotImplementedError(f"Cannot convert the {gate} from gate to QCIS.")

def _valid_angle(angle) -> float:
    """Restrict the angle to the interval [-pi, pi]."""
    return np.mod(angle + np.pi, np.pi * 2) - np.pi

[docs]class QCIS: """Convert a circuit to qcis format. Examples: >>> circ = Circuit() >>> circ.x(0).z(1,0).rx({"a":-2*np.sqrt(2)}, 0).sx(0).barrier() >>> circ.ry(ParameterResolver(data={'theta':-np.pi}, const=np.pi), 1) >>> string= QCIS().to_string(circ) >>> print(string) X Q0 CZ Q0 Q1 RX Q0 -2√2*a X2P Q0 B Q0 B Q1 RY Q1 -π*theta + π >>> circ1 = QCIS().from_string(string) >>> print(circ1) ┏━━━┓ ┏━━━━━━━━━━━━┓ ┏━━━━┓ q0: ──┨╺╋╸┠───■───┨ RX(-2√2*a) ┠─┨ SX ┠─▓──────────────────────── ┗━━━┛ ┃ ┗━━━━━━━━━━━━┛ ┗━━━━┛ ▓ ┏━┻━┓ ▓ ┏━━━━━━━━━━━━━━━━━━┓ q1: ────────┨ Z ┠───────────────────────▓─┨ RY(-π*theta + π) ┠─── ┗━━━┛ ┗━━━━━━━━━━━━━━━━━━┛ """ def __init__(self): from mindquantum.core import gates as G # pylint: disable=import-outside-toplevel,cyclic-import from mindquantum.core.circuit import Circuit # pylint: disable=import-outside-toplevel,cyclic-import self.gate_class = G self.circuit_class = Circuit def _get_gate_terms(self, string) -> List[str]: """Extract lines containing gate.""" terms = string.split("\n") terms = [_.strip() for _ in terms if _.strip()] return terms def _get_gate_name(self, term) -> str: """Get the gate name from the term.""" name = re.findall(r"^([\w]+)(?=\s)", term)[0] return name def _get_qubits(self, term) -> List[int]: """Get the qubits acted upon by the gate from the term.""" qubits = re.findall(r"(?<=Q)(\d+)", term) return list(reversed([int(i) for i in qubits])) # [obj_qubit, ctrl_qubit] def _get_gate_info(self, term) -> Tuple[str, List[int], List[ParameterResolver]]: """Get the gate information (name, params and qubits) according to the term.""" term = term.strip() gate_name = self._get_gate_name(term) qubits = self._get_qubits(term) params = [] if gate_name.startswith("R"): param = "".join(term.split(" ")[2:]) params = [_string_to_param(param)] return gate_name, qubits, params def _to_gate(self, gate_name: str, qubits: List[int], params: List[ParameterResolver]): """Create a gate according to the given information.""" if gate_name in ["Z", "CZ"]: return self.gate_class.Z.on(*qubits) if gate_name == "I": return self.gate_class.I.on(*qubits) if gate_name == "X": return self.gate_class.X.on(*qubits) if gate_name == "Y": return self.gate_class.Y.on(*qubits) if gate_name == "H": return self.gate_class.H.on(*qubits) if gate_name == "S": return self.gate_class.S.on(*qubits) if gate_name == "SD": return self.gate_class.S.hermitian().on(*qubits) if gate_name == "T": return self.gate_class.T.on(*qubits) if gate_name == "TD": return self.gate_class.T.hermitian().on(*qubits) if gate_name == "B": return self.gate_class.BarrierGate().on(*qubits) if gate_name == "X2P": return self.gate_class.SX.on(*qubits) if gate_name == "X2M": return self.gate_class.SX.hermitian().on(*qubits) if gate_name == "Y2P": return self.gate_class.RY(np.pi/2).on(*qubits) if gate_name == "Y2M": return self.gate_class.RY(-np.pi/2).on(*qubits) if gate_name == "RX": return self.gate_class.RX(*params).on(*qubits) if gate_name == "RY": return self.gate_class.RY(*params).on(*qubits) if gate_name == "RZ": return self.gate_class.RZ(*params).on(*qubits) _failed_from_qcis(gate_name) return None
[docs] def from_file(self, file_name: str): """Read a qcis file. Args: file_name (str): The path of file that stored quantum circuit in qcis format. Returns: Circuit, the quantum circuit translated from qcis file. """ with fdopen(file_name, 'r') as fd: terms = fd.readlines() return self.from_string('\n'.join(terms))
[docs] def from_string(self, string: str): r"""Read a QCIS string. Args: string (str): The QCIS string of Circuit. Returns: :class:`~.core.circuit.Circuit`, the quantum circuit translated from QCIS string. Examples: >>> string = "X Q0 \nCZ Q0 Q1 \nRX Q0 -2√2*a\nX2P Q0 \nB Q0\nB Q1\nRY Q1 -π*theta + π" >>> circ = QCIS().from_string(string) >>> print(circ) ┏━━━┓ ┏━━━━━━━━━━━━┓ ┏━━━━┓ q0: ──┨╺╋╸┠───■───┨ RX(-2√2*a) ┠─┨ SX ┠─▓──────────────────────── ┗━━━┛ ┃ ┗━━━━━━━━━━━━┛ ┗━━━━┛ ▓ ┏━┻━┓ ▓ ┏━━━━━━━━━━━━━━━━━━┓ q1: ────────┨ Z ┠───────────────────────▓─┨ RY(-π*theta + π) ┠─── ┗━━━┛ ┗━━━━━━━━━━━━━━━━━━┛ """ from mindquantum.core.gates import BarrierGate _check_input_type('string', str, string) terms = self._get_gate_terms(string) circ = self.circuit_class() barrier_qubits = [] for term in terms: gate_name, qubits, params = self._get_gate_info(term) gate = self._to_gate(gate_name, qubits, params) if isinstance(gate, BarrierGate): barrier_qubits.extend(gate.obj_qubits) else: if barrier_qubits: circ += BarrierGate().on(barrier_qubits) barrier_qubits = [] circ += gate if barrier_qubits: circ += BarrierGate().on(barrier_qubits) return circ
def _qubits_str(self, gate) -> str: """Convert the gate's qubits to a string""" return "".join(f"Q{qubit} " for qubit in gate.ctrl_qubits + gate.obj_qubits)
[docs] def to_string(self, circuit, parametric: bool = True) -> str: """Convert the input circuit to qcis. Args: circuit (Circuit): The quantum circuit you want to translated to qcis. parametric (bool): Whether to keep the parameters in gates. If it is ``False`` , we will discard all parameters and rotation gates with zero angles. The remaining angles will be restricted to the interval [-pi, pi]. Default: ``True``. Returns: str, The qcis format of the input circuit. Examples: >>> circ = Circuit() >>> circ.x(0).z(1,0).rx({"a":-2*np.sqrt(2)}, 0).sx(0).barrier() >>> circ.ry(ParameterResolver(data={'theta':-np.pi}, const=np.pi), 1) >>> string= QCIS().to_string(circ) >>> print(string) X Q0 CZ Q0 Q1 RX Q0 -2√2*a X2P Q0 B Q0 B Q1 RY Q1 -π*theta + π Raises: TypeError: if `circuit` is not a Circuit. NotImplementedError: if the input circuit containing gates which is not supported by qcis. """ _check_input_type('circuit', self.circuit_class, circuit) if circuit.is_noise_circuit: raise ValueError("Cannot convert noise circuit to QCIS.") string = [] for gate in circuit: # type: ignore if isinstance(gate, self.gate_class.BarrierGate): if not gate.obj_qubits: for i in range(circuit.n_qubits): string.append(f"B Q{i}") else: string.append("B " + self._qubits_str(gate)) continue if isinstance(gate, self.gate_class.Measure): string.append("M " + self._qubits_str(gate)) continue if isinstance(gate, (self.gate_class.YGate, self.gate_class.HGate)): if gate.ctrl_qubits: _failed_to_qcis(gate) string.append(gate.name + " " + self._qubits_str(gate)) continue if isinstance(gate, self.gate_class.XGate): if not gate.ctrl_qubits: string.append("X " +self. _qubits_str(gate)) continue elif len(gate.ctrl_qubits) == 1: string.append("H " + f"Q{gate.obj_qubits[0]}") string.append("CZ " + self._qubits_str(gate)) string.append("H " + f"Q{gate.obj_qubits[0]}") continue else: _failed_to_qcis(gate) if isinstance(gate, self.gate_class.ZGate): if not gate.ctrl_qubits: string.append("Z " + self._qubits_str(gate)) else: if len(gate.ctrl_qubits) > 1: _failed_to_qcis(gate) string.append("CZ " + self._qubits_str(gate)) continue if isinstance(gate, (self.gate_class.TGate, self.gate_class.SGate)): if gate.ctrl_qubits: _failed_to_qcis(gate) if gate.hermitianed: string.append(f"{gate.name}D " + self._qubits_str(gate)) else: string.append(f"{gate.name} " + self._qubits_str(gate)) continue if isinstance(gate, self.gate_class.SXGate): if gate.ctrl_qubits: _failed_to_qcis(gate) if gate.hermitianed: string.append(f"{gate.name[1]}2M " + self._qubits_str(gate)) else: string.append(f"{gate.name[1]}2P " + self._qubits_str(gate)) continue if isinstance(gate, (self.gate_class.RX, self.gate_class.RY, self.gate_class.RZ)): if gate.ctrl_qubits: _failed_to_qcis(gate) if parametric: param = str(gate.coeff) else: if not gate.coeff.const: continue param = str(_valid_angle(gate.coeff.const)) string.append(f"{gate.name} " + self._qubits_str(gate) + param) continue if isinstance(gate, self.gate_class.IGate): if gate.ctrl_qubits: _failed_to_qcis(gate) string.append("I " + self._qubits_str(gate)) continue _failed_to_qcis(gate) return "\n".join(string)
[docs] def to_file(self, file_name: str, circuit, parametric: bool = True) -> None: """ Convert a quantum circuit to qcis format and save in file. Args: file_name (str): The file name you want to save the qcis file. circuit (Circuit): The Circuit you want to convert. parametric (bool): Whether to keep the parameters in gates. If it is ``False`` , we will discard all parameters and rotation gates with zero angles. The remaining angles will be restricted to the interval [-pi, pi]. Default: ``True``. Raises: TypeError: if `file_name` is not a str. TypeError: if `circuit` is not a Circuit. NotImplementedError: if the input circuit containing gates which is not supported by qcis. """ if not isinstance(file_name, str): raise TypeError(f'file_name requires a str, but get {type(file_name)}') if not isinstance(circuit, self.circuit_class): raise TypeError(f"circuit requires a Circuit, but get {type(circuit)}") with fdopen(file_name, 'w') as fd: fd.writelines(self.to_string(circuit, parametric)) print(f"write circuit to {file_name} finished!")