diff --git a/examples/bell_state/host.py b/examples/bell_state/host.py index 18b9d2369f3bd8292c54b6b67b252f448ac03ad6..c0665b24d7533481fc512eabd502a2606cb2752b 100644 --- a/examples/bell_state/host.py +++ b/examples/bell_state/host.py @@ -6,7 +6,7 @@ qu_file = Path(__file__).parent / "kernel.qu" def routine(circ_name, num_shots=1): - task = Quingo_task(qu_file, circ_name) + task = Quingo_task(qu_file, circ_name, debug_mode=True) cfg = ExeConfig(ExeMode.SimFinalResult, num_shots=num_shots) qasm_fn = compile(task, params=(), config_file="") sim_result = execute(qasm_fn, BackendType.QUALESIM_QUANTUMSIM, cfg) diff --git a/examples/bell_state/kernel.qu b/examples/bell_state/kernel.qu index 77206da72e53bd882163e86f2e939e6d0a5eb906..b4eee97770292933c3dad1f8480afff0aadec346 100644 --- a/examples/bell_state/kernel.qu +++ b/examples/bell_state/kernel.qu @@ -1,10 +1,13 @@ import std_ops operation bell_state() : unit { - using(q0: qubit, q1: qubit) { - H(q0); - CNOT(q0, q1); - measure(q0); - measure(q1); + using(q : qubit[2] = {42,48}) { + H(q[0]); + CNOT(q[0], q[1]); + measure(q[0]); + measure(q[1]); } -} \ No newline at end of file +} +// operation main(): unit{ +// return bell_state(); +// } \ No newline at end of file diff --git a/examples/bell_state/test.json b/examples/bell_state/test.json new file mode 100644 index 0000000000000000000000000000000000000000..7a54b89f1628f92c885d53f9518486a1346cb79f --- /dev/null +++ b/examples/bell_state/test.json @@ -0,0 +1,635 @@ +{ + "qubits": [ + "Q00", + "Q01", + "Q02", + "Q03", + "Q04", + "Q05", + "Q06", + "Q07", + "Q08", + "Q09", + "Q10", + "Q11", + "Q12", + "Q13", + "Q14", + "Q15", + "Q16", + "Q17", + "Q18", + "Q19", + "Q20", + "Q21", + "Q22", + "Q23", + "Q24", + "Q25", + "Q26", + "Q27", + "Q28", + "Q29", + "Q30", + "Q31", + "Q32", + "Q33", + "Q34", + "Q35", + "Q36", + "Q37", + "Q38", + "Q39", + "Q40", + "Q41", + "Q42", + "Q43", + "Q44", + "Q45", + "Q46", + "Q47", + "Q48", + "Q49", + "Q50", + "Q51", + "Q52", + "Q53", + "Q54", + "Q55", + "Q56", + "Q57", + "Q58", + "Q59", + "Q60", + "Q61", + "Q62", + "Q63", + "Q64", + "Q65" + ], + "czs": [ + [ + "Q00", + "Q06" + ], + [ + "Q00", + "Q07" + ], + [ + "Q01", + "Q07" + ], + [ + "Q01", + "Q08" + ], + [ + "Q02", + "Q08" + ], + [ + "Q02", + "Q09" + ], + [ + "Q03", + "Q09" + ], + [ + "Q03", + "Q10" + ], + [ + "Q04", + "Q10" + ], + [ + "Q04", + "Q11" + ], + [ + "Q05", + "Q11" + ], + [ + "Q06", + "Q12" + ], + [ + "Q07", + "Q12" + ], + [ + "Q07", + "Q13" + ], + [ + "Q08", + "Q13" + ], + [ + "Q08", + "Q14" + ], + [ + "Q09", + "Q14" + ], + [ + "Q09", + "Q15" + ], + [ + "Q10", + "Q15" + ], + [ + "Q10", + "Q16" + ], + [ + "Q11", + "Q16" + ], + [ + "Q11", + "Q17" + ], + [ + "Q12", + "Q18" + ], + [ + "Q12", + "Q19" + ], + [ + "Q13", + "Q19" + ], + [ + "Q13", + "Q20" + ], + [ + "Q14", + "Q20" + ], + [ + "Q14", + "Q21" + ], + [ + "Q15", + "Q21" + ], + [ + "Q15", + "Q22" + ], + [ + "Q16", + "Q22" + ], + [ + "Q16", + "Q23" + ], + [ + "Q17", + "Q23" + ], + [ + "Q18", + "Q24" + ], + [ + "Q19", + "Q24" + ], + [ + "Q19", + "Q25" + ], + [ + "Q20", + "Q25" + ], + [ + "Q20", + "Q26" + ], + [ + "Q21", + "Q26" + ], + [ + "Q21", + "Q27" + ], + [ + "Q22", + "Q27" + ], + [ + "Q22", + "Q28" + ], + [ + "Q23", + "Q28" + ], + [ + "Q23", + "Q29" + ], + [ + "Q24", + "Q30" + ], + [ + "Q24", + "Q31" + ], + [ + "Q25", + "Q31" + ], + [ + "Q25", + "Q32" + ], + [ + "Q26", + "Q32" + ], + [ + "Q26", + "Q33" + ], + [ + "Q27", + "Q33" + ], + [ + "Q27", + "Q34" + ], + [ + "Q28", + "Q34" + ], + [ + "Q28", + "Q35" + ], + [ + "Q29", + "Q35" + ], + [ + "Q30", + "Q36" + ], + [ + "Q31", + "Q36" + ], + [ + "Q31", + "Q37" + ], + [ + "Q32", + "Q37" + ], + [ + "Q32", + "Q38" + ], + [ + "Q33", + "Q38" + ], + [ + "Q33", + "Q39" + ], + [ + "Q34", + "Q39" + ], + [ + "Q34", + "Q40" + ], + [ + "Q35", + "Q40" + ], + [ + "Q35", + "Q41" + ], + [ + "Q36", + "Q42" + ], + [ + "Q36", + "Q43" + ], + [ + "Q37", + "Q43" + ], + [ + "Q37", + "Q44" + ], + [ + "Q38", + "Q44" + ], + [ + "Q38", + "Q45" + ], + [ + "Q39", + "Q45" + ], + [ + "Q39", + "Q46" + ], + [ + "Q40", + "Q46" + ], + [ + "Q40", + "Q47" + ], + [ + "Q41", + "Q47" + ], + [ + "Q42", + "Q48" + ], + [ + "Q43", + "Q48" + ], + [ + "Q43", + "Q49" + ], + [ + "Q44", + "Q49" + ], + [ + "Q44", + "Q50" + ], + [ + "Q45", + "Q50" + ], + [ + "Q45", + "Q51" + ], + [ + "Q46", + "Q51" + ], + [ + "Q46", + "Q52" + ], + [ + "Q47", + "Q52" + ], + [ + "Q47", + "Q53" + ], + [ + "Q48", + "Q54" + ], + [ + "Q48", + "Q55" + ], + [ + "Q49", + "Q55" + ], + [ + "Q49", + "Q56" + ], + [ + "Q50", + "Q56" + ], + [ + "Q50", + "Q57" + ], + [ + "Q51", + "Q57" + ], + [ + "Q51", + "Q58" + ], + [ + "Q52", + "Q58" + ], + [ + "Q52", + "Q59" + ], + [ + "Q53", + "Q59" + ], + [ + "Q54", + "Q60" + ], + [ + "Q55", + "Q60" + ], + [ + "Q55", + "Q61" + ], + [ + "Q56", + "Q61" + ], + [ + "Q56", + "Q62" + ], + [ + "Q57", + "Q62" + ], + [ + "Q57", + "Q63" + ], + [ + "Q58", + "Q63" + ], + [ + "Q58", + "Q64" + ], + [ + "Q59", + "Q64" + ], + [ + "Q59", + "Q65" + ] + ], + "readout": [ + { + "channel": "01", + "qubits": [ + "Q00", + "Q01", + "Q02", + "Q03", + "Q04", + "Q05" + ] + }, + { + "channel": "02", + "qubits": [ + "Q06", + "Q07", + "Q08", + "Q09", + "Q10", + "Q11" + ] + }, + { + "channel": "03", + "qubits": [ + "Q12", + "Q13", + "Q14", + "Q15", + "Q16", + "Q17" + ] + }, + { + "channel": "04", + "qubits": [ + "Q18", + "Q19", + "Q20", + "Q21", + "Q22", + "Q23" + ] + }, + { + "channel": "05", + "qubits": [ + "Q24", + "Q25", + "Q26", + "Q27", + "Q28", + "Q29" + ] + }, + { + "channel": "06", + "qubits": [ + "Q30", + "Q31", + "Q32", + "Q33", + "Q34", + "Q35" + ] + }, + { + "channel": "07", + "qubits": [ + "Q36", + "Q37", + "Q38", + "Q39", + "Q40", + "Q41" + ] + }, + { + "channel": "08", + "qubits": [ + "Q42", + "Q43", + "Q44", + "Q45", + "Q46", + "Q47" + ] + }, + { + "channel": "09", + "qubits": [ + "Q48", + "Q49", + "Q50", + "Q51", + "Q52", + "Q53" + ] + }, + { + "channel": "10", + "qubits": [ + "Q54", + "Q55", + "Q56", + "Q57", + "Q58", + "Q59" + ] + }, + { + "channel": "11", + "qubits": [ + "Q60", + "Q61", + "Q62", + "Q63", + "Q64", + "Q65" + ] + } + ] +} \ No newline at end of file diff --git a/examples/rem_noise/calibration.qu b/examples/rem_noise/calibration.qu new file mode 100644 index 0000000000000000000000000000000000000000..d2989cabfa9df8129189b4017303ea52c908cd95 --- /dev/null +++ b/examples/rem_noise/calibration.qu @@ -0,0 +1,24 @@ +import std_ops +// Z for 0, X for 1, Y for 2 +operation calibration_circuits(num_qubits: int, observable: int[], state_labels:int[]) : unit { + using(qs: qubit[num_qubits]) { + for(int i = 0; i < num_qubits; i += 1) { + if (state_labels[i] == 1){ + // X + X(qs[i]); + } + if (observable[i] == 1) { + H(qs[i]); + H(qs[i]); + } + if (observable[i] == 2) { + // Y + H(qs[i]); + S(qs[i]); + Sdag(qs[i]); + H(qs[i]); + } + measure(qs[i]); + } + } +} \ No newline at end of file diff --git a/examples/rem_noise/rem.ipynb b/examples/rem_noise/rem.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..66426816c91b947fcb106cff92b9c7d021be620e --- /dev/null +++ b/examples/rem_noise/rem.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to use\n", + "***\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以下举例子说明rem的使用流程和接口格式,考虑以下可观测量期望值求解问题。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from quingo import *\n", + "from pathlib import Path\n", + "from quingo.lib.rem import get_corr_exp_value_for_full_matrix_model\n", + "n_qubits = 2\n", + "# observable = 'ZZ'\n", + "observable = [0, 0]\n", + "\n", + "qu_file = Path(\"test.qu\")\n", + "\n", + "from quingo.lib.utils import get_prob_noisy\n", + "\n", + "noise_config = [0, 1], [0.1, 0.1], ['bflip', 'bflip']\n", + "task = Quingo_task(qu_file, \"bell_state\")\n", + "\n", + "noisy_circ_prob = get_prob_noisy(task,noise_config,n_qubits,shots=50)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 0.42, 1: 0.14, 2: 0, 3: 0.44}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "noisy_circ_prob" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "qu_calibration= Path(\"calibration.qu\")\n", + "calibration_circ = Quingo_task(qu_calibration, \"calibration_circuits\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from quingo.lib.utils import calibration_matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(2, [0, 0], [0, 0]), (2, [0, 0], [0, 1]), (2, [0, 0], [1, 0]), (2, [0, 0], [1, 1])]\n" + ] + } + ], + "source": [ + "cali_matrix_fm = calibration_matrix(calibration_circ,2,[0,0],noise_config)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.74, 0.07, 0.1 , 0. ],\n", + " [0.12, 0. , 0.82, 0.09],\n", + " [0.13, 0.84, 0.02, 0.12],\n", + " [0.01, 0.09, 0.06, 0.79]])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cali_matrix_fm" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0.42 0.14 0. 0.44]\n", + "[ 0.5806 -0.1715 0.0235 0.5674]\n" + ] + } + ], + "source": [ + "corr_exp_value_fp = get_corr_exp_value_for_full_matrix_model(n_qubits, noisy_circ_prob, cali_matrix_fm)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 0.49623028762904114,\n", + " 1: -7.704358491026306e-18,\n", + " 2: 0.014963835514714814,\n", + " 3: 0.488805876856244}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "corr_exp_value_fp" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "tmp", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/rem_noise/test.qu b/examples/rem_noise/test.qu new file mode 100644 index 0000000000000000000000000000000000000000..3d9cbf4afb00b4e95d1e66e2d254886f2700578a --- /dev/null +++ b/examples/rem_noise/test.qu @@ -0,0 +1,10 @@ +import std_ops + +operation bell_state() : unit { + using(q : qubit[2]) { + H(q[0]); + CNOT(q[0], q[1]); + measure(q[0]); + measure(q[1]); + } +} \ No newline at end of file diff --git a/examples/xiaohong/host.py b/examples/xiaohong/host.py index 051118ccf8eca9de12af0bcd00ed26884aea302f..ff22041c98d20645000764fd108eed5eea26bea5 100644 --- a/examples/xiaohong/host.py +++ b/examples/xiaohong/host.py @@ -11,9 +11,9 @@ def routine(circ_name, num_shots=1): ExeMode.RealMachine, num_shots, xh_login_key="7e6999bab11453428b8ded1fac00b3ea", - xh_machine_name="Transponder", + xh_machine_name="Transpose", ) - qasm_fn = compile(task, params=(), config_file="") + qasm_fn = compile(task, params=(), config_file="", target="qcloud_sh") res = execute(qasm_fn, BackendType.XIAOHONG, cfg) print("result: ", res) diff --git a/setup.cfg b/setup.cfg index 5bb5b89214f0bb1de87fefcd933e4bb2622a3f76..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,36 +0,0 @@ -[metadata] -name = quingo -version = 0.3.2 -author = Xiang Fu -author_email = gtaifu@gmail.com -use_2to3 = False -description = Quingo Runtime System -long_description = file: README.md -long_description_content_type = text/markdown -url = https://gitee.com/quingo/quingo-runtime -project_urls = - Bug Tracker = https://gitee.com/quingo/quingo-runtime/issues -classifiers = - Programming Language :: Python :: 3 :: Only - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - -[options] -package_dir = - = src -packages =find: -python_requires = >=3.7 -install_requires = - numpy - pyqcisim >= 1.3.1 - symqc >= 1.1.1 - colorama - termcolor - requests - tqdm - qualesim >= 1.0.2 - qualesim-tequila >= 1.0.2 - pyquiet >= 0.0.4 - -[options.packages.find] -where = src \ No newline at end of file diff --git a/setup.py b/setup.py index b908cbe55cb344569d32de1dfc10ca7323828dc5..f5a38316cdb7aa67b2f97138405bfe61680dd47f 100755 --- a/setup.py +++ b/setup.py @@ -1,3 +1,43 @@ -import setuptools +from setuptools import setup, find_packages +import platform -setuptools.setup() +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name="quingo", + version="0.3.3", + author="Xiang Fu", + author_email="gtaifu@gmail.com", + description="Quingo Runtime System", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://gitee.com/quingo/quingo-runtime", + project_urls={ + "Bug Tracker": "https://gitee.com/quingo/quingo-runtime/issues", + }, + classifiers=[ + "Programming Language :: Python :: 3 :: Only", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + ], + package_dir={"": "src"}, + packages=find_packages(where="src"), + python_requires=">=3.7", + install_requires=[ + "numpy", + "pyqcisim >= 1.3.1", + "symqc >= 1.1.1", + "colorama", + "termcolor", + "requests", + "tqdm", + "pyquiet >= 0.0.4", + ], + extras_require={ + ':sys_platform == "linux"': [ + "qualesim >= 1.0.2", + "qualesim-tequila >= 1.0.2", + ], + }, +) diff --git a/src/quingo/backend/__init__.py b/src/quingo/backend/__init__.py index 3a7fb7a538c8990627c711473e27cb56e2cdd3b3..1208a16d6ede238e1f63da24a4882859e7c95060 100755 --- a/src/quingo/backend/__init__.py +++ b/src/quingo/backend/__init__.py @@ -1,6 +1,6 @@ from quingo.backend.backend_hub import BackendType from quingo.backend.pyqcisim_tequila import PyQCISim_tequila from quingo.backend.pyqcisim_quantumsim import PyQCISim_quantumsim -from quingo.backend.qualesim import QuaLeSim +from quingo.backend.qualesim import QuaLeSim_tequila, QuaLeSim_quantumsim from quingo.backend.symqc import IfSymQC from quingo.backend.qisa import Qisa diff --git a/src/quingo/backend/backend_hub.py b/src/quingo/backend/backend_hub.py index a576e44079639c26d1d5b9ef89cbf954ab501a5c..d1fb3751ef744e5a99ce669d584a51a35df8b04e 100755 --- a/src/quingo/backend/backend_hub.py +++ b/src/quingo/backend/backend_hub.py @@ -12,6 +12,7 @@ class BackendType(enum.Enum): XIAOHONG = enum.auto() QUALESIM_TEQUILA = enum.auto() QUALESIM_QUANTUMSIM = enum.auto() + QOS = enum.auto() @singleton @@ -49,17 +50,23 @@ class Backend_hub: Qisa.QCIS, ), BackendType.QUALESIM_TEQUILA: ( - "QuaLeSim", + "QuaLeSim_tequila", "qualesim", True, Qisa.QCIS, ), BackendType.QUALESIM_QUANTUMSIM: ( - "QuaLeSim", + "QuaLeSim_quantumsim", "qualesim", True, Qisa.QCIS, ), + BackendType.QOS: ( + "QOS", + "qos", + False, + Qisa.QCIS, + ), } def support(self, backend_type): diff --git a/src/quingo/backend/if_backend.py b/src/quingo/backend/if_backend.py index 35543fc0741b14843e192d894e60e68c0cd0bc47..039014b80ae7dee89ea563b2171ec88b7076986e 100755 --- a/src/quingo/backend/if_backend.py +++ b/src/quingo/backend/if_backend.py @@ -23,5 +23,8 @@ class If_backend: def upload_program(self, program): raise NotImplementedError + def upload_program_str(self, program: str): + raise NotImplementedError + def execute(self, exe_config: ExeConfig): raise NotImplementedError diff --git a/src/quingo/backend/pyqcisim_quantumsim.py b/src/quingo/backend/pyqcisim_quantumsim.py index 5f3c7e8fc1cb5e1a39cbd44b345e8178c469e90c..6271008da7f15276cb30d7517f4ace0acc30dcdb 100755 --- a/src/quingo/backend/pyqcisim_quantumsim.py +++ b/src/quingo/backend/pyqcisim_quantumsim.py @@ -29,6 +29,10 @@ class PyQCISim_quantumsim(If_backend): program = prog_fn.open("r").read() self.sim.compile(program) + def upload_program_str(self, program: str): + """upload the program string to the simulator.""" + self.sim.compile(program) + def execute(self, exe_config: ExeConfig) -> Union[List | NDArray]: """Execute the given quantum circuit. Args: diff --git a/src/quingo/backend/pyqcisim_tequila.py b/src/quingo/backend/pyqcisim_tequila.py index 42a20052ff41e3172a9bab78d63c8ad9dc78299d..8e535699e5bf26981b3767f6da6e931b2a18a741 100644 --- a/src/quingo/backend/pyqcisim_tequila.py +++ b/src/quingo/backend/pyqcisim_tequila.py @@ -21,6 +21,10 @@ class PyQCISim_tequila(If_backend): program = prog_fn.open("r").read() self.sim.compile(program) + def upload_program_str(self, program: str): + """upload the program string to the simulator.""" + self.sim.compile(program) + def execute(self, exe_config: ExeConfig): """Execute the given quantum circuit. Args: @@ -31,15 +35,26 @@ class PyQCISim_tequila(If_backend): """ if exe_config.mode == ExeMode.SimShots: - return self.sim.simulate("one_shot", exe_config.num_shots) + return self.sim.simulate( + "one_shot", exe_config.num_shots, noise_config=exe_config.noise_config + ) if exe_config.mode == ExeMode.SimFinalResult: - return self.sim.simulate("final_result") + return self.sim.simulate( + "final_result", noise_config=exe_config.noise_config + ) if exe_config.mode == ExeMode.SimStateVector: - names, nd_array_values = self.sim.simulate("state_vector") + names, nd_array_values = self.sim.simulate( + "state_vector", noise_config=exe_config.noise_config + ) return (names, nd_array_values) + if exe_config.mode == ExeMode.SimProbability: + return self.sim.simulate( + "probability", noise_config=exe_config.noise_config + ) + raise ValueError( "Unsupported execution mode ({}) for TEQUILA.".format(exe_config.mode) ) diff --git a/src/quingo/backend/qisa.py b/src/quingo/backend/qisa.py index b98e392303083473468e763bfd1f34a7e95638df..2a801ee63aeadd714ffef7ecbb5027b975402e0c 100644 --- a/src/quingo/backend/qisa.py +++ b/src/quingo/backend/qisa.py @@ -4,6 +4,7 @@ import enum class Qisa(enum.Enum): QCIS = enum.auto() QUIET = enum.auto() + BRANCH_QUIET = enum.auto() eQASM = enum.auto() Quantify = enum.auto() @@ -16,6 +17,8 @@ def get_qisa_name(qisa: Qisa): return "qcis" if qisa == Qisa.QUIET: return "quiets" + if qisa == Qisa.BRANCH_QUIET: + return "branch-quiets" if qisa == Qisa.eQASM: return "eqasm" if qisa == Qisa.Quantify: @@ -30,7 +33,7 @@ def get_suffix(qisa: Qisa): if qisa == Qisa.QCIS: return ".qcis" - if qisa == Qisa.QUIET: + if qisa == Qisa.QUIET or qisa == Qisa.BRANCH_QUIET: return ".qi" if qisa == Qisa.eQASM: return ".eqasm" diff --git a/src/quingo/backend/qos.py b/src/quingo/backend/qos.py new file mode 100755 index 0000000000000000000000000000000000000000..80b04e70dad58945fc688350032cc51598d1ad29 --- /dev/null +++ b/src/quingo/backend/qos.py @@ -0,0 +1,57 @@ +from __future__ import annotations +from numpy.typing import NDArray +from typing import List, Union +import json + +from quingo.utils import ensure_path +from .backend_hub import BackendType +from .if_backend import If_backend +from quingo.core.exe_config import ExeMode, ExeConfig +from pyqos.experiment.data_taking.scan_circuits import RunCircuits + + +class QOS(If_backend): + """A functional QCIS simulation backend using PyQCISim and QuantumSim.""" + + def __init__(self): + super().__init__(BackendType.QOS) + + def upload_program(self, prog_fn): + """ + Upload assembly or binary program to the simulator. + + Args: + prog_fn: the name of the assembly or binary file. + """ + prog_fn = ensure_path(prog_fn) + self.qcis_circuit = prog_fn.open("r").read() + qubits_fn = prog_fn.parent / (prog_fn.stem + ".json") + self.qubits = json.load(qubits_fn.open("r")) + + def execute(self, exe_config: ExeConfig) -> Union[List | NDArray]: + """Execute the given quantum circuit. + Args: + - mode (str): the simulation mode to use: + + The number of shots is specified in exe_config.num_shots, which is only valid for + ExeMode.SimShots. + """ + if exe_config.mode == ExeMode.RealMachine: + # print("qubits = ", self.qubits) + # print("qcis_circuit = ", self.qcis_circuit) + # return "qqqqq" + raw_res = RunCircuits( + qubits=[self.qubits], + use_template=False, + circuits=([self.qcis_circuit] * exe_config.qos_circuit_times), + data_type="P01", + sampling_interval=200e-6, + num_shots=exe_config.num_shots, + ) + raw_res.wait() + frams = raw_res.dataset.get_stream_data() + return frams + + raise ValueError( + "Unsupported execution mode ({}) for QOS.".format(exe_config.mode) + ) diff --git a/src/quingo/backend/qualesim.py b/src/quingo/backend/qualesim.py index 7abcf00d2543ccd5641bd164df03e1922147ff5c..bc54bf24967b5fbf9e687a0b76e201321f1a3466 100644 --- a/src/quingo/backend/qualesim.py +++ b/src/quingo/backend/qualesim.py @@ -5,14 +5,16 @@ from pathlib import Path from quingo.backend.backend_hub import BackendType from quingo.backend.if_backend import If_backend from quingo.core.exe_config import ExeConfig, ExeMode -from qualesim.plugin import Loglevel -from qualesim.host import Simulator +import numpy as np -class QuaLeSim(If_backend): +class QuaLeSim_base(If_backend): def __init__(self, backend_type=BackendType.QUALESIM_QUANTUMSIM): super().__init__(backend_type) + from qualesim.plugin import Loglevel + from qualesim.host import Simulator + self.sim = Simulator(stderr_verbosity=Loglevel.OFF) if backend_type == BackendType.QUALESIM_QUANTUMSIM: self.sim.with_backend("quantumsim", verbosity=Loglevel.OFF) @@ -29,6 +31,8 @@ class QuaLeSim(If_backend): prog_fn = ensure_path(prog_fn) if prog_fn.suffix in [".qcis", ".qi"]: + from qualesim.plugin import Loglevel + self.sim.with_frontend(str(prog_fn), verbosity=Loglevel.OFF) else: raise TypeError( @@ -36,6 +40,9 @@ class QuaLeSim(If_backend): "'.qcis' (for QCIS) and '.qi' (for QUIET-s)".format(prog_fn.suffix) ) + def upload_program_str(self, program: str): + pass + def execute(self, exe_config: ExeConfig): if exe_config.mode == ExeMode.SimShots: measure_mod = "one_shot" @@ -60,16 +67,18 @@ class QuaLeSim(If_backend): ) if exe_config.mode == ExeMode.SimFinalResult: - measure_mod = "one_shot" + measure_mod = "state_vector" try: self.sim.simulate() - res = self.sim.run( - measure_mod=measure_mod, num_shots=exe_config.num_shots - ) + res = self.sim.run(measure_mod=measure_mod) self.sim.stop() - final_state = res["res"] - final_state["quantum"] = tuple(final_state["quantum"]) - return final_state["quantum"] + final_state = eval(res["res"]) + result = dict() + result["classical"] = final_state["classical"] + result["quantum"] = tuple( + (final_state["quantum"][0], np.array(final_state["quantum"][1])) + ) + return result except Exception as e: raise ValueError( @@ -99,3 +108,13 @@ class QuaLeSim(If_backend): exe_config.mode ) ) + + +class QuaLeSim_tequila(QuaLeSim_base): + def __init__(self): + super().__init__(BackendType.QUALESIM_TEQUILA) + + +class QuaLeSim_quantumsim(QuaLeSim_base): + def __init__(self): + super().__init__(BackendType.QUALESIM_QUANTUMSIM) diff --git a/src/quingo/backend/quingo_result_format_spec.md b/src/quingo/backend/quingo_result_format_spec.md index 1df899487f2e7a36db1aba00e87b765119d9ba66..d0b4ddd991bc27b459ed4bdf24408273cb9c3ce3 100644 --- a/src/quingo/backend/quingo_result_format_spec.md +++ b/src/quingo/backend/quingo_result_format_spec.md @@ -42,11 +42,10 @@ end The result shall be: ```python -{ - 'classical': {}, - 'quantum': (['q1', 'q2'], - [(0.7071067811865474+0j), 0j, 0j, (0.7071067811865476+0j)]) -} + ( + ['q1', 'q2'], + [(0.7071067811865474+0j), 0j, 0j, (0.7071067811865476+0j)] + ) ``` ## SymbolicStateVector diff --git a/src/quingo/backend/symqc.py b/src/quingo/backend/symqc.py index 8e39be100d42b96ae12e8635a26fbff6ce893ffd..ab7ab0e90c061dc9ca92133623b2abcbbea5c631 100644 --- a/src/quingo/backend/symqc.py +++ b/src/quingo/backend/symqc.py @@ -3,6 +3,7 @@ from .backend_hub import BackendType from .if_backend import If_backend from quingo.core.exe_config import ExeConfig, ExeMode from symqc.simulator import SymQC +import numpy as np class IfSymQC(If_backend): @@ -25,6 +26,10 @@ class IfSymQC(If_backend): else: raise TypeError("The SymQC simulator can only accept QCIS instructions.") + def upload_program_str(self, program: str): + """upload the program string to the simulator.""" + self.sim.compile(program) + def execute(self, exe_config: ExeConfig): """Execute the given quantum circuit. Args: @@ -38,6 +43,12 @@ class IfSymQC(If_backend): if exe_config.mode == ExeMode.SimFinalResult: raw_res = self.sim.simulate("final_state") + raw_res["quantum"] = ( + raw_res["quantum"][0], + np.array(raw_res["quantum"][1]).reshape( + -1, + ), + ) return raw_res if exe_config.mode == ExeMode.SimStateVector: diff --git a/src/quingo/backend/xiaohong.py b/src/quingo/backend/xiaohong.py index d170d6368fb8028c1b4381d5cecb2f6f5866c371..b0a4cf11d01dfa978dd5e8a4d436a181cbdc2533 100644 --- a/src/quingo/backend/xiaohong.py +++ b/src/quingo/backend/xiaohong.py @@ -2,7 +2,7 @@ from quingo.utils import ensure_path from .backend_hub import BackendType from .if_backend import If_backend from quingo.core.exe_config import ExeConfig, ExeMode -import pyezQ +from quingo.lib.pyezQ import Account class XiaoHong(If_backend): @@ -21,6 +21,10 @@ class XiaoHong(If_backend): prog_fn = ensure_path(prog_fn) self.qcis_circuit = prog_fn.open("r").read() + def upload_program_str(self, program: str): + """upload the program string to the simulator.""" + self.qcis_circuit = program + def execute(self, exe_config: ExeConfig): """Execute the given quantum circuit. Args: @@ -56,7 +60,7 @@ class XiaoHong(If_backend): return result def set_account(self, login_key, machine_name): - self.account = pyezQ.Account(login_key=login_key, machine_name=machine_name) + self.account = Account(login_key=login_key, machine_name=machine_name) print(f"Set account successfully:") print(f" login key = {login_key[0:5]}" + "*" * (len(login_key) - 5)) print(f" machine name = {machine_name}") diff --git "a/src/quingo/core/build\346\226\207\344\273\266\345\244\271\347\256\241\347\220\206\351\234\200\346\261\202\345\210\206\346\236\220.md" "b/src/quingo/core/build\346\226\207\344\273\266\345\244\271\347\256\241\347\220\206\351\234\200\346\261\202\345\210\206\346\236\220.md" new file mode 100644 index 0000000000000000000000000000000000000000..53d929bf1846da2ec3deb11eadf5bb0d6e989922 --- /dev/null +++ "b/src/quingo/core/build\346\226\207\344\273\266\345\244\271\347\256\241\347\220\206\351\234\200\346\261\202\345\210\206\346\236\220.md" @@ -0,0 +1,8 @@ +文件夹管理的需求是什么? + +- 每个task对应一个build文件夹,还是每个task的每次执行对应一个文件夹? + - 每个task对应一个文件夹。 +- 用户应当可以指定build文件夹的位置。 + - 使用特定的位置:在指定文件夹下创建临时文件夹 + - 若没有指定,则使用/tmp/。使用tmp时,该文件夹被使用完之后,会被删除 +- 用户可以指定,临时文件夹用完之后是否被删除 \ No newline at end of file diff --git a/src/quingo/core/compile.py b/src/quingo/core/compile.py index 07d6c41d30016ed2f9a1c851b8d86eee928936a0..b49bff7fd2125f38b9665653a2b07607257f13a1 100644 --- a/src/quingo/core/compile.py +++ b/src/quingo/core/compile.py @@ -11,22 +11,40 @@ from quingo.backend.qisa import Qisa, get_qisa_name, get_suffix logger = get_logger((__name__).split(".")[-1]) -def compile(task: Quingo_task, params: tuple, qasm_fn: Path = None, config_file=""): +def compile(task: Quingo_task, params: tuple, qasm_fn: Path = None, **kwargs): """Compile the quingo file with given parameters and return the path of the generated qasm file. """ + if "config_file" in kwargs: + config_file = kwargs["config_file"] + else: + config_file = "" + if "target" in kwargs: + target = kwargs["target"] + else: + target = "" + if "chip_path" in kwargs: + chip_path = kwargs["chip_path"] + else: + chip_path = "" + logger.setLevel(logging.INFO) gen_main_file(task.called_qu_fn, task.called_func, task.cl_entry_fn, params) if qasm_fn is None: suffix = get_suffix(task.qisa_type) qasm_fn = task.cl_entry_fn.with_suffix(suffix) + mq_fn = task.cl_entry_fn.with_suffix(".json") else: qasm_fn = ensure_path(qasm_fn) + mq_fn = qasm_fn.stem + ".json" + mq_fn = ensure_path(mq_fn) quingoc_path = Path(get_mlir_path()) - compile_cmd = compose_cl_cmd(task, qasm_fn, quingoc_path, config_file) + compile_cmd = compose_cl_cmd( + task, qasm_fn, quingoc_path, config_file, target, chip_path, mq_fn + ) if task.debug_mode: logger.info(compile_cmd) ret_value = subprocess.run( @@ -51,7 +69,15 @@ def compile(task: Quingo_task, params: tuple, qasm_fn: Path = None, config_file= return qasm_fn -def compose_cl_cmd(task: Quingo_task, qasm_fn: Path, quingoc_path: Path, configfile=""): +def compose_cl_cmd( + task: Quingo_task, + qasm_fn: Path, + quingoc_path: Path, + configfile="", + target="", + chip_path="", + mq_fn="", +): qasm_fn = ensure_path(qasm_fn) quingoc_path = ensure_path(quingoc_path) @@ -70,11 +96,19 @@ def compose_cl_cmd(task: Quingo_task, qasm_fn: Path, quingoc_path: Path, configf config_fn = '--config-fn="{}"'.format(str(configfile)) + chip_path_ = '--chip-config="{}"'.format(str(chip_path)) + + target_ = '--target="{}"'.format(str(target)) + # mq_path = '--mq-path="{}"'.format(str(mq_fn)) + cmd_eles = [ cl_path, cl_entry_fn, opt_inc_dirs, config_fn, + chip_path_, + target_, + # mq_path, opt_isa, opt_qubit_map, opt_out_fn, diff --git a/src/quingo/core/compiler_config.py b/src/quingo/core/compiler_config.py index 6be46f83ecaf44aec92f271fc7d7113d303b87f4..417dd73bb56afba8c5bc1da28a63850628e19427 100644 --- a/src/quingo/core/compiler_config.py +++ b/src/quingo/core/compiler_config.py @@ -97,7 +97,7 @@ def get_mlir_path(): "Cannot find the compiler.\n" " To resolve this problem, you can install quingoc with two ways:\n" ' 1. run the following command "python -m quingo.install_quingoc"\n' - " 2. Dowload quingoc from https://gitee.com/quingo/quingoc-release/" + " 2. Download quingoc from https://gitee.com/quingo/quingoc-release/" "releases and save it at a directory in the system path \n" "or configure its path by calling this method inside python:\n" " `quingo.set_mlir_compiler_path()`" diff --git a/src/quingo/core/exe_config.py b/src/quingo/core/exe_config.py index 0c84518aa5ff1f495a0f6057307390022dc58163..56c4e2d074293427d9b7dc97f9d6d5844bda259c 100644 --- a/src/quingo/core/exe_config.py +++ b/src/quingo/core/exe_config.py @@ -16,6 +16,7 @@ class ExeMode(enum.Enum): SimFinalResult = enum.auto() SimStateVector = enum.auto() SymbolicStateVector = enum.auto() + SimProbability = enum.auto() RealMachine = enum.auto() @@ -26,11 +27,15 @@ class ExeConfig: num_shots: int = 1, xh_login_key: str = None, # use for connecting XIAOHONG xh_machine_name: str = None, # use for connecting XIAOHONG + qos_circuit_times: int = 100, # use for connecting QOS + noise_config=None, ): self.mode = mode self.num_shots = num_shots self.xh_login_key = xh_login_key self.xh_machine_name = xh_machine_name + self.qos_circuit_times = qos_circuit_times + self.noise_config = noise_config def __str__(self) -> str: return str(self.mode) diff --git a/src/quingo/core/manager.py b/src/quingo/core/manager.py index 395085307cabc7277d2c4385ccbe36e70b8b0f1f..2a5fffbea62f9027fca9d69063d676014abcfa79 100755 --- a/src/quingo/core/manager.py +++ b/src/quingo/core/manager.py @@ -13,6 +13,7 @@ from quingo.core.quingo_task import Quingo_task from quingo.core.compile import compile from quingo.backend.backend_hub import BackendType, Backend_hub from quingo.core.quingo_logger import get_logger +from quingo.utils import validate_path logger = get_logger((__name__).split(".")[-1]) @@ -28,7 +29,7 @@ def verify_backend_config(backend: BackendType, exe_config: ExeConfig) -> bool: def execute( - qasm_fn: Path, + qasm_fn_or_str: Path, be_type: BackendType, exe_config: ExeConfig = ExeConfig(), debug_mode=False, @@ -40,13 +41,21 @@ def execute( raise ValueError( "Error configuration {} on the backend {}".format(str(exe_config), backend) ) - execute_cmd = ( - f'simulating instructions "{str(qasm_fn)}" with backend {str(be_type.name)}' - ) + if debug_mode: - logger.info(execute_cmd) + execute_info = "execute the following program with backend {}: \n {}".format( + str(be_type.name), str(qasm_fn_or_str) + ) + logger.info(execute_info) + backend = Backend_hub().get_instance(be_type) - backend.upload_program(qasm_fn) + + qasm_fn = validate_path(qasm_fn_or_str) + if qasm_fn is None: + backend.upload_program_str(qasm_fn_or_str) + else: + backend.upload_program(qasm_fn) + result = backend.execute(exe_config) if exe_config.mode == ExeMode.SimStateVector: names, array_values = result @@ -79,9 +88,9 @@ def call( params: tuple, be_type: BackendType = BackendType.QUANTUM_SIM, exe_config: ExeConfig = ExeConfig(), - config_fn="", + **kwargs, ): """Execute the quingo task on the specified backend and return the result.""" - qasm_fn = compile(task, params, config_file=config_fn) + qasm_fn = compile(task, params, **kwargs) return execute(qasm_fn, be_type, exe_config, debug_mode=task.debug_mode) diff --git a/src/quingo/core/quingo_task.py b/src/quingo/core/quingo_task.py index c662ae4f7c06ab4e19bf75efb6f07b2d5b0e33d7..7519b5564c564786760baf658375e749e3436788 100644 --- a/src/quingo/core/quingo_task.py +++ b/src/quingo/core/quingo_task.py @@ -5,6 +5,8 @@ import tempfile from quingo.backend.backend_hub import BackendType from quingo.backend.qisa import Qisa from quingo.utils import ensure_path +import time +import os DEBUG_MODE = False @@ -15,41 +17,111 @@ def create_empty_dir(dir_path: Path): dir_path.mkdir() +def get_cur_time_as_str(): + cur_time = time.localtime() + return "{:04}{:02}{:02}_{:02}{:02}{:02}".format( + cur_time.tm_year, + cur_time.tm_mon, + cur_time.tm_mday, + cur_time.tm_hour, + cur_time.tm_min, + cur_time.tm_sec, + ) + + class Quingo_task: - def __init__(self, called_qu_fn: Path, called_func: str, **kwargs) -> None: + def __init__( + self, + called_qu_fn: Path, + called_func: str, + build_under=None, + debug_mode=False, + delete_build_dir=True, + qisa=None, + backend=BackendType.QUANTUM_SIM, + qubits_info=None, + ) -> None: """ - Define a quingo task by specifying the quingo file and the entry function. - - Note, this task only stores the following five elements: - - the path of the called Quingo file (called_qu_fn) - - the name of the entry function (called_func) - - the path of the build directory (build_dir) - - debug_mode - - the target qisa (qisa) if specified upon initialization - - the target backend (backend) if specified upon initialization - - If `qisa` and `backend` are not specified, then the value of them will be infered - when required. - - The `build_dir` is calculated at the first time it is called. If the debug mode is - on, then the build_dir is under the same directory as the called Quingo file. - Otherwise, the build_dir is a temporary directory. - - Except them, all other properties are calculated when they are called. By doing so, - only a minimal amount of information is stored in the task object, which reduces the - risk of inconsistency when using this object. + Initializes a Quingo task object. + + This constructor defines a Quingo task by specifying the Quingo file and the entry function. It stores essential information required for executing the Quingo task, including paths, debug mode, target qisa, and backend. Other properties are calculated on-demand to minimize stored information and reduce inconsistency risks. + + Parameters: + - called_qu_fn (Path): The path of the Quingo file to be called. + - called_func (str): The name of the entry function within the Quingo file. + - build_under (Optional[Path]): The path under which the build directory should be created. + If None, the build directory's location is determined based on the debug mode. + - debug_mode (bool): If True, the task runs in debug mode, affecting the location of the + build directory and possibly other behaviors. + - delete_build_dir (bool): delete the build directory after task is completed. + Defaults to True + - qisa (Optional[Any]): The target assembly specification. + - backend (BackendType): The target backend for the Quingo task. Defaults to QuantumSim. + - qubits_info (Optional[Any]): Information about the qubits used in the task. + + Note: + The `build_dir` is calculated the first time it is needed. + In debug mode, it is located in the `build` dir under the current working directory. + Otherwise, it is a temporary directory (like `/tmp/`). """ # file name and function name called_qu_fn = ensure_path(called_qu_fn) self._called_qu_fn = called_qu_fn self._called_func = called_func - self._build_dir = None - self._qubits_info = kwargs.get("qubits_info", None) - self.debug_mode = kwargs.get("debug_mode", False) + self.debug_mode = debug_mode + # qisa and backend - self._qisa = kwargs.get("qisa", None) - self._backend = kwargs.get("backend", BackendType.QUANTUM_SIM) + self._qisa = qisa + self._backend = backend + self._qubits_info = qubits_info + + self.create_build_dir(build_under, delete_build_dir, debug_mode) + + def create_build_dir( + self, build_under=None, delete_build_dir=True, debug_mode=False + ): + """It specifies a temporary build directory for this task. + By default, this build directory will be deleted when the task is destroyed. + + When `build_under` is None, the build directory is under system temporary directory. + Otherwise, a new build directory will be created under this given directory. + + When `debug_mode` is True, and `build_under` is None, a build directory will be created under `/build/`. + + 中文版需求说明: + - 每个task对应一个build文件夹,还是每个task的每次执行对应一个文件夹? + - 每个task对应一个文件夹 + - 如果一个task要进行多次执行,目前直接覆盖 + + - 用户应当可以指定build文件夹的位置。 + - 使用特定的位置:在指定文件夹下创建临时文件夹 + - 若没有指定,则使用/tmp/。使用tmp时,该文件夹被使用完之后,会被删除 + + - 文件夹用完之后默认被删除,但在用户指定下,临时文件夹用完之后可以被保留 + """ + if debug_mode and build_under is None: + self.parent_work_dir = Path.cwd() / gc.build_dirname + else: + self.parent_work_dir = build_under + + self.delete_build_dir = False if debug_mode else delete_build_dir + build_dir_prefix = "qg-" + get_cur_time_as_str() + "-" + + if self.parent_work_dir is not None: + if not self.parent_work_dir.exists(): + self.parent_work_dir.mkdir() + + self.working_dir = tempfile.mkdtemp( + dir=self.parent_work_dir, + prefix=build_dir_prefix, + ) + + assert Path(self.working_dir).exists() + + def __del__(self): + if self.delete_build_dir: + shutil.rmtree(str(self.working_dir)) @property def qubits_info(self): @@ -75,24 +147,8 @@ class Quingo_task: @property def build_dir(self): - """The path of the build directory. - - - In Debug mode, the build directory is under the current directory. - - Otherwise, the build directory is a temporary directory. - """ - # since build_dir can be a temporary directory, we need to record it instead of - # calculating it every time. - if self._build_dir is not None: - return self._build_dir - - if self.debug_mode: # debug mode create `build` dir under the current dir - self._build_dir = Path.cwd() / gc.build_dirname - create_empty_dir(self._build_dir) - - else: - self._build_dir = Path(tempfile.mkdtemp(prefix="quingo-")) - - return self._build_dir + """The path of the build directory. see `create_build_dir` for more details.""" + return Path(self.working_dir).absolute() @property def qisa_type(self): diff --git a/src/quingo/lib/calibration.qu b/src/quingo/lib/calibration.qu new file mode 100644 index 0000000000000000000000000000000000000000..d2989cabfa9df8129189b4017303ea52c908cd95 --- /dev/null +++ b/src/quingo/lib/calibration.qu @@ -0,0 +1,24 @@ +import std_ops +// Z for 0, X for 1, Y for 2 +operation calibration_circuits(num_qubits: int, observable: int[], state_labels:int[]) : unit { + using(qs: qubit[num_qubits]) { + for(int i = 0; i < num_qubits; i += 1) { + if (state_labels[i] == 1){ + // X + X(qs[i]); + } + if (observable[i] == 1) { + H(qs[i]); + H(qs[i]); + } + if (observable[i] == 2) { + // Y + H(qs[i]); + S(qs[i]); + Sdag(qs[i]); + H(qs[i]); + } + measure(qs[i]); + } + } +} \ No newline at end of file diff --git a/src/quingo/lib/pyezQ.py b/src/quingo/lib/pyezQ.py new file mode 100644 index 0000000000000000000000000000000000000000..20466847c861b03ebc41044a3fc888e0ef952a6b --- /dev/null +++ b/src/quingo/lib/pyezQ.py @@ -0,0 +1,1130 @@ +# -*- coding: utf-8 -*- +import json +import requests +import re + +# import os +# import shutil +from time import time, sleep +import random +import numpy as np + +# import traceback +import datetime +from functools import wraps + +# from isqmap import transpile +# from qcis_to_qasm.qcis_to_qasm import QcisToQasm +# from qasm_to_qcis.qasm_to_qcis import QasmToQcis +# from sabreMapper.sabre_mapper import SabreMapper +from typing import List, Optional, Dict, Union + +# from simplify import QCIS_Simplify, QASM_Simplify + + +class Account: + + def __init__( + self, login_key: Optional[str] = None, machine_name: Optional[str] = None + ): + """accout initialization + + Args: + login_key: + API Token under personal center on the web. Defaults to None. Defaults to None. + machine_name: + name of quantum computer. Defaults to None. + + Raises: + Exception: throw an exception when login fails + """ + # self.qasmtoqcis = QasmToQcis() + # self.qcistoqasm = QcisToQasm() + # self.qcis_simplify = QCIS_Simplify() + # self.qasm_simplify = QASM_Simplify() + self.login_key = login_key + self.token = None + self.machine_name = machine_name + cloud_url = "https://quantumcomputer.ac.cn/" + self.base_url = f"{cloud_url}" + self.login = self.log_in() + if self.machine_name: + self.set_machine(self.machine_name) + if self.login == 0: + raise Exception("登录失败") + self.computer_selection_mark = 1 # 0--原来老的12比特 1--新的66比特 + + def reconnect_on_failuer(func, max_retries=2, retry_delay=1): + @wraps(func) + def wrapper(self, *args, **kwargs): + retries = 0 + while retries < max_retries: + try: + result = func(self, *args, **kwargs) + return result + except Exception as e: + print( + f"{func.__name__} execution failed, " + f"try count:{retries + 1} error info:{e}" + ) + if retries == max_retries: + break + if hasattr(e, "code") and e.code == 10100101: + # 说明是token未设置,需要重新登录 + print("user token loss or not set, log in again.") + self.log_in() + sleep(retry_delay) + retries = retries + 1 + raise Exception( + f"function:[{func.__name__}] Max retries exceeded. Attempt {max_retries} times failed. " + ) + + return wrapper + + @reconnect_on_failuer + def log_in(self): + """Authenticate username and password and return user credit + + Returns: + int: log in state, 1 means pass authentication, 0 means failed + + """ + url = f"{self.base_url}/sdk/api/user/login" + data = {"loginToken": self.login_key} + res = requests.post(url, json=data) + status_code = res.status_code + if status_code != 200: + raise Exception(f"登录失败, 请求接口失败, status_code:{status_code}") + result = json.loads(res.text) + code = result.get("code", -1) + msg = result.get("msg", "登录失败") + if code != 0: + print(f"登录失败:{msg}") + raise Exception(f"登录失败:{msg}") + token = result.get("data").get("token") + self.token = token + return 1 + + @reconnect_on_failuer + def create_experiment(self, exp_name: str): + """create a new experiment, the new one is the experiment set ID. + + Args: + exp_name: new experiment collection Name + + Returns: + Union[int, str]: 0 failed, not 0 successful, success returns the experimental set id + """ + if self.computer_selection_mark: + url = f"{self.base_url}/sdk/api/multiple/experiment/save" + else: + url = f"{self.base_url}/sdk/api/experiment/save" + print("当前创建实验使用的机器名:", self.machine_name) + data = { + "experimentClipId": "", + "name": exp_name, + "machineName": self.machine_name, + "source": "SDK", + } + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception(f"创建实验失败, 请求接口失败, status_code:{status_code}") + result = json.loads(res.text) + code = result.get("code", -1) + if code == -10: + raise TokenNotSetException() + msg = result.get("msg", "创建实验失败") + if code != 0: + print(f"创建实验失败:{msg}") + return 0 + lab_id = result.get("data").get("lab_id") + return lab_id + + @reconnect_on_failuer + def save_experiment( + self, lab_id: str, exp_data: str, version: str, is_verify: Optional[bool] = True + ): + """save the experiment and return the experiment ID. + + Args: + lab_id: + the result returned by the create_experiment interface, experimental set id + exp_data: + experimental content, qics + version: + version description + is_verify: + Is the circuit verified.True verify, False do not verify. Defaults to True. + + Examples: + the input parameter can be the following value: + + lab_id: XXX + exp_data: + X Q1 + Y Q12 + S Q3 + SD Q15 + T Q12 + TD Q3 + Z Q12 + H Q1 + RX Q3 2.78 + RY Q9 1.97 + RXY Q15 1.23 3.04 + X2P Q1 + X2M Q1 + Y2P Q3 + Y2M Q12 + X Q19 + CZ Q1 Q7 + RZ Q8 2.16 + I Q1 100 + B Q1 Q12 Q3 + M Q15 Q12 Q5 Q6 + + Returns: + Union[int, str]: 0 failed, not 0 successful, success returns the experiment id + """ + exp_data = exp_data.upper() + exp_data = self.get_experiment_data(exp_data) + if self.computer_selection_mark: + url = f"{self.base_url}/sdk/api/multiple/experiment/detail/save" + else: + url = f"{self.base_url}/sdk/api/experiment/detail/save" + data = { + "circuit": exp_data, + "lab_id": lab_id, + "language": "qcis", + "version": version, + "machineName": self.machine_name, + "is_verify": is_verify, + } + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception(f"保存实验失败, 请求接口失败, status_code:{status_code}") + result = json.loads(res.text) + code = result.get("code", -1) + if code == -10: + raise TokenNotSetException() + msg = result.get("msg", "保存实验失败") + if code != 0: + print(f"保存实验失败:{msg}") + return 0 + save_result = result.get("data").get("exp_id") + + return save_result + + @reconnect_on_failuer + def run_experiment(self, exp_id: str, num_shots: Optional[int] = 12000): + """running the experiment returns the query result id. + + Args: + exp_id: + the result returned by the save_experiment interface, experimental id + num_shots: + number of repetitions per experiment. Defaults to 12000. + + Returns: + Union[int, str]: 0 failed, not 0 successful, success returns the query id. + """ + if self.computer_selection_mark: + url = f"{self.base_url}/sdk/api/multiple/experiment/temporary/save" + else: + url = f"{self.base_url}/sdk/api/experiment/temporary/save" + data = { + "circuit": [""], + "exp_id": exp_id, + "lab_id": "", + "query_id": [""], + "shots": num_shots, + "version": "", + "machineName": self.machine_name, + "source": "SDK", + } + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception(f"运行实验失败, 请求接口失败, status_code:{status_code}") + result = json.loads(res.text) + code = result.get("code", -1) + msg = result.get("msg", "运行实验失败") + if code == -10: + raise TokenNotSetException() + if code != 0: + print(f"运行实验失败:{msg}, {result}") + return 0 + run_result = result.get("data").get("query_id") + return run_result + + @reconnect_on_failuer + def query_experiment( + self, + query_id: Union[str, List[str]], + max_wait_time: Optional[int] = 60, + result_type=2, + ): + """query experimental results + + Args: + query_id: + the result returned by the run_experiment interface, experimental set id + max_wait_time: + maximum waiting time for querying experiments. Defaults to 60. + result_type: + election of return value type of other quantum computer except oneD12, + only probability is returned by oneD12, + result_type value of 0 represents the raw data, + and a value of 1 represents the probability valueDefaults to 2. + + The maximum number of experimental result queries supported by the server is 50. + If there are more than 50, an error message will be displayed. + + Raises: + Exception: query experiment result type error + + Returns: + Union[int, str]: 0 failed, not 0 successful, success returns the experimental result + """ + if isinstance(query_id, str): + query_id = [query_id] + t0 = time() + while time() - t0 < max_wait_time: + try: + if self.computer_selection_mark: + url = f"{self.base_url}/sdk/api/multiple/experiment/find/results" + if result_type not in [0, 1, 2]: + print("查询实验结果类型错误") + return 0 + else: + url = f"{self.base_url}/sdk/api/experiment/find/results" + result_type = 1 + data = {"query_id": query_id, "type": result_type} + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception( + f"查询实验失败, 请求接口失败, status_code:{status_code}" + ) + result = json.loads(res.text) + code = result.get("code", -1) + msg = result.get("msg", "查询实验失败") + if code == -10: + raise TokenNotSetException() + if code != 0: + print(f"查询实验失败:{msg}") + return 0 + query_exp = result.get("data", None) + if query_exp: + return query_exp + except: + import traceback + + print(traceback.format_exc()) + continue + sleep_time = random.randint(0, 15) * 0.3 + round(random.uniform(0, 1.5), 2) + print(f"查询实验结果请等待: {{:.2f}}秒".format(sleep_time)) + sleep(sleep_time) + raise Exception("查询实验结果失败, 实验结果为空") + + @reconnect_on_failuer + def submit_job( + self, + circuit: Optional[Union[List, str]] = None, + exp_name: Optional[str] = "exp0", + parameters: Optional[List[List]] = None, + values: Optional[List[List]] = None, + num_shots: Optional[int] = 12000, + lab_id: Optional[str] = None, + exp_id: Optional[str] = None, + version: Optional[str] = "version01", + is_verify: Optional[bool] = True, + ): + """submit experimental tasks + There are some parameter range limitations when using batch submission circiuts. + 1. circuits length less than 50 + numshots maximum 100000 + the number of measurement qubits is less than 15 + 2. circuits length greater than 50 but less than 100 + numshots maximum 50000 + the number of measurement qubits is less than 30 + 3. circuits length greater than 100 but less than 600 + numshots maximum 10000 + the number of measurement bits is less than the number of all available qubits + + Args: + circuit: + experimental content, qics. Defaults to None. + exp_name: + new experiment collection Name. Defaults to 'exp0'. + parameters: + parameters that need to be assigned in the experimental content. Defaults to None. + values: + The values corresponding to the parameters that need to be assigned in the experimental content. Defaults to None. + num_shots: + number of repetitions per experiment. Defaults to 12000. + lab_id: + the result returned by the create_experiment interface, experimental set id. Defaults to None. + exp_id: + the result returned by the save_experiment interface, experimental id. Defaults to None. + version: + version description. Defaults to 'version01'. + is_verify: + Is the circuit verified.True verify, False do not verify. Defaults to True. + + Returns: + Union[int, str]: 0 failed, not 0 successful, success returns the query id. + """ + if isinstance(circuit, str): + circuit = [circuit] + if len(circuit) > 1: + version = None + if ( + circuit + and parameters + and values + and len(parameters) == len(circuit) == len(values) + ): + new_circuit = self.assign_parameters(circuit, parameters, values) + if not new_circuit: + print("无法为线路赋值,请检查线路,参数和参数值之后重试") + return 0 + else: + new_circuit = circuit + if self.computer_selection_mark: + url = f"{self.base_url}/sdk/api/multiple/experiment/temporary/save" + else: + url = f"{self.base_url}/sdk/api/experiment/temporary/save" + data = { + "circuit": new_circuit, + "exp_id": exp_id, + "lab_id": lab_id, + "name": exp_name, + "shots": num_shots, + "version": version, + "machineName": self.machine_name, + "source": "SDK", + "is_verify": is_verify, + } + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception(f"运行实验失败, 请求接口失败, status_code:{status_code}") + result = json.loads(res.text) + code = result.get("code", -1) + msg = result.get("msg", "运行实验失败") + if code == -10: + raise TokenNotSetException() + if code != 0: + print(f"运行实验失败:{msg}") + return 0 + run_result = result.get("data").get("query_id") + return run_result + + def assign_parameters( + self, circuits: List[str], parameters: List[List], values: List[List] + ): + """Check if the number of parameters, values match the circuit definition + + Args: + circuits: + string, QCIS circuit definition with or without parameter place holder + parameters: + list or ndarray of strings, parameters to be filled + values: + list or ndarray of floats, values to be assigned + + Returns: + circuit: circuit with parameters replaced by values or empty string + empty string occurs when errors prevents parameters to be assigned + """ + new_circuit = [] + for circuit, parameter, value in zip(circuits, parameters, values): + circuit = circuit.upper() + p = re.compile(r"\{(\w+)\}") + circuit_parameters = p.findall(circuit) + if circuit_parameters: + # # 如果values为整数或浮点数,改为列表格式########################################################## + # if isinstance(values, (float, int)): + # values = [values] + # # 如果parameters为字符格式,改为列表格式######################################################### + # if isinstance(parameters, str): + # parameters = [parameters] + + # 将所有parameter变为大写, 否则set(parameters) != set(circuit_parameters) 不通过 ############### + after_parameter = [p.upper() for p in parameter] + + if not value: + error_message = ( + f"线路含有参数{circuit_parameters}, 请提供相应的参数值" + ) + print(error_message) + return "" + + else: + if len(circuit_parameters) != len(value): + error_message = f"线路含有{len(circuit_parameters)}个参数, 您提供了{len(value)}个参数值" + print(error_message) + return "" + + elif after_parameter and len(circuit_parameters) != len( + after_parameter + ): + error_message = f"线路含有{len(circuit_parameters)}个参数, 您提供了{len(after_parameter)}个参数" + print(error_message) + return "" + + elif set(after_parameter) != set(circuit_parameters): + error_message = "线路中的参数与您输入的参数名称不符" + print(error_message) + else: + param_dic = {} + ############################# 这个转化可以删了 ######################################### + # parameters_upper = [p.upper() for p in parameters] + for p, v in zip(after_parameter, value): + param_dic[p] = v + expData = circuit.format(**param_dic) + new_circuit.append(expData) + elif parameter or value: + error_message = "线路定义中不含有参数,无法接受您输入的参数或参数值" + print(error_message) + return "" + else: + expData = circuit + new_circuit.append(expData) + return new_circuit + + def get_experiment_data(self, circuit: str): + """Parse circuit description and generate + experiment script and extract number of measured qubits. + + Args: + circuit: + string, QCIS circuit + + Returns: + expData: + string, transformed circuit + """ + # get gates from circuit + if self.login_key: + gates_list = circuit.split("\n") + gates_list_strip = [g.strip() for g in gates_list if g] + gates_list_strip = [g for g in gates_list_strip if g] + + # transform circuit from QCIS to expData + expData = "\n".join(gates_list_strip) + return expData + else: + gates_list = circuit.split("\n") + gates_list_strip = [g.strip() for g in gates_list if g] + gates_list_strip = [g for g in gates_list_strip if g] + + # transform circuit from QCIS to expData + expData = ";".join(gates_list_strip) + return expData + + @reconnect_on_failuer + def set_machine(self, machine_name: str): + """set the machine name. + + Args: + machine_name: name of quantum computer. + + Raises: + Exception: Failed to set machine name, request interface failed. + Exception: Failed to set machine name. + """ + url = f"{self.base_url}/sdk/api/quantum/computer/verify" + data = { + "experimentClipId": "", + "machineName": machine_name, + "name": "", + "source": "SDK", + } + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception( + f"下载实验参数失败, 请求接口失败, status_code:{status_code}" + ) + result = json.loads(res.text) + code = result.get("code", -1) + msg = result.get("msg", "设置机器名失败") + if code == -10: + raise TokenNotSetException() + if code != 0: + print(f"设置机器名失败:{msg}") + return 0 + self.machine_name = machine_name + data = result.get("data") + if data: + self.computer_selection_mark = 0 + else: + self.computer_selection_mark = 1 + + @reconnect_on_failuer + def download_config(self, read_time=None, down_file: Optional[bool] = True): + """except oneD12 quantum computer, download experimental parameters. + + Args: + read_time: + select configuration data according to the reading time, and the parameter format is yyyy-MM-dd HH:mm:ss, Defaults to None. + down_file: + the parameter is True to write to the file, and False to directly return the experimental parameters. Defaults to True. + + Returns: + Union[int, str]: 0 failed, not 0 successful, success returns the experimental parameters. + """ + if not self.computer_selection_mark: + raise Exception( + f"current quantum computer does not support download_config" + ) + url = f"{self.base_url}/sdk/api/multiple/experiment/config/download" + data = {"name": self.machine_name, "readTime": read_time} + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception( + f"下载实验参数失败, 请求接口失败, status_code:{status_code}" + ) + result = json.loads(res.text) + if "code" in result: + msg = result.get("msg", "下载实验参数失败") + print(f"下载实验参数失败:{msg}") + return 0 + cur_time = self.current_time() + if down_file: + with open(f"./{self.machine_name}_config_param_{cur_time}.json", "w") as f: + f.write(json.dumps(result)) + return result + + # def convert_qasm_to_qcis( + # self, + # qasm: str, + # qubit_map: Optional[Dict]=None + # ): + # """convert qasm to qcis. + + # Args: + # qasm: + # qasm. + # qubit_map: + # Number mapping in qasm, where the value is None, + # directly maps bits based on the format of number plus 1. Defaults to None. + # Raises: + # Exception: language conversion failed. + + # Returns: + # str: simplified qcis. + # """ + # qcis_raw = self.qasmtoqcis.convert_qasm_to_qcis( + # qasm, qubit_map=qubit_map) + # simplity_qcis = self.simplify_qcis(qcis_raw) + # return simplity_qcis + + # def convert_qasm_to_qcis_from_file( + # self, + # qasm_file: str, + # qubit_map: Optional[Dict]=None): + # """Read qasm from file and convert it to qcis + + # Args: + # qasm_file: + # qasm file. + # qubit_map: + # Number mapping in qasm, where the value is None, + # directly maps bits based on the format of number plus 1. Defaults to None. + + # Raises: + # Exception: language conversion failed. + + # Returns: + # str: simplified qcis. + # """ + # qcis_raw = self.qasmtoqcis.convert_qasm_to_qcis_from_file( + # qasm_file, qubit_map=qubit_map) + # simplity_qcis = self.simplify_qcis(qcis_raw) + # return simplity_qcis + + # def convert_qcis_to_qasm( + # self, + # qcis: str + # ): + # """convert qcis to qasm. + + # Args: + # qcis: qcis + + # Returns: + # str: converted qasm. + # """ + # qasm_circuit = self.qcistoqasm.convert_qcis_to_qasm(qcis) + # return qasm_circuit + + def qcis_check_regular(self, qcis_raw: str): + """qcis regular check,normal returns 1, abnormal returns 0 + + Args: + qcis_raw: qcis + + Returns: + Union[int, str]: 0 failed, not 0 successful, successfully returned the input qics. + """ + url = f"{self.base_url}/server/api/multiple/experiment/verify" + data = {"quantumComputerName": self.machine_name, "qcis": qcis_raw} + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception( + f"下载实验参数失败, 请求接口失败, status_code:{status_code}" + ) + result = json.loads(res.text) + code = result.get("code", -1) + msg = result.get("msg", "qcis检验失败") + if code == -10: + raise TokenNotSetException() + if code != 0: + print(f"qcis检验失败: {msg}") + return 0 + return qcis_raw + + # def simplify_qcis( + # self, + # qcis_raw: str): + # """simplification of qcis lines. + # If simplification fails, prompt an error message and return the original qcis circuit. + + # Args: + # qcis_raw: qcis + + # Returns: + # str: simplified qcis. + # """ + # simplity_qcis = self.qcis_simplify.simplify(qcis_raw) + # return simplity_qcis + + # def simplify_qasm(self, qasm_raw: str): + # """simplification of qasm lines. + # If simplification fails, prompt an error message and return the original qasm circuit. + + # Args: + # qasm_raw: qasm + + # Returns: + # str: simplified qasm. + # """ + # simplify_qasm = self.qasm_simplify.simplify(qasm_raw) + # return simplify_qasm + + def current_time(self): + """get the current time + + Returns: + str: time string + """ + timestamp = datetime.datetime.fromtimestamp(time()) + str_time = timestamp.strftime("%Y%m%d%H%M%S") + return str_time + + def readout_data_to_state_probabilities(self, result): + state01 = result.get("results") + basis_list = [] + basis_content = "".join( + ["".join([str(s) for s in state]) for state in state01[1:]] + ) + qubits_num = len(state01[0]) # 测量比特个数 + for idx in range(qubits_num): + basis_result = basis_content[idx : len(basis_content) : qubits_num] + basis_list.append([True if res == "1" else False for res in basis_result]) + return basis_list + + # 读取数据转换成量子态概率全部返回 + def readout_data_to_state_probabilities_whole(self, result: Dict): + """read data and convert it into a quantum state probability, all returns. + + Args: + result: the results returned after query_experiment. + + Returns: + Dict: probability + """ + if not self.computer_selection_mark: + raise Exception( + f"{self.machine_name}:current quantum computer does not support computational state probabilities" + ) + basis_list = self.readout_data_to_state_probabilities(result) + probabilities = self.original_onversion_whole(basis_list) + return probabilities + + # 读取数据转换成量子态概率部分,概率为0不返回 + def readout_data_to_state_probabilities_part(self, result: Dict): + """read data and convert it into a quantum state probability, do not return with a probability of 0. + + Args: + result: the results returned after query_experiment. + + Returns: + Dict: probability + """ + if not self.computer_selection_mark: + raise Exception( + f"{self.machine_name}:current quantum computer does not support computational state probabilities" + ) + basis_list = self.readout_data_to_state_probabilities(result) + probabilities = self.original_onversion_part(basis_list) + return probabilities + + def original_onversion_whole(self, state01): + # 当state01为一维时转换成二维数据 + if isinstance(state01[0], bool): + state01 = [state01] + n = len(state01) # 读取比特数 + # 测量比特概率限制 + # if n > MAX_QUBIT_NUM: + # print(f'Number of qubits > {MAX_QUBIT_NUM}, cannot calculate probabilities.') + counts = [0] * (2**n) + state01_T = np.transpose(state01) # 转置 + numShots = len(state01_T) # 测量重复次数 + # 统计所有numShots 列 + for num in range(numShots): + k = 0 + for i in range(n): + k += state01_T[num][i] * (2**i) + counts[k] += 1 + # 计算概率 + # P=[counts[k]/numShots for k in range(2**n)] + P = {bin(k)[2:].zfill(n): counts[k] / numShots for k in range(2**n)} + return P + + def original_onversion_part(self, state01): + # 当state01为一维时转换成二维数据 + if isinstance(state01[0], bool): + state01 = [state01] + n = len(state01) # 读取比特数 + # 测量比特概率限制 + # if n > MAX_QUBIT_NUM: + # raise Exception(f'Number of qubits > {MAX_QUBIT_NUM}, cannot calculate probabilities.') + counts = {} + state01_T = np.transpose(state01) # 转置 + numShots = len(state01_T) # 测量重复次数 + # 统计所有numShots 列 + for num in range(numShots): + k = 0 + for i in range(n): + k += state01_T[num][i] * (2**i) + prob_state = bin(k)[2:].zfill(n) + if prob_state not in counts: + counts[prob_state] = 1 + else: + counts[prob_state] += 1 + # 计算概率 + # P=[counts[k]/numShots for k in range(2**n)] + P = {k: v / numShots for k, v in counts.items()} + return P + + # 量子态概率矫正 + def probability_calibration(self, result: Dict, config_json: Optional[Dict] = None): + """correction of the measured probability of 01 quantum state. + + Args: + result: + the results returned after query_experiment. + config_json: + experimental parameters of quantum computer. + config_json value is None, read the latest experimental parameters for calculation. + Defaults to None. + + Raises: + Exception: cannot calibrate probability with fidelity. + + Returns: + Dict: corrected probability. + """ + if not self.computer_selection_mark: + raise Exception( + f"{self.machine_name}:current quantum computer does not support computational probability correction" + ) + CM_CACHE = {} + if config_json is None: + config_json = self.download_config(down_file=False) + qubit_num = [f"Q{i}" for i in result.get("results")[0]] + n = len(qubit_num) # 测量比特个数 + qubits = config_json["readout"]["readoutArray"]["|0> readout fidelity"][ + "qubit_used" + ] + readout_fidelity0 = config_json["readout"]["readoutArray"][ + "|0> readout fidelity" + ]["param_list"] + readout_fidelity1 = config_json["readout"]["readoutArray"][ + "|1> readout fidelity" + ]["param_list"] + iq2probFidelity = [ + [readout_fidelity0[qubits.index(q)], readout_fidelity1[qubits.index(q)]] + for q in qubit_num + ] + P = self.readout_data_to_state_probabilities_whole(result) + Pm = list(P.values()) + if not isinstance(iq2probFidelity[0], list): + iq2probFidelity = [iq2probFidelity] + f = tuple([float(fi) for fi in sum(iq2probFidelity, [])]) + if f not in CM_CACHE: + inv_CM = 1 + for k in iq2probFidelity[::-1]: + F00 = k[0] + F11 = k[1] + if F00 + F11 == 1: + raise Exception( + f"Cannot calibrate probability with fidelity: [{F00}, {F11}]" + ) + inv_cm = np.array([[F11, F11 - 1], [F00 - 1, F00]]) / (F00 + F11 - 1) + inv_CM = np.kron(inv_CM, inv_cm) + CM_CACHE[f] = inv_CM + else: + inv_CM = CM_CACHE[f] + Pi = np.dot(inv_CM, (np.array(Pm, ndmin=2).T)) + Pi = {bin(idx)[2:].zfill(n): k[0] for idx, k in enumerate(Pi)} + return Pi + + # 对矫正后的概率进行修正 + def probability_correction(self, probabilities): + """correction of the measured probability of 01 quantum state. + If there is a probability greater than 1, change this item to 1; + If there is anything less than 0, change the item to 0. + + Args: + probabilities: + corrected probability. + + Returns: + Dict: corrected probability. + """ + abnormal_fidelity_list = list( + filter(lambda x: x < 0 or x > 1, probabilities.values()) + ) + if not abnormal_fidelity_list: + return probabilities + for k, v in probabilities.items(): + if v > 1: + probabilities[k] = 1 + elif v < 0: + probabilities[k] = 0 + fidelity_sum = sum(probabilities.values()) + for k, v in probabilities.items(): + probabilities[k] = v / fidelity_sum + return probabilities + + def get_coupling_map(self, config_json): + qubits = config_json["overview"]["qubits"] + qubits_used = config_json["qubit"]["singleQubit"]["gate error"]["qubit_used"] + disable_qubits = [q for q in qubits if q not in qubits_used] + coupler_map = config_json["overview"]["coupler_map"] + adjacency_list = [] + for Q1, Q2 in coupler_map.values(): + q1 = int(Q1[1:]) + q2 = int(Q2[1:]) + if Q1 in disable_qubits or Q2 in disable_qubits: + continue + adjacency_list.append([q1, q2]) + return adjacency_list + + # def qcis_mapping_isq( + # self, + # qcis_circuit: str, + # initial_layout: Optional[Dict]=None, + # objective: Optional[str]='size', + # seed: Optional[int]=None, + # use_post_opt: Optional[bool]=False): + # """The script transpiles qcis string by searching for a mapping from virtual to physical qubit + # and a swap strategy such that the circuit described by qcis can be fitted into a hardware + # described by the coupling_map, in the meanwhile reduces circuit depth. + + # Args: + # qcis_circuit: qcis circuit + # initial_layout: + # Initial position of virtual qubits on physical qubits. + # If given, this is the initial state in search of virtual to physical qubit mapping + # e.g.: + # {0:4, 1:1, 2:5, 3:2, 4:0, 5:3}. Defaults to None. + # objective: + # size: min. # of added swaps + # depth: min. depth + # no_swap: try best to find an initial mapping requiring no swaps; raise + # an error if fail. Defaults to 'size'. + # seed: + # Set random seed for the stochastic part of the tranpiler. Defaults to None. + # use_post_opt: + # we provide a genetic alg. which utilizes exchange rules for + # swaps to futher min. depth. Defaults to False. + + # Raises: + # TranspileError: if graph specified by coupling map is disconnected. + + # Returns: + # str: qcis string after transpilation + # """ + # if not self.computer_selection_mark: + # raise Exception(f'current quantum computer does not support qcis_mapping_isq') + # config_json = self.download_config(down_file=False) + # coupling_map = self.get_coupling_map(config_json) + # try: + # qasm_circuit = self.convert_qcis_to_qasm(qcis_circuit) + # cur_time = self.current_time() + # qpu_file = f'./{self.machine_name}_config_param_{cur_time}.json' + # with open(qpu_file, 'w') as f: + # f.write(json.dumps(config_json)) + # qasm_transpiled, _, _, _ = transpile(qasm_circuit, + # coupling_map, + # initial_layout=initial_layout, + # objective=objective, + # seed=seed, + # use_post_opt=use_post_opt) + # simplity_qcis = self.convert_qasm_to_qcis(qasm_transpiled) + # os.remove(qpu_file) + # return simplity_qcis + # except Exception as e: + # print(e) + # print(traceback.format_exc()) + # print('circuit mapping error, will submit using the original route') + # os.remove(qpu_file) + # return qcis_circuit + + # def qcis_mapping_sabre( + # self, + # qcis_circuit: str): + # """The script transpiles qcis string by searching for a mapping from virtual to physical qubit + # and a swap strategy such that the circuit described by qcis can be fitted into a hardware + # described by the coupling_map, in the meanwhile reduces circuit depth. + + # Args: + # qcis_circuit: qcis circuit + + # Returns: + # str : qcis after mapping + # """ + # if not self.computer_selection_mark: + # raise Exception(f'current quantum computer does not support qcis_mapping_quingo') + # config_json = self.download_config(down_file=False) + # try: + # # qcis转换成qasm并写入qasm_file中 + # qasm_circuit = self.qcistoqasm.convert_qcis_to_qasm(qcis_circuit) + # folder_path = 'temp' + # if not os.path.exists(folder_path): + # os.makedirs(folder_path) + # qasm_file = f'{folder_path}/qasm.qasm' + # with open(qasm_file, 'w') as f: + # f.write(qasm_circuit) + # sabre = SabreMapper() + # # 组装chip_info_fn映射信息 + # chip_info_fn = {} + # couplings = [] + # coupler_maps = config_json.get('overview').get('coupler_map') + # coupler_used = config_json.get('twoQubitGate').get( + # 'czGate').get('gate error').get('qubit_used') + # cz_gate_error = config_json.get('twoQubitGate').get( + # 'czGate').get('gate error').get('param_list') + # for coupler, error in zip(coupler_used, cz_gate_error): + # fidelity = 1 - error + # coupler_qubit_map = coupler_maps.get(coupler) + # couplings.append( + # {"fidelity": fidelity, "qubit pair": coupler_qubit_map}) + # chip_info_fn['couplings'] = couplings + # qubit_used = config_json.get('qubit').get( + # 'singleQubit').get('gate error').get('qubit_used') + # qubit_gate_error = config_json.get('qubit').get( + # 'singleQubit').get('gate error').get('param_list') + # qubit_fidelity = {} + # for qubit, error in zip(qubit_used, qubit_gate_error): + # qubit_fidelity[qubit] = 1 - error + # chip_info_fn['fidelity'] = qubit_fidelity + # chip_info_fn['has multiple chips'] = False + # chip_info_fn['qubits'] = qubit_used + + # chip_info_fn_file = f'{folder_path}/chip_info_fn.json' + # mapped_qasm_fn_file = f'{folder_path}/mapped_qasm_fn.qasm' + # qubit_mapping_fn_file = f'{folder_path}/qubit_mapping_fn.json' + # with open(chip_info_fn_file, 'w') as f: + # json.dump(chip_info_fn, f) + # # 调用quMapper做mapping操作 + # success = sabre.map_schedule( + # qasm_file, chip_info_fn_file, mapped_qasm_fn_file, qubit_mapping_fn_file) + # if success: + # with open(qubit_mapping_fn_file, 'r') as f: + # qubit_mapping_fn = json.load(f) + # qubit_map = qubit_mapping_fn.get('physical qubits idx') + # # mapping成功,将转换后的qasm转为qcis,其中编号根据physical qubits idx做映射 + # simplity_qcis = self.convert_qasm_to_qcis_from_file( + # mapped_qasm_fn_file, qubit_map) + # shutil.rmtree(folder_path) + # return simplity_qcis + # except Exception as e: + # print(e) + # print(traceback.format_exc()) + # print('circuit mapping error, will submit using the original route') + # shutil.rmtree(folder_path) + # return qcis_circuit + + @reconnect_on_failuer + def get_experiment_circuit(self, query_id: Union[str, List[str]]): + """according to the exp_id obtained experimental circuit + + Args: + query_id: the result returned by the run_experiment interface, experimental set id. + + The maximum number of experimental line queries supported by the server is 50. + If it exceeds 50, an error message will be displayed. + + Returns: + Union[int, List[Dict]]: 0 failed, not 0 successful, success returns the experimental circuit, + The parameters of the returned experimental circuit include qcis、mapQcis and computerQcis, + qcis is the line submitted by the user, mapQcis is the compiled circuit, + computerQcis is a circuit submitted to a quantum computer. + """ + if isinstance(query_id, str): + query_id = [query_id] + if self.computer_selection_mark: + url = f"{self.base_url}/sdk/api/multiple/experiment/detail" + else: + url = f"{self.base_url}/sdk/api/experiment/detail/find" + data = {"query_id": query_id} + headers = {"sdk_token": self.token} + res = requests.post(url, json=data, headers=headers) + status_code = res.status_code + if status_code != 200: + raise Exception( + f"查询实验线路失败, 请求接口失败, status_code:{status_code}" + ) + result = json.loads(res.text) + code = result.get("code", -1) + msg = result.get("msg", "查询实验线路失败") + if code != 0: + print(f"查询实验线路: {msg}") + return 0 + circuit = result.get("data") + return circuit + + +class StandradError(Exception): + def __init__(self, msg="", code=0): + self.msg = msg + self.code = code + + def __str__(self): + return f"[{self.code}] [{self.msg}]" + + +class TokenNotSetException(StandradError): + code = 10100101 + + def __init__(self): + self.msg = f"user token has expired or not set." diff --git a/src/quingo/lib/rem.py b/src/quingo/lib/rem.py new file mode 100644 index 0000000000000000000000000000000000000000..6f02729cc3c61a0604a23763a906c53b350da531 --- /dev/null +++ b/src/quingo/lib/rem.py @@ -0,0 +1,157 @@ +""" +Readout Error Mitigation +""" + +# from qiskit import QuantumCircuit +import numpy as np +from scipy.optimize import minimize +from typing import Dict + + +def get_corr_exp_value_for_full_matrix_model( + n_qubits: int, + noisy_circ_prob: Dict[int, float], + calibration_matrix, +) -> float: + """ + Correct the unbiased estimator of expectation value + + Args: + n_qubits: number of qubits + noisy_circ_prob: probability distribution measured in pauli basis (eigenvector of observable) of target noisy circuit + calibration_matrix: that is estimated value of full noise matrix + + Returns: + corr_exp_value: corrected expectation value + """ + + if len(noisy_circ_prob) != 2**n_qubits: + raise RuntimeError("incorrect length of arg noisy_circ_prob") + noisy_prob_vector = np.zeros(2**n_qubits) + for j in range(len(noisy_prob_vector)): + noisy_prob_vector[j] = noisy_circ_prob.get(j, 0) + # get prob corrected + try: + corr_prob_vector = np.linalg.inv(calibration_matrix) @ noisy_prob_vector + except: + corr_prob_vector = np.linalg.pinv(calibration_matrix) @ noisy_prob_vector + negative_prob_occur = False + for prob_value in corr_prob_vector: + if prob_value < 0: + negative_prob_occur = True + break + if not negative_prob_occur: + corr_circ_prob = {i: corr_prob_vector[i] for i in range(len(corr_prob_vector))} + else: # may cost much time for a large number of qubit + func = lambda x: np.linalg.norm( + calibration_matrix @ x - noisy_prob_vector, ord=2 + ) + cons = [{"type": "eq", "fun": lambda x: sum(x) - 1}] + for i in range(2**n_qubits): + cons.append({"type": "ineq", "fun": lambda x, i=i: x[i]}) + cons.append({"type": "ineq", "fun": lambda x, i=i: 1 - x[i]}) + + x0 = corr_prob_vector + + res = minimize(func, x0, method="SLSQP", constraints=cons) + if res.success: + corr_prob_vector = res.x + corr_circ_prob = {i: corr_prob_vector[i] for i in range(len(corr_prob_vector))} + + return corr_circ_prob + # corr_exp_value = 0 + # for state, prob in corr_circ_prob.items(): + # if fmt_str.format(state).count("1") % 2 == 0: + # corr_exp_value += 1 * prob + # else: + # corr_exp_value += -1 * prob + # return corr_exp_value + + +# Tensor Product Model + + +# def build_calibration_circuits_for_tensor_product_model( +# n_qubits: int, observable: str +# ) -> Tuple[List[QuantumCircuit], List[str]]: +# """ """ + +# state_labels = ["0" * n_qubits, "1" * n_qubits] +# calibration_circuits = [] + +# for label in state_labels: +# circ = QuantumCircuit(n_qubits) +# for i in range(n_qubits): +# if observable[::-1][i] == "Z": +# if label[::-1][i] == "1": +# circ.x(i) +# else: +# pass +# elif observable[::-1][i] == "X": +# if label[::-1][i] == "1": +# circ.x(i) +# circ.h(i) +# else: +# circ.h(i) +# elif observable[::-1][i] == "Y": +# if label[::-1][i] == "1": +# circ.x(i) +# circ.h(i) +# circ.s(i) +# else: +# circ.h(i) +# circ.s(i) +# else: +# raise RuntimeError("unsupported observable") +# calibration_circuits.append(circ) + +# return calibration_circuits, state_labels + + +# def cal_noise_matrix_for_tensor_product_model( +# n_qubits: int, cali_circs_prob: List[Dict[int, float]] +# ): +# """ """ + +# calibration_matrices = [] +# fmt_str = "{:0" + str(n_qubits) + "b}" +# for i in range(n_qubits): +# noise_matrix_i = np.zeros((2, 2)) +# prob_0_1 = prob_1_0 = 0 +# for state, prob in cali_circs_prob[0].items(): +# if fmt_str.format(state)[::-1][i] == "1": +# prob_0_1 += prob +# for state, prob in cali_circs_prob[1].items(): +# if fmt_str.format(state)[::-1][i] == "0": +# prob_1_0 += prob +# noise_matrix_i[0][1] = prob_1_0 +# noise_matrix_i[1][1] = 1 - prob_1_0 +# noise_matrix_i[1][0] = prob_0_1 +# noise_matrix_i[0][0] = 1 - prob_0_1 +# calibration_matrices.append(noise_matrix_i) +# return calibration_matrices + + +# def get_corr_exp_value_for_tensor_product_model( +# n_qubits: int, noisy_circ_prob, calibration_matrices +# ): + +# fmt_str = "{:0" + str(n_qubits) + "b}" +# corr_exp_value = 0 +# e_vector = np.array([1, 1]) +# pauli_Z = np.array([[1, 0], [0, -1]]) +# for state, prob in noisy_circ_prob.items(): +# tmp = 1 +# bin_str = fmt_str.format(state) +# for i in range(n_qubits): +# qubit_i_vector = np.zeros(2) +# qubit_i_vector[int(bin_str[::-1][i])] = 1 +# tmp *= ( +# e_vector +# @ pauli_Z +# @ np.linalg.inv(calibration_matrices[i]) +# @ qubit_i_vector +# ) +# corr_exp_value += tmp * prob + +# return corr_exp_value diff --git a/src/quingo/lib/utils.py b/src/quingo/lib/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..7e327153e70a00dbae4ae4e9db643a2ae132a3c1 --- /dev/null +++ b/src/quingo/lib/utils.py @@ -0,0 +1,80 @@ +from quingo import * +import numpy as np +from pathlib import Path +from typing import List, Dict + + +def cal_noise_matrix_for_full_matrix_model( + n_qubits: int, cali_circs_prob: List[Dict[int, float]] +): + """ + Calculate the noise matrix through measuring the probability distribution of calibration circuits + + Args: + n_qubits: number of qubits + cali_circs_probs: probability distributions measured in pauli basis (eigenvector of observable) of calibration circuits + + Returns: + calibration_matrix: that is estimated value of full noise matrix + """ + calibration_matrix = np.zeros((2**n_qubits, 2**n_qubits)) + for i in range(len(cali_circs_prob)): + for label, value in cali_circs_prob[i].items(): + calibration_matrix[label][i] = value + + return calibration_matrix + + +def get_prob_noisy(task: Quingo_task, noise_config, n_qubits, params=(), shots=32000): + cfg = ExeConfig(ExeMode.SimProbability, num_shots=shots, noise_config=noise_config) + qasm_fn = compile(task, params=params) + sim_result = execute(qasm_fn, BackendType.TEQUILA, cfg) + dic = {} + for i in range(len(sim_result[1])): + dic[i] = sim_result[1][i].real + return dic + + +def b_get_prob_noisy(task: Quingo_task, noise_config, n_qubits, params=(), shots=32000): + cfg = ExeConfig(ExeMode.SimShots, num_shots=shots, noise_config=noise_config) + qasm_fn = compile(task, params=params) + sim_result = execute(qasm_fn, BackendType.TEQUILA, cfg) + + def count_elements(lst): + return np.unique(np.array(lst), return_counts=True, axis=0) + + res = count_elements(sim_result[1]) + + def list_to_int(bit_list): + return sum(1 << i for i, bit in enumerate(bit_list) if bit) + + dic = {} + for i in range(2**n_qubits): + dic[i] = 0 + for i in range(len(res[0])): + dic[list_to_int(res[0][i])] = res[1][i] / shots + return dic + + +def int_to_list(number, bit_length=None): + if bit_length is None: + bit_length = number.bit_length() if number >= 0 else 1 + binary_list = [(number >> i) & 1 for i in range(bit_length - 1, -1, -1)] + return binary_list + + +qu_calibration = Path("calibration_circuits.qu") +calibration_circ = Quingo_task(qu_calibration, "calibration_circuits") + + +def calibration_matrix(calibration_circ, nqubits, observable, noise_config): + cali_circs_fm = [] + for ii in range(2**nqubits): + paramsi = (nqubits, observable, int_to_list(ii, nqubits)) + cali_circs_fm.append(paramsi) + cali_circs_prob_fm = [ + b_get_prob_noisy(calibration_circ, noise_config, nqubits, params, shots=100) + for params in cali_circs_fm + ] + cali_matrix_fm = cal_noise_matrix_for_full_matrix_model(nqubits, cali_circs_prob_fm) + return cali_matrix_fm diff --git a/src/quingo/utils.py b/src/quingo/utils.py index 67cfa2c7c400f1a68e9af6126afdbd829a567c68..a38fff9c8f19e1d08d98177d08498eafaf687f26 100644 --- a/src/quingo/utils.py +++ b/src/quingo/utils.py @@ -1,6 +1,7 @@ import sympy as sp from pathlib import Path import numpy as np +import os def ensure_path(fn) -> Path: @@ -10,6 +11,31 @@ def ensure_path(fn) -> Path: return fn +def validate_path(fn) -> Path: + """ + Validates the given file path. + + Args: + fn (str or Path): The file path to validate. + + Returns: + Path: The validated file path as a `Path` object, or `None` if the path is invalid. + """ + if not isinstance(fn, (str, Path)): + return None + + if isinstance(fn, str): + if os.path.isfile(fn): + return Path(fn).resolve() + else: + return None + + if not fn.exists(): + return None + else: + return Path(fn).resolve() + + def is_number(s): try: float(s) @@ -55,3 +81,81 @@ def state_fidelity(state_a: np.ndarray, state_b: np.ndarray): float: The state fidelity between the two quantum states. """ return np.vdot(state_a, state_b) + + +def verify_qubit_map(old_qubit_order, new_qubit_order): + if len(set(old_qubit_order)) != len(set(new_qubit_order)): + raise ValueError( + "The old and new qubit orders do not have the same qubits: \n{}\n{}".format( + old_qubit_order, new_qubit_order + ) + ) + + +def shuffle_qubits_in_state( + old_qubit_order: list, new_qubit_order, state: np.ndarray +) -> np.ndarray: + """This function shuffles the state vector (`state`) with a given qubit order + (`old_qubit_order`), returns a new state vector representing the same state + but the qubit order is the new one (`new_qubit_order`). + + Note: Little-Endian is used in the qubit order, i.e., the least significant + qubit is at index 0, and the most significant bit is at index n - 1. + + Parameters: + old_qubit_order (list): The old qubit order, e.g., [0, 1, 2]. + new_qubit_order (list): The new qubit order, e.g., [1, 2, 0]. + state (np.ndarray): The state vector to shuffle. + + Returns: + np.ndarray: The new state vector with the new qubit order. + + q0, q1, q2 -> q2, q0, q1 + 0 0 0 -> 0 0 0 + 0 0 1 -> 1 0 0 + 0 1 0 -> 0 0 1 + 0 1 1 -> 1 0 1 + 1 0 0 -> 0 1 0 + 1 0 1 -> 1 1 0 + 1 1 0 -> 0 1 1 + 1 1 1 -> 1 1 1 + + how to get the new idx for the old idx (idx_map)? + 1. get the qubit name for each index in the old qubit order (q1) + 2. get the new qubit index according to the qubit map (2) + 0 -> q0 -> 1 + 1 -> q1 -> 2 + 2 -> q2 -> 0 + idx_map = {0: 1, 1: 2, 2: 0} + + After having the new qubit index for each old qubit index, we can calculate the new index + for the value in the new state vector. For example, we have a state vector with 3 qubits, + and the qubit map is {0: 2, 1: 0, 2: 1}. We can calculate the new index for each value + in the state vector by the following steps: + 1. get the binary representation of the old index. + 2. move the bit to the new position according to the idx_map. + 3. get the new index by converting the binary representation to an integer. + + so, new_state[new_idx] = state[old_idx], where new_idx = idx_map[old_idx] + """ + verify_qubit_map(old_qubit_order, new_qubit_order) + num_qubits = len(old_qubit_order) + + old_qubit_idx = {old_qubit_order[i]: i for i in range(num_qubits)} + new_qubit_idx = {new_qubit_order[i]: i for i in range(num_qubits)} + + idx_map = {} # old qubit index -> new qubit index + for this_old_qubit_idx in range(num_qubits): + qubit_name = old_qubit_order[this_old_qubit_idx] + this_new_qubit_idx = new_qubit_idx[qubit_name] + idx_map[this_old_qubit_idx] = this_new_qubit_idx + + new_state = np.zeros_like(state) + for i in range(len(state)): + new_idx = 0 + for j in range(num_qubits): + # select the j-th bit in i, and move it to the new position + new_idx |= (((1 << j) & i) >> j) << idx_map[j] + + new_state[new_idx] = state[i] + return new_state diff --git a/unittest/backends/test_backends.py b/unittest/backends/test_backends.py index d9f181b6207c08e1e447110ea1cacf118a858694..522729bfa8bed9d20924ef25e8b44dcb92dd49db 100644 --- a/unittest/backends/test_backends.py +++ b/unittest/backends/test_backends.py @@ -1,6 +1,6 @@ from quingo.backend.pyqcisim_tequila import PyQCISim_tequila from quingo.backend.pyqcisim_quantumsim import PyQCISim_quantumsim -from quingo.backend.qualesim import QuaLeSim +from quingo.backend.qualesim import QuaLeSim_quantumsim, QuaLeSim_tequila from quingo.backend.symqc import IfSymQC from quingo.backend.backend_hub import BackendType, Backend_hub from quingo.backend.qisa import Qisa @@ -28,8 +28,8 @@ class Test_backends: assert sim.is_simulator() == is_sim # QuaLeSim_tequila and QuaLeSim_quantumsim default Qisa type is QCIS - # single(QuaLeSim_tequila, BackendType.QUALESIM_TEQUILA, Qisa.QCIS, True) - single(QuaLeSim, BackendType.QUALESIM_QUANTUMSIM, Qisa.QCIS, True) + single(QuaLeSim_tequila, BackendType.QUALESIM_TEQUILA, Qisa.QCIS, True) + single(QuaLeSim_quantumsim, BackendType.QUALESIM_QUANTUMSIM, Qisa.QCIS, True) single(PyQCISim_tequila, BackendType.TEQUILA, Qisa.QCIS, True) single(PyQCISim_quantumsim, BackendType.QUANTUM_SIM, Qisa.QCIS, True) single(IfSymQC, BackendType.SYMQC, Qisa.QCIS, True) @@ -42,14 +42,27 @@ class Test_backends: except Exception as e: assert False, "upload_program failed: {}".format(e) - # single(QuaLeSim_tequila, qcis_fn) - # single(QuaLeSim_tequila, quiet_fn) - single(QuaLeSim, qcis_fn) - single(QuaLeSim, quiet_fn) + single(QuaLeSim_tequila, qcis_fn) + single(QuaLeSim_tequila, quiet_fn) + single(QuaLeSim_quantumsim, qcis_fn) + single(QuaLeSim_quantumsim, quiet_fn) single(PyQCISim_tequila, qcis_fn) single(PyQCISim_quantumsim, qcis_fn) single(IfSymQC, qcis_fn) + def test_upload_program_str(self): + def single(BackendClass, qasm): + sim = BackendClass() + try: + sim.upload_program_str(qasm) + except Exception as e: + assert False, "upload_program failed: {}".format(e) + + qasm_str = "H Q0\nCNOT Q0 Q1\nMEASURE Q0\nMEASURE Q1" + single(PyQCISim_tequila, qasm_str) + single(PyQCISim_quantumsim, qasm_str) + single(IfSymQC, qasm_str) + def test_get_from_hub(self): def single(backend_type, simulator_class): hub = Backend_hub() @@ -58,7 +71,8 @@ class Test_backends: single(BackendType.QUANTUM_SIM, PyQCISim_quantumsim) single(BackendType.TEQUILA, PyQCISim_tequila) - single(BackendType.QUALESIM_QUANTUMSIM, QuaLeSim) + single(BackendType.QUALESIM_QUANTUMSIM, QuaLeSim_quantumsim) + single(BackendType.QUALESIM_TEQUILA, QuaLeSim_tequila) single(BackendType.SYMQC, IfSymQC) diff --git a/unittest/backends/test_qualesim.py b/unittest/backends/test_qualesim.py index aeafbcbe3e27a94e92042ba370c06da9c1e8d5ca..9165b8f35486c505138c99890a42e41f16d7250d 100644 --- a/unittest/backends/test_qualesim.py +++ b/unittest/backends/test_qualesim.py @@ -1,6 +1,6 @@ import pytest from quingo.backend.backend_hub import BackendType -from quingo.backend.qualesim import QuaLeSim +from quingo.backend.qualesim import QuaLeSim_quantumsim, QuaLeSim_tequila from quingo.core.exe_config import ExeConfig, ExeMode from quingo import execute from quingo.utils import number_distance @@ -21,27 +21,14 @@ bell_qasm_fns = [ ] -@pytest.fixture( - scope="module", - params=[BackendType.QUALESIM_QUANTUMSIM, BackendType.QUALESIM_TEQUILA], -) -def get_backend_type(request): - return request.param - - -@pytest.fixture(scope="module") -def get_qualesim(get_backend_type): - backend_type = get_backend_type - return QuaLeSim(backend_type) - - @pytest.fixture(params=bell_qasm_fns) def get_bell_qasm_fn(request): return request.param -def test_call_qualesim(get_qualesim, get_bell_qasm_fn): - simulator = get_qualesim +@pytest.fixture(params=[QuaLeSim_quantumsim, QuaLeSim_tequila]) +def test_call_qualesim(params, get_bell_qasm_fn): + simulator = params simulator.upload_program(get_bell_qasm_fn) num_shots = 10 exe_config = ExeConfig(ExeMode.SimShots, num_shots) diff --git a/unittest/backends/test_result_format.py b/unittest/backends/test_result_format.py index 5805597ac9c80738f02e21359ce73f0886bea4a3..9498581b3df8534259223cbce0b73bc91dbc1fd3 100644 --- a/unittest/backends/test_result_format.py +++ b/unittest/backends/test_result_format.py @@ -64,8 +64,15 @@ def test_final_result_with_msmt(get_simulator, get_num_shots): num_shots = get_num_shots exe_config = ExeConfig(ExeMode.SimFinalResult, num_shots=num_shots) - result = execute(bell_qcis_fn, simulator, exe_config) - print("result: ", result) + result_dict = execute(bell_qcis_fn, simulator, exe_config) + print("result for {}: ".format(simulator), result_dict) + assert "classical" in result_dict and "quantum" in result_dict + assert isinstance(result_dict["classical"], dict) + assert len(result_dict["classical"]) == 2 + # assert len(result_dict["quantum"]) == 2 + # names, state_vec = result_dict["quantum"] + # assert names == [] + # assert state_vec == 1 # both qubits are measured def test_final_result_without_msmt(get_simulator, get_num_shots): @@ -73,8 +80,14 @@ def test_final_result_without_msmt(get_simulator, get_num_shots): num_shots = get_num_shots exe_config = ExeConfig(ExeMode.SimFinalResult, num_shots=num_shots) - result = execute(bell_no_msmt_qcis_fn, simulator, exe_config) - print("result: ", result) + result_dict = execute(bell_no_msmt_qcis_fn, simulator, exe_config) + assert "classical" in result_dict and "quantum" in result_dict + assert isinstance(result_dict["classical"], dict) + assert len(result_dict["classical"]) == 0 + assert len(result_dict["quantum"]) == 2 + names, state_vec = result_dict["quantum"] + assert names == ["Q1", "Q2"] + assert state_vec.shape == (2 ** len(names),) def test_state_vector_without_msmt(get_simulator): @@ -95,6 +108,8 @@ def test_state_vector_with_msmt(get_simulator): exe_config = ExeConfig(ExeMode.SimStateVector) qubit_names, state_vec = execute(bell_qcis_fn, simulator, exe_config) + print("sim: ", simulator) + print("state vec: ", state_vec) assert qubit_names == ["Q1", "Q2"] assert isinstance(state_vec, (list, np.ndarray)) assert state_vec.shape == (4,) diff --git a/unittest/backends/test_sim_modes.py b/unittest/backends/test_sim_modes.py new file mode 100644 index 0000000000000000000000000000000000000000..be91e9275b87ddc18a75ec52b34d176e2e585206 --- /dev/null +++ b/unittest/backends/test_sim_modes.py @@ -0,0 +1,35 @@ +import pytest +from pathlib import Path +from quingo.backend.pyqcisim_tequila import PyQCISim_tequila +from quingo.core.exe_config import ExeConfig, ExeMode + + +# @pytest.fixture( +# scope="module", +# params=[BackendType.TEQUILA], +# ) +# def get_backend_type(request): +# return request.param + + +@pytest.fixture(scope="module") +def get_tequila(): + return PyQCISim_tequila() + + +unittest_dir = Path(__file__).parent / ".." + + +@pytest.fixture(scope="module", params=[unittest_dir / "test_qcis" / "bell.qcis"]) +def get_bell_qasm_fn(request): + return request.param + + +def test_call_tequila(get_tequila, get_bell_qasm_fn): + simulator = get_tequila + simulator.upload_program(get_bell_qasm_fn) + exe_config = ExeConfig(ExeMode.SimProbability) + names, p0s = simulator.execute(exe_config) + assert names == ["Q1", "Q2"] + assert len(p0s) == len(names) + assert all(p0 == pytest.approx(0.5) for p0 in p0s) diff --git a/unittest/test_build_dir.py b/unittest/test_build_dir.py new file mode 100644 index 0000000000000000000000000000000000000000..1b6aa0831d6bb70596219126f20748d5ec32d820 --- /dev/null +++ b/unittest/test_build_dir.py @@ -0,0 +1,73 @@ +from quingo import Quingo_task +import pytest +from pathlib import Path +import shutil +import os + + +@pytest.fixture(scope="module") +def qcis_fn(): + return "bell.qcis" + + +cur_dir = Path(__file__).parent +bell_qu_fn = cur_dir / "test_qu" / "bell.qu" + + +def test_default_build_dir(): + task = Quingo_task(bell_qu_fn, "bell") + build_dir = task.build_dir + + print(build_dir) + assert task.parent_work_dir == None + assert str(build_dir).startswith("/tmp/") + assert build_dir.exists() + assert build_dir.is_dir() + assert build_dir.name.startswith("qg") + + +def test_default_build_dir_debug(): + task = Quingo_task(bell_qu_fn, "bell", debug_mode=True) + build_dir = task.build_dir + + print(build_dir) + assert str(task.parent_work_dir) == os.path.join(os.getcwd(), "build") + assert build_dir.exists() + assert build_dir.is_dir() + assert build_dir.name.startswith("qg") + + shutil.rmtree(str(build_dir)) + + +def test_custom_build_dir_no_delete(): + build_under = cur_dir / "build-test" + task = Quingo_task( + bell_qu_fn, "bell", build_under=build_under, delete_build_dir=False + ) + build_dir = task.build_dir + + print(build_dir) + assert task.parent_work_dir == build_under + assert build_dir.exists() + assert build_dir.is_dir() + assert build_dir.name.startswith("qg") + + shutil.rmtree(str(build_dir)) + + +def test_custom_build_dir_with_delete(): + build_under = cur_dir / "build" + task = Quingo_task( + bell_qu_fn, "bell", build_under=build_under, delete_build_dir=True + ) + build_dir = task.build_dir + + print(build_dir) + assert task.parent_work_dir == build_under + assert build_dir.exists() + assert build_dir.is_dir() + assert build_dir.name.startswith("qg") + + del task + + assert not build_dir.exists() diff --git a/unittest/test_compile_cmd.py b/unittest/test_compile_cmd.py index e950c9b9a6deb77a1749f04c9f233e9232947d8f..9b27dc34baddda5b7924279a34c1baa10f01130b 100644 --- a/unittest/test_compile_cmd.py +++ b/unittest/test_compile_cmd.py @@ -25,13 +25,13 @@ class TestCompileCmd: mlir_path = Path(get_mlir_path()) cmd = compose_cl_cmd(task, qasm_fn, mlir_path) cmd_eles = cmd.split() - assert len(cmd_eles) == 10 + assert len(cmd_eles) == 12 assert mlir_path.resolve().samefile(cmd_eles[0].strip('"')) assert cmd_eles[1] == '"{}"'.format(task.cl_entry_fn.resolve()) assert cmd_eles[2] == "-I" assert cmd_eles[4] == "-I" - assert cmd_eles[7] == "--isa=qcis" - assert cmd_eles[8] == "-o" + assert cmd_eles[9] == "--isa=qcis" + assert cmd_eles[10] == "-o" # assert Path(qasm_fn).samefile(cmd_eles[8].strip('"')) def test_compile(self): @@ -40,8 +40,8 @@ class TestCompileCmd: qasm_fn = compile(task, ()) with qasm_fn.open("r") as f: lines = f.readlines() - assert lines[0].strip() == "H Q0" - assert lines[2].strip() == "CZ Q0 Q1" + assert lines[0].strip().split() == ["H", "Q0"] + assert lines[1].strip().split() == ["CNOT", "Q0", "Q1"] def test_compile2(self): bell_fn = qu_dir / "bell.qu" @@ -50,8 +50,8 @@ class TestCompileCmd: assert qasm_fn.samefile(unittest_dir / "out_bell.qcis") with qasm_fn.open("r") as f: lines = f.readlines() - assert lines[0].strip() == "H Q0" - assert lines[2].strip() == "CZ Q0 Q1" + assert lines[0].strip().split() == ["H", "Q0"] + assert lines[1].strip().split() == ["CNOT", "Q0", "Q1"] if __name__ == "__main__": diff --git a/unittest/test_execution.py b/unittest/test_execution.py index c24dff4c031f1220e0b4e1201e386cef9d0d6cda..7c346c7587b1672f7282626a1332384cc070b06d 100644 --- a/unittest/test_execution.py +++ b/unittest/test_execution.py @@ -1,9 +1,37 @@ +import pytest from pathlib import Path from quingo import BackendType, Quingo_task, ExeConfig, ExeMode from quingo import call, compile, execute unittest_dir = Path(__file__).parent qu_file = unittest_dir / "test_qu" / "bell.qu" +qcis_file = unittest_dir / "test_qcis" / "bell.qcis" + + +@pytest.fixture( + scope="module", + params=[qcis_file, "H Q0\nCNOT Q0 Q1\nMEASURE Q0\nMEASURE Q1"], +) +def qasm_fn_or_str(request): + return request.param + + +@pytest.fixture( + scope="module", + params=[BackendType.QUANTUM_SIM, BackendType.TEQUILA, BackendType.SYMQC], +) +def backends_for_qcis(request): + return request.param + + +def test_execute(qasm_fn_or_str, backends_for_qcis): + num_shot = 10 + cfg = ExeConfig(ExeMode.SimShots, num_shot) + res = execute(qasm_fn_or_str, backends_for_qcis, cfg) + print(res) + + assert len(res[0]) == 2 + assert len(res[1]) == 10 def test_compile_execute(): diff --git a/unittest/test_utils.py b/unittest/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..1b69e7064c009dc56278911ab96a1e6f34ee0382 --- /dev/null +++ b/unittest/test_utils.py @@ -0,0 +1,76 @@ +import pytest +import numpy as np +from quingo.utils import shuffle_qubits_in_state + + +@pytest.mark.parametrize( + "old_qubit_order, new_qubit_order, state, expected_state", + [ + ( + ["q0", "q1"], + ["q1", "q0"], + np.array([0, 1, 2, 3]), + np.array([0, 2, 1, 3]), + ), + ( + ["q0", "q1"], + ["q0", "q1"], + np.array([0, 1, 2, 3]), + np.array([0, 1, 2, 3]), + ), + ( + ["q0", "q1", "q2"], + ["q2", "q0", "q1"], + np.array([0, 1, 2, 3, 4, 5, 6, 7]), + np.array([0, 4, 1, 5, 2, 6, 3, 7]), + ), + ( + ["q2", "q0", "q1"], + ["q0", "q1", "q2"], + np.array([0, 4, 1, 5, 2, 6, 3, 7]), + np.array([0, 1, 2, 3, 4, 5, 6, 7]), + ), + ], +) +def test_shuffle_qubits_in_state( + old_qubit_order, new_qubit_order, state, expected_state +): + shuffled_state = shuffle_qubits_in_state(old_qubit_order, new_qubit_order, state) + np.testing.assert_array_equal(shuffled_state, expected_state) + + +@pytest.mark.parametrize( + "old_qubit_order, new_qubit_order", + [ + ([0, 1, 2, 3], [2, 3, 1, 0]), + ([0, 1, 2, 3], [3, 2, 1, 0]), + ([0, 1, 2, 3, 4], [2, 4, 3, 1, 0]), + ([2, 4, 3, 1, 0], [0, 1, 2, 3, 4]), + ([0, 1, 2, 3, 4, 5], [2, 4, 3, 5, 1, 0]), + ([0, 1, 2, 3, 4, 5, 6], [2, 6, 4, 3, 5, 1, 0]), + ([0, 1, 2, 3, 4, 5, 6, 7], [2, 7, 1, 3, 5, 4, 6, 0]), + ], +) +def test_single_value_state(old_qubit_order, new_qubit_order): + + num_qubits = len(old_qubit_order) + + old_name_2_idx_map = {old_qubit_order[i]: i for i in range(num_qubits)} + new_name_2_idx_map = {new_qubit_order[i]: i for i in range(num_qubits)} + + for idx in range(num_qubits): + qubit_name = old_qubit_order[idx] + + state = np.zeros(2**num_qubits) + state[1 << old_name_2_idx_map[qubit_name]] = 1 # pure state, e.g., |0010> + print("state: ", state) + + exp_state = np.zeros(2**num_qubits) + # pure state after shuffling, e.g., |0100> + exp_state[1 << new_name_2_idx_map[qubit_name]] = 1 + print("exp_state: ", exp_state) + + shuffled_state = shuffle_qubits_in_state( + old_qubit_order, new_qubit_order, state + ) + print("shuffled_state: ", shuffled_state)