From 44ec09b67ce994d5bd16553a54d59f0c4524da41 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 16 Dec 2024 11:20:19 +0800 Subject: [PATCH 01/30] update version handling and dependencies --- cqlib/VERSION.txt | 1 + cqlib/_version.py | 8 +- poetry.lock | 1281 ------------------------------------------ pyproject.toml | 35 +- requirements-dev.txt | 9 + requirements.txt | 8 + 6 files changed, 34 insertions(+), 1308 deletions(-) create mode 100644 cqlib/VERSION.txt delete mode 100644 poetry.lock create mode 100644 requirements-dev.txt create mode 100644 requirements.txt diff --git a/cqlib/VERSION.txt b/cqlib/VERSION.txt new file mode 100644 index 0000000..cb174d5 --- /dev/null +++ b/cqlib/VERSION.txt @@ -0,0 +1 @@ +1.2.1 \ No newline at end of file diff --git a/cqlib/_version.py b/cqlib/_version.py index 14d2bf1..09cafa4 100644 --- a/cqlib/_version.py +++ b/cqlib/_version.py @@ -13,7 +13,7 @@ """Version number""" - +import os import sys if sys.version_info < (3, 10, 0): # pragma: no cover @@ -23,7 +23,11 @@ if sys.version_info < (3, 10, 0): # pragma: no cover "You can download the latest version of Python at https://www.python.org/downloads/\n" ) +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) +with open(os.path.join(ROOT_DIR, "VERSION.txt")) as version_file: + VERSION = version_file.read().strip() + version: str __version__: str -__version__ = version = '1.2.1' +__version__ = version = VERSION diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index cf19c40..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1281 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "antlr4-python3-runtime" -version = "4.13.1" -description = "ANTLR 4.13.1 runtime for Python 3" -optional = false -python-versions = "*" -files = [ - {file = "antlr4-python3-runtime-4.13.1.tar.gz", hash = "sha256:3cd282f5ea7cfb841537fe01f143350fdb1c0b1ce7981443a2fa8513fddb6d1a"}, - {file = "antlr4_python3_runtime-4.13.1-py3-none-any.whl", hash = "sha256:78ec57aad12c97ac039ca27403ad61cb98aaec8a3f9bb8144f889aa0fa28b943"}, -] - -[[package]] -name = "astroid" -version = "3.2.4" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, - {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} - -[[package]] -name = "certifi" -version = "2024.7.4" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "contourpy" -version = "1.2.1" -description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.9" -files = [ - {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, - {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"}, - {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"}, - {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"}, - {file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"}, - {file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"}, - {file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"}, - {file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"}, - {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"}, - {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"}, - {file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"}, - {file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"}, - {file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"}, - {file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"}, - {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"}, - {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"}, - {file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"}, - {file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"}, - {file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"}, - {file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"}, - {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"}, - {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"}, - {file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"}, - {file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"}, - {file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"}, -] - -[package.dependencies] -numpy = ">=1.20" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "dill" -version = "0.3.8" -description = "serialize all of Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.1.1" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -files = [ - {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, - {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "filelock" -version = "3.15.4" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "fonttools" -version = "4.53.1" -description = "Tools to manipulate font files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, - {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, - {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, - {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, - {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, - {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, - {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, - {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, - {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"}, - {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"}, - {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"}, - {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"}, - {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"}, - {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"}, - {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"}, - {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"}, - {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"}, - {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"}, - {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"}, - {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"}, - {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"}, - {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"}, - {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"}, - {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"}, - {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"}, - {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"}, - {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"}, - {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"}, - {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"}, - {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"}, - {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"}, - {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"}, - {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"}, - {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"}, - {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"}, - {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"}, - {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"}, - {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"}, - {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"}, - {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"}, - {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, - {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] -lxml = ["lxml (>=4.0)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - -[[package]] -name = "identify" -version = "2.6.0" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, - {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "kiwisolver" -version = "1.4.5" -description = "A fast implementation of the Cassowary constraint solver" -optional = false -python-versions = ">=3.7" -files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, -] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "matplotlib" -version = "3.9.1" -description = "Python plotting package" -optional = false -python-versions = ">=3.9" -files = [ - {file = "matplotlib-3.9.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ccd6270066feb9a9d8e0705aa027f1ff39f354c72a87efe8fa07632f30fc6bb"}, - {file = "matplotlib-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:591d3a88903a30a6d23b040c1e44d1afdd0d778758d07110eb7596f811f31842"}, - {file = "matplotlib-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2a59ff4b83d33bca3b5ec58203cc65985367812cb8c257f3e101632be86d92"}, - {file = "matplotlib-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc001516ffcf1a221beb51198b194d9230199d6842c540108e4ce109ac05cc0"}, - {file = "matplotlib-3.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:83c6a792f1465d174c86d06f3ae85a8fe36e6f5964633ae8106312ec0921fdf5"}, - {file = "matplotlib-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:421851f4f57350bcf0811edd754a708d2275533e84f52f6760b740766c6747a7"}, - {file = "matplotlib-3.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b3fce58971b465e01b5c538f9d44915640c20ec5ff31346e963c9e1cd66fa812"}, - {file = "matplotlib-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a973c53ad0668c53e0ed76b27d2eeeae8799836fd0d0caaa4ecc66bf4e6676c0"}, - {file = "matplotlib-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd5acf8f3ef43f7532c2f230249720f5dc5dd40ecafaf1c60ac8200d46d7eb"}, - {file = "matplotlib-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab38a4f3772523179b2f772103d8030215b318fef6360cb40558f585bf3d017f"}, - {file = "matplotlib-3.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2315837485ca6188a4b632c5199900e28d33b481eb083663f6a44cfc8987ded3"}, - {file = "matplotlib-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0c977c5c382f6696caf0bd277ef4f936da7e2aa202ff66cad5f0ac1428ee15b"}, - {file = "matplotlib-3.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:565d572efea2b94f264dd86ef27919515aa6d629252a169b42ce5f570db7f37b"}, - {file = "matplotlib-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d397fd8ccc64af2ec0af1f0efc3bacd745ebfb9d507f3f552e8adb689ed730a"}, - {file = "matplotlib-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26040c8f5121cd1ad712abffcd4b5222a8aec3a0fe40bc8542c94331deb8780d"}, - {file = "matplotlib-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12cb1837cffaac087ad6b44399d5e22b78c729de3cdae4629e252067b705e2b"}, - {file = "matplotlib-3.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0e835c6988edc3d2d08794f73c323cc62483e13df0194719ecb0723b564e0b5c"}, - {file = "matplotlib-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:44a21d922f78ce40435cb35b43dd7d573cf2a30138d5c4b709d19f00e3907fd7"}, - {file = "matplotlib-3.9.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0c584210c755ae921283d21d01f03a49ef46d1afa184134dd0f95b0202ee6f03"}, - {file = "matplotlib-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11fed08f34fa682c2b792942f8902e7aefeed400da71f9e5816bea40a7ce28fe"}, - {file = "matplotlib-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0000354e32efcfd86bda75729716b92f5c2edd5b947200be9881f0a671565c33"}, - {file = "matplotlib-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db17fea0ae3aceb8e9ac69c7e3051bae0b3d083bfec932240f9bf5d0197a049"}, - {file = "matplotlib-3.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:208cbce658b72bf6a8e675058fbbf59f67814057ae78165d8a2f87c45b48d0ff"}, - {file = "matplotlib-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:dc23f48ab630474264276be156d0d7710ac6c5a09648ccdf49fef9200d8cbe80"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3fda72d4d472e2ccd1be0e9ccb6bf0d2eaf635e7f8f51d737ed7e465ac020cb3"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:84b3ba8429935a444f1fdc80ed930babbe06725bcf09fbeb5c8757a2cd74af04"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b918770bf3e07845408716e5bbda17eadfc3fcbd9307dc67f37d6cf834bb3d98"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f1f2e5d29e9435c97ad4c36fb6668e89aee13d48c75893e25cef064675038ac9"}, - {file = "matplotlib-3.9.1.tar.gz", hash = "sha256:de06b19b8db95dd33d0dc17c926c7c9ebed9f572074b6fac4f65068a6814d010"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -kiwisolver = ">=1.3.1" -numpy = ">=1.23" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=2.3.1" -python-dateutil = ">=2.7" - -[package.extras] -dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mpmath" -version = "1.3.0" -description = "Python library for arbitrary-precision floating-point arithmetic" -optional = false -python-versions = "*" -files = [ - {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, - {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, -] - -[package.extras] -develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] -docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4)"] -tests = ["pytest (>=4.6)"] - -[[package]] -name = "networkx" -version = "3.3" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.10" -files = [ - {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, - {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, -] - -[package.extras] -default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, -] - -[[package]] -name = "openqasm3" -version = "1.0.0" -description = "Reference OpenQASM AST in Python" -optional = false -python-versions = "*" -files = [ - {file = "openqasm3-1.0.0-py3-none-any.whl", hash = "sha256:d4371737b4a49b0d56248ed3d30766a94000bccfb43303ec9c7ead351a1b6cc3"}, - {file = "openqasm3-1.0.0.tar.gz", hash = "sha256:3f2bb1cca855cff114e046bac22d59adbf9b754cac6398961aa6d22588fb688e"}, -] - -[package.extras] -all = ["antlr4-python3-runtime (>=4.7,<4.14)", "importlib-metadata", "pytest (>=6.0)", "pyyaml"] -parser = ["antlr4-python3-runtime (>=4.7,<4.14)", "importlib-metadata"] -tests = ["pytest (>=6.0)", "pyyaml"] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "pillow" -version = "10.4.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - -[[package]] -name = "platformdirs" -version = "4.2.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "3.8.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "pylint" -version = "3.2.6" -description = "python code static checker" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "pylint-3.2.6-py3-none-any.whl", hash = "sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f"}, - {file = "pylint-3.2.6.tar.gz", hash = "sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3"}, -] - -[package.dependencies] -astroid = ">=3.2.4,<=3.3.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, -] -isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pyparsing" -version = "3.1.2" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pytest" -version = "8.3.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-html" -version = "4.1.1" -description = "pytest plugin for generating HTML reports" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_html-4.1.1-py3-none-any.whl", hash = "sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71"}, - {file = "pytest_html-4.1.1.tar.gz", hash = "sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07"}, -] - -[package.dependencies] -jinja2 = ">=3.0.0" -pytest = ">=7.0.0" -pytest-metadata = ">=2.0.0" - -[package.extras] -docs = ["pip-tools (>=6.13.0)"] -test = ["assertpy (>=1.1)", "beautifulsoup4 (>=4.11.1)", "black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "pytest-mock (>=3.7.0)", "pytest-rerunfailures (>=11.1.2)", "pytest-xdist (>=2.4.0)", "selenium (>=4.3.0)", "tox (>=3.24.5)"] - -[[package]] -name = "pytest-metadata" -version = "3.1.1" -description = "pytest plugin for test session metadata" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b"}, - {file = "pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8"}, -] - -[package.dependencies] -pytest = ">=7.0.0" - -[package.extras] -test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "tox (>=3.24.5)"] - -[[package]] -name = "pytest-xdist" -version = "3.6.1" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, - {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sympy" -version = "1.13.1" -description = "Computer algebra system (CAS) in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, - {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, -] - -[package.dependencies] -mpmath = ">=1.1.0,<1.4" - -[package.extras] -dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] - -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tomlkit" -version = "0.13.0" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, - {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "urllib3" -version = "2.2.2" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "virtualenv" -version = "20.26.3" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "135acdd759591b012f60b10d646e4389e66174bba22072fb5bbf5fee794c169c" diff --git a/pyproject.toml b/pyproject.toml index 23a477c..ff7e5eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [project] name = "cqlib" -version = "1.2.1" description = "China Quantum Library" authors = [ { name = "China Telecom Quantum Group Quantum Computing Team", email = "tianyan@chinatelecom.cn" }, @@ -30,27 +29,7 @@ keywords = [ "quantum", "sdk", ] - -dependencies = [ - "matplotlib>=3.7.5", - "networkx>=3.0", - "numpy>=2.1.2", - "requests>=2.31.0", - "sympy>=1.12.0", - "tabulate>=0.9.0", - "openqasm3[parser]>=1.0.0", - "antlr4-python3-runtime>=4.13.2" -] - -[project.optional-dependencies] -dev = [ - "pytest>=8.0.2", - "pylint>=3.2.2", - "pre-commit>=3.7.1", - "pytest-xdist>=3.6.1", - "pytest-html>=4.1.1", - "antlr4-tools>=0.2.1", -] +dynamic = ["version", "dependencies",] [project.urls] Homepage = "https://qc.zdxlz.com/" @@ -59,12 +38,16 @@ Repository = "https://gitee.com/cq-lib/cqlib" Issues = "https://gitee.com/cq-lib/cqlib/issues" [build-system] -requires = ["setuptools>=61.0", "wheel", 'delocate', "poetry-core", 'numpy==2.1.2', 'pipx'] +requires = ["setuptools>=61.0", "wheel", 'delocate', "poetry-core", 'numpy>=2.1.2', 'pipx'] build-backend = "setuptools.build_meta" [tool.setuptools] packages = ["cqlib"] +[tool.setuptools.dynamic] +version = { file = "cqlib/VERSION.txt" } +dependencies = {file = "requirements.txt" } + [tool.cibuildwheel] before-all = "uname -a" skip = "pp* cp36-* cp37-* cp38-* cp39-* *musllinux*" @@ -77,9 +60,11 @@ environment = "MACOSX_DEPLOYMENT_TARGET=12" repair-wheel-command = "delocate-wheel -w {dest_dir} -v {wheel} && pipx run abi3audit --strict --report {wheel}" [tool.cibuildwheel.windows] -repair-wheel-command = ["pip install delvewheel", +repair-wheel-command = [ + "pip install delvewheel", 'delvewheel repair -w {dest_dir} {wheel}', - "pipx run abi3audit --strict --report {wheel}"] + "pipx run abi3audit --strict --report {wheel}" +] [tool.setuptools.package-data] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..5193a70 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,9 @@ +pytest>=8.0.2 +pylint>=3.2.2 +pre-commit>=3.7.1 +pytest-xdist>=3.6.1 +pytest-html>=4.1.1 +antlr4-tools>=0.2.1 +delocate +poetry-core +pipx \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..11f026e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +matplotlib >= 3.7.5 +networkx>=3.0 +numpy>=2.1.2 +requests>=2.31.0 +sympy>=1.12.0 +tabulate>=0.9.0 +openqasm3[parser]>=1.0.0 +antlr4-python3-runtime>=4.13.2 \ No newline at end of file -- Gitee From 69c28d0d9c6405ef2f1ca50b89a8096082641433 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Fri, 14 Feb 2025 09:52:07 +0800 Subject: [PATCH 02/30] build simulator , github action --- .github/workflows/build_linux_2_17.yml | 4 +- .github/workflows/build_mac.yml | 6 +- .github/workflows/build_win.yml | 116 ++++++++++++++--------- cqlib/simulator/statevector_simulator.py | 46 ++------- pyproject.toml | 10 +- setup.py | 77 ++++++++------- 6 files changed, 126 insertions(+), 133 deletions(-) diff --git a/.github/workflows/build_linux_2_17.yml b/.github/workflows/build_linux_2_17.yml index 42fb72e..678d477 100644 --- a/.github/workflows/build_linux_2_17.yml +++ b/.github/workflows/build_linux_2_17.yml @@ -15,8 +15,8 @@ jobs: include: - os: ubuntu-latest # 测试通过 arch: x86_64 - - os: ubuntu-latest # 测试通过 - arch: i686 +# - os: ubuntu-latest # 测试通过 +# arch: i686 runs-on: ${{ matrix.os }} diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml index 2a08f09..bb7a280 100644 --- a/.github/workflows/build_mac.yml +++ b/.github/workflows/build_mac.yml @@ -15,7 +15,7 @@ jobs: include: - os: macos-latest # 测试通过 arch: arm64 - - os: macos-12 # 测试通过 + - os: macos-13 # 测试通过 arch: x86_64 runs-on: ${{ matrix.os }} @@ -42,12 +42,8 @@ jobs: - name: Build wheels with cibuildwheel env: - CIBW_ARCHS_LINUX: ${{ matrix.arch }} - CIBW_ARCHS_WINDOWS: ${{ matrix.arch }} CIBW_ARCHS_MACOS: ${{ matrix.arch }} -# CIBW_BUILD: cp31-* CIBW_ENVIRONMENT_WINDOWS: "CFLAGS=/openmp" - CIBW_PROJECT_REQUIRES_PYTHON: ">=3.10" CIBW_BUILD_VERBOSITY: 1 MACOSX_DEPLOYMENT_TARGET: 12.0 CIBW_MACOSX_DEPLOYMENT_TARGET: 12.0 diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml index 8a8e8bc..f8b2d68 100644 --- a/.github/workflows/build_win.yml +++ b/.github/workflows/build_win.yml @@ -7,50 +7,61 @@ on: branches: [ main ] jobs: - build_wheels_windows_x86: - environment: release - strategy: - fail-fast: false - matrix: - include: - - os: windows-latest - arch: x86 -# - os: windows-latest # 不支持,不折腾了 -# arch: ARM64 - - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout source code - uses: actions/checkout@v4 - - - name: Install cibuildwheel - run: pip install -U cibuildwheel delocate numpy - - - name: Build wheels with cibuildwheel - shell: cmd - env: - CIBW_ENVIRONMENT_WINDOWS: > - PATH="C:\\mingw32\\bin;$PATH" - CC="C:/mingw32/bin/gcc.exe" - LD="C:/mingw32/bin/ld.exe" - CIBW_ARCHS_WINDOWS: ${{ matrix.arch }} -# CIBW_BUILD: cp31?-* - CIBW_PROJECT_REQUIRES_PYTHON: ">=3.10" - CIBW_BUILD_VERBOSITY: 1 - run: | - where gcc - gcc --version - where ld - ld --version - python -m cibuildwheel --output-dir wheelhouse --archs ${{ matrix.arch }} - - - name: Upload built wheels - uses: actions/upload-artifact@v4 - with: - name: wheels-${{ matrix.os }}-${{ matrix.arch }} - path: wheelhouse/*.whl - +# build_wheels_windows_x86: +# environment: release +# strategy: +# fail-fast: false +# matrix: +# include: +# - os: windows-latest +# arch: x86 +## - os: windows-latest # 不支持,不折腾了 +## arch: ARM64 +# +# runs-on: ${{ matrix.os }} +# +# steps: +# - name: Checkout source code +# uses: actions/checkout@v4 +# +# - name: Set up Python +# uses: actions/setup-python@v5 +# id: setpy +# with: +# python-version: | +# 3.10.11 +# 3.11.9 +# 3.12.9 +# 3.13.2 +# architecture: 'x86' +# - run: echo '${{ steps.setpy.outputs }}' +# +# - name: Install cibuildwheel +# run: pip install -U cibuildwheel delocate +# +# - name: Build wheels with cibuildwheel +# shell: cmd +# env: +# CIBW_ENVIRONMENT_WINDOWS: > +# PATH="C:\\mingw32\\bin;$PATH" +# CC="C:/mingw32/bin/gcc.exe" +# LD="C:/mingw32/bin/ld.exe" +# CIBW_ARCHS_WINDOWS: ${{ matrix.arch }} +# CIBW_PROJECT_REQUIRES_PYTHON: '>=3.10' +# CIBW_BUILD_VERBOSITY: 1 +# CIBW_PYTHON_HOME: ${{ steps.setpy.outputs.python-path }} +# run: | +# where gcc +# gcc --version +# where ld +# ld --version +# python -m cibuildwheel --output-dir wheelhouse --archs ${{ matrix.arch }} +# +# - name: Upload built wheels +# uses: actions/upload-artifact@v4 +# with: +# name: wheels-${{ matrix.os }}-${{ matrix.arch }} +# path: wheelhouse/*.whl build_wheels_windows_amd64: environment: release @@ -71,15 +82,26 @@ jobs: - name: Checkout source code uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + id: setpy + with: + python-version: | + 3.10.11 + 3.11.9 + 3.12.9 + 3.13.2 + - run: echo '${{ steps.setpy.outputs }}' + - name: Install cibuildwheel - run: pip install -U cibuildwheel delocate numpy + run: pip install -U cibuildwheel delocate - name: Build wheels with cibuildwheel env: CIBW_ARCHS_WINDOWS: ${{ matrix.arch }} -# CIBW_BUILD: cp31?-* - CIBW_PROJECT_REQUIRES_PYTHON: ">=3.10" + CIBW_PROJECT_REQUIRES_PYTHON: '>=3.10' CIBW_BUILD_VERBOSITY: 1 + CIBW_PYTHON_HOME: ${{ steps.setpy.outputs.python-path }} run: cibuildwheel --output-dir wheelhouse - name: Upload built wheels diff --git a/cqlib/simulator/statevector_simulator.py b/cqlib/simulator/statevector_simulator.py index 8ee9918..3816a08 100644 --- a/cqlib/simulator/statevector_simulator.py +++ b/cqlib/simulator/statevector_simulator.py @@ -42,8 +42,9 @@ from cqlib.circuits.circuit import Circuit from cqlib.circuits.parameter import Parameter from cqlib.exceptions import CqlibError from cqlib.simulator.mergy import Gate, merge_gate -from cqlib.simulator.statevector_simulator_c import get_measure, get_probs, \ - get_sample, get_state, free_memory +# from cqlib.simulator.statevector_simulator_c import get_measure, get_probs, \ +# get_sample, get_state +from cqlib.simulator.statevector_simulator_c import get_state, get_probs, get_measure, get_sample gate_name_map = { "H": 72, @@ -80,33 +81,6 @@ gate_name_map = { } -class CGate(ctypes.Structure): - """ - CGate class represents a quantum gate structure in C, which is used to interface - with the low-level C library for quantum gate simulations. - - """ - _fields_ = [ - ("gate_id", ctypes.c_size_t), - ("qubits", ctypes.POINTER(ctypes.c_uint32)), - ("qubits_len", ctypes.c_size_t), - ("theta", ctypes.POINTER(ctypes.c_double)), - ("mat", ctypes.c_void_p), - ] - - def __init__(self, gate: Gate): - """ - Initializes the CGate object by converting a Python Gate object to the C structure. - """ - super().__init__() - self.gate_id = gate_name_map[gate.name] - # NOTE: may raise key Err, but this is a feature - self.qubits = (len(gate.qubits) * ctypes.c_uint32)(*gate.qubits) - self.qubits_len = len(gate.qubits) - self.theta = (len(gate.theta) * ctypes.c_double)(*gate.theta) - self.mat = gate.mat.ctypes.data_as(ctypes.c_void_p) - - class StatevectorSimulator: """ StatevectorSimulator is a quantum circuit simulator that simulates quantum circuits @@ -170,16 +144,12 @@ class StatevectorSimulator: param = float(param.value(params=self.circuit.parameters_value)) ps.append(param) name = item.instruction.name - # if name in ['CRX', 'CRY', 'CRZ', 'CCX', 'SWAP']: - # name = 'fgate' - # if self.is_fusion: - # raise ValueError(f"gate {item.instruction.name} not support fusion.") self.gates.append(Gate( name=name, qubits=qubits, theta=ps, - mat=np.array(item.instruction) + mat=np.asarray(item.instruction) )) def _check_fusion(self): @@ -224,9 +194,9 @@ class StatevectorSimulator: """ self._check_fusion() gates_list = self.get_gates() - state_array, state_ptr_capsule = get_state(self.nq, 2 ** self.nq, gates_list, self.omp_threads) + state_list, state_ptr_capsule = get_state(self.nq, 2 ** self.nq, gates_list, self.omp_threads) self.state_ptr_capsule = state_ptr_capsule - state = {np.binary_repr(i, width=self.nq): val for i, val in enumerate(state_array)} + state = {np.binary_repr(i, width=self.nq): val for i, val in enumerate(state_list)} return state def probs(self) -> dict[str, np.float64]: @@ -307,6 +277,7 @@ class StatevectorSimulator: # Call get_sample from the C extension module samples_array, samples_ptr_capsule = get_sample( shots, + len(self.measure_qubits), self.measure_ptr_capsule, self.omp_threads, self.nq, @@ -323,10 +294,7 @@ class StatevectorSimulator: if is_raw_data: return samples_array - samples_array = samples_array.astype(np.uint64) mq_len = len(self.measure_qubits) - # powers_of_two = 2 ** np.arange(mq_len)[::-1] - # samples_int = samples_array.dot(powers_of_two).astype(np.uint64) counts = Counter(samples_array) # Convert counts to binary strings result = { diff --git a/pyproject.toml b/pyproject.toml index ff7e5eb..be81d87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,19 +38,16 @@ Repository = "https://gitee.com/cq-lib/cqlib" Issues = "https://gitee.com/cq-lib/cqlib/issues" [build-system] -requires = ["setuptools>=61.0", "wheel", 'delocate', "poetry-core", 'numpy>=2.1.2', 'pipx'] +requires = ["setuptools>=61.0", "wheel", 'delocate', 'pipx'] build-backend = "setuptools.build_meta" [tool.setuptools] packages = ["cqlib"] -[tool.setuptools.dynamic] -version = { file = "cqlib/VERSION.txt" } -dependencies = {file = "requirements.txt" } - [tool.cibuildwheel] before-all = "uname -a" skip = "pp* cp36-* cp37-* cp38-* cp39-* *musllinux*" +test-command = "python -c \"import cqlib.simulator.statevector_simulator_c\"" [tool.cibuildwheel.linux] repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel} && pipx run abi3audit --strict --report {wheel}" @@ -60,10 +57,11 @@ environment = "MACOSX_DEPLOYMENT_TARGET=12" repair-wheel-command = "delocate-wheel -w {dest_dir} -v {wheel} && pipx run abi3audit --strict --report {wheel}" [tool.cibuildwheel.windows] +environment = { CC="gcc", CXX="g++" } repair-wheel-command = [ "pip install delvewheel", 'delvewheel repair -w {dest_dir} {wheel}', - "pipx run abi3audit --strict --report {wheel}" +# "pipx run abi3audit --strict --report {wheel}" ] diff --git a/setup.py b/setup.py index 67b4d58..61672a1 100644 --- a/setup.py +++ b/setup.py @@ -3,44 +3,54 @@ import platform import sys import sysconfig -import numpy - -from setuptools import setup, Extension, find_packages -# from distutils.command.build_ext import build_ext as build_ext_orig +from setuptools import setup, Extension, find_packages, find_namespace_packages from setuptools.command.build_ext import build_ext as build_ext_orig python_paths = sysconfig.get_paths() -python_lib_dir = sysconfig.get_config_var('LIBDIR') +print(f'python_paths: {python_paths}') + +library_dirs = [] +py_limited_api = True if sys.platform == 'win32': - architecture = platform.machine() - cibw_arch = os.environ.get('CIBW_ARCHS_WINDOWS') - if cibw_arch == 'AMD64': - p = r"C:\Users\runneradmin\.nuget\packages\python\3.10.11\tools\libs" - elif cibw_arch == 'arm64': - raise SystemError("当前系统是 ARM64 架构,暂不支持") - elif cibw_arch == 'x86': - p = r"C:\Users\runneradmin\.nuget\packages\pythonx86\3.10.11\tools" + v = sys.version_info + if v.minor == 10: + version = '3.10.11' + elif v.minor == 11: + version = '3.11.9' + elif v.minor == 12: + version = '3.12.9' + elif v.minor == 13: + version = '3.13.2' else: - raise SystemError(f"未知架构: {architecture}") + raise ValueError('不知道的 Python 版本') + arch = os.getenv('CIBW_ARCHS_WINDOWS', 'AMD64') + print(f'arch: {arch}') + if arch == 'AMD64': + arch = 'x64' + python_lib = os.path.join(r'C:\hostedtoolcache\windows\Python', version, arch, 'libs') + print(f'python_lib: {python_lib} {os.path.exists(python_lib)} {os.walk(python_lib)}') os.environ['CC'] = 'gcc' os.environ['LD'] = 'ld' - extra_compile_args = ['-fopenmp'] - extra_link_args = [ - '-fopenmp', - '-lgomp', - '-lpython310', - f"-L{p}", - ] - + extra_compile_args = ['-O3', '-fopenmp', '-static'] + extra_link_args = ['-static', '-fopenmp', '-Wl,-Bstatic', '-lgomp'] + library_dirs = [python_lib] + py_limited_api = False elif sys.platform == 'darwin': os.environ['CC'] = 'gcc-14' os.environ['MACOSX_DEPLOYMENT_TARGET'] = '12.0' libomp_prefix = os.environ.get('LIBOMP_PREFIX', '/opt/homebrew/opt/libomp') - extra_compile_args = ['-fopenmp', f'-I{libomp_prefix}/include'] - extra_link_args = [f'-L{libomp_prefix}/lib', '-fopenmp', - f'-Wl,-rpath,{libomp_prefix}/lib'] + extra_compile_args = [ + '-fopenmp', + f'-I{libomp_prefix}/include' + ] + extra_link_args = [ + f'-L{libomp_prefix}/lib', + '-fopenmp', + '-lomp', + f'-Wl,-rpath,{libomp_prefix}/lib' + ] if platform.machine() == 'arm64': # 针对 ARM 架构(Apple Silicon) extra_compile_args.append('-march=armv8-a') @@ -67,25 +77,25 @@ ext_modules = [ include_dirs=[ 'cqlib/simulator/c_fusion/include', python_paths['include'], - numpy.get_include() ], - library_dirs=[python_paths['platlib']], # 链接库文件所在目录 + library_dirs=[python_paths['platlib']] + library_dirs, # 链接库文件所在目录 sources=[ 'cqlib/simulator/c_fusion/src/c_fusion.c', 'cqlib/simulator/c_fusion/src/state_vector.c', ], - define_macros=[('Py_LIMITED_API', '0x030A0000')], - py_limited_api=True, - # extra_compile_args=extra_compile_args, - extra_compile_args=extra_compile_args + ['-DPy_LIMITED_API=0x030A0000'], + define_macros=[('Py_LIMITED_API', '0x030A0000')] if py_limited_api else [], + py_limited_api=py_limited_api, + extra_compile_args=extra_compile_args + (['-DPy_LIMITED_API=0x030A0000'] if py_limited_api else []), extra_link_args=extra_link_args, ), ] +print(f"find_namespace_packages: {find_namespace_packages(include=['cqlib', 'cqlib.*'])}") +print(f"find_packages: {find_packages(include=['cqlib', 'cqlib.*'])}") setup( name='cqlib', ext_modules=ext_modules, - packages=find_packages(include=['cqlib', 'cqlib.*']), + packages=find_namespace_packages(include=['cqlib', 'cqlib.*']), package_data={ 'cqlib': ['*'], }, @@ -94,7 +104,6 @@ setup( exclude_package_data={ 'cqlib.simulator': ['*.c', '*.h'], }, - py_limited_api='3.10', cmdclass={'build_ext': CustomBuildExt}, - options={"bdist_wheel": {"py_limited_api": "cp310"}}, + options={"bdist_wheel": {"py_limited_api": "cp310"} if py_limited_api else {}}, ) -- Gitee From cee1d90cb7dde4e800f6cc0766b81fa321ba1ad7 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Fri, 14 Feb 2025 10:28:39 +0800 Subject: [PATCH 03/30] fix: add tool.setuptools.dynamic --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index be81d87..7646509 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,10 @@ keywords = [ ] dynamic = ["version", "dependencies",] +[tool.setuptools.dynamic] +version = { file = "cqlib/VERSION.txt" } +dependencies = {file = "requirements.txt" } + [project.urls] Homepage = "https://qc.zdxlz.com/" Documentation = "https://cqlib.readthedocs.io/" @@ -61,6 +65,7 @@ environment = { CC="gcc", CXX="g++" } repair-wheel-command = [ "pip install delvewheel", 'delvewheel repair -w {dest_dir} {wheel}', +# windows 上不使用 abi3,而是每个 Python 版本打包一个 wheel 文件 # "pipx run abi3audit --strict --report {wheel}" ] -- Gitee From 535baf3b96ffebb06bba61bf6c22ca47f5e2feff Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 17 Feb 2025 16:29:41 +0800 Subject: [PATCH 04/30] add statevector sim memory recycling testcase --- cqlib/simulator/statevector_simulator.py | 3 +- requirements-dev.txt | 3 +- tests/simulator/test_statevector_sim.py | 40 +++++++++++++++++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/cqlib/simulator/statevector_simulator.py b/cqlib/simulator/statevector_simulator.py index 3816a08..97539c3 100644 --- a/cqlib/simulator/statevector_simulator.py +++ b/cqlib/simulator/statevector_simulator.py @@ -42,8 +42,7 @@ from cqlib.circuits.circuit import Circuit from cqlib.circuits.parameter import Parameter from cqlib.exceptions import CqlibError from cqlib.simulator.mergy import Gate, merge_gate -# from cqlib.simulator.statevector_simulator_c import get_measure, get_probs, \ -# get_sample, get_state + from cqlib.simulator.statevector_simulator_c import get_state, get_probs, get_measure, get_sample gate_name_map = { diff --git a/requirements-dev.txt b/requirements-dev.txt index 5193a70..2fdfbc8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,4 +6,5 @@ pytest-html>=4.1.1 antlr4-tools>=0.2.1 delocate poetry-core -pipx \ No newline at end of file +pipx +psutil \ No newline at end of file diff --git a/tests/simulator/test_statevector_sim.py b/tests/simulator/test_statevector_sim.py index 17eec02..7c4dee1 100644 --- a/tests/simulator/test_statevector_sim.py +++ b/tests/simulator/test_statevector_sim.py @@ -12,10 +12,12 @@ # that they have been altered from the originals. """Test statevector simulator""" - +import gc import math +import os import numpy as np +import psutil from cqlib import Circuit, Parameter from cqlib.simulator import StatevectorSimulator @@ -697,3 +699,39 @@ def test_u(): '11': 0.67208884 - 0.02346986j } all_close(state, s1) + + +def test_memory(): + """test memory recycling""" + process = psutil.Process(os.getpid()) + initial_mem = process.memory_info().rss + + def run_simulation(num_qubits): + c = Circuit(num_qubits) + for i in range(num_qubits): + c.h(i) + c.measure_all() + + sim = StatevectorSimulator(c) + state = sim.statevector() + return sim, state + + for i in range(10, 21, 2): + print(f'\nqubits: {i}') + sim, state = run_simulation(i) + peak_mem = process.memory_info().rss + del sim + del state + gc.collect() + final_mem = process.memory_info().rss + + used_mem = peak_mem - initial_mem + remaining_mem = final_mem - initial_mem + print(f"initial: {initial_mem / 1024 / 1024:.2f} MB") + print(f"peak: {peak_mem / 1024 / 1024:.2f} MB (+{used_mem / 1024 / 1024:.2f} MB)") + print(f"released: {final_mem / 1024 / 1024:.2f} MB (remain: {remaining_mem / 1024 / 1024:.2f} MB)") + + # Allow a maximum of 10 MB of temporary residual data + max_allowed_leak = 10 * 1024 * 1024 + assert remaining_mem < max_allowed_leak, \ + f"Memory leak detected. Residual memory: {remaining_mem / 1024 / 1024:.2f} MB" -- Gitee From a7fe290d821a3a8b01a02a4bac3b5acaa6339790 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 18 Feb 2025 11:21:47 +0800 Subject: [PATCH 05/30] change networkx to rustworkx --- .pre-commit-config.yaml | 2 +- cqlib/circuits/circuit.py | 30 +++++++------ cqlib/circuits/dag.py | 35 ++++++++------- requirements.txt | 3 +- tests/circuit/__init__.py | 12 +++++ tests/circuit/test_dag.py | 92 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 tests/circuit/test_dag.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 587dc46..663002a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,6 @@ repos: hooks: - id: pylint name: pylint - entry: poetry run pylint + entry: pylint language: system types: [python] diff --git a/cqlib/circuits/circuit.py b/cqlib/circuits/circuit.py index 75f0e52..9a720d7 100644 --- a/cqlib/circuits/circuit.py +++ b/cqlib/circuits/circuit.py @@ -51,7 +51,7 @@ class Circuit: """ self._qubits: dict[str, Qubit] = self._initialize_qubits(qubits) self._parameters: dict[Parameter, Optional[float]] = self._initialize_parameters(parameters) - self._instruction_sequence: list[InstructionData] = [] + self._circuit_data: list[InstructionData] = [] @staticmethod def _initialize_parameters( @@ -111,13 +111,17 @@ class Circuit: """Return number of qubits.""" return len(self._qubits) + def circuit_data(self): + """Circuit data""" + return self._circuit_data + def qubits_path(self) -> dict[Qubit, list[InstructionData]]: """ Constructs a path of operations for each qubit. """ path = defaultdict(list) max_depths = defaultdict(int) - for op in self._instruction_sequence: + for op in self._circuit_data: instruction = op.instruction qubits = op.qubits if instruction.num_qubits != len(qubits): @@ -196,7 +200,7 @@ class Circuit: list[InstructionData]: The sequence of instructions added to the circuit, each represented by an InstructionData object. """ - return self._instruction_sequence + return self._circuit_data def insert(self, instruction: Instruction, qubits: Qubits, index: int = None): """ @@ -210,14 +214,14 @@ class Circuit: """ instruction, qubits = self._prepare_instruction(instruction, qubits) if index is None: - index = len(self._instruction_sequence) + index = len(self._circuit_data) elif not isinstance(index, int): raise TypeError("Index must be an integer or None.") - elif index < 0 or index > len(self._instruction_sequence): + elif index < 0 or index > len(self._circuit_data): raise ValueError(f"Index {index} out of bounds for instructions list of length " - f"{len(self._instruction_sequence)}.") + f"{len(self._circuit_data)}.") - self._instruction_sequence.insert( + self._circuit_data.insert( index, InstructionData(instruction=instruction, qubits=qubits) ) @@ -231,7 +235,7 @@ class Circuit: qubits (Qubits): The qubit(s) to which the instruction is applied. """ instruction, qubits = self._prepare_instruction(instruction, qubits) - self._instruction_sequence.append(InstructionData(instruction=instruction, qubits=qubits)) + self._circuit_data.append(InstructionData(instruction=instruction, qubits=qubits)) def append_instruction_data(self, instruction_data: InstructionData): """ @@ -240,7 +244,7 @@ class Circuit: Args: instruction_data (InstructionData): The instruction_data to be appended. """ - self._instruction_sequence.append(instruction_data) + self._circuit_data.append(instruction_data) def _prepare_instruction(self, instruction: Instruction, qubits: Qubits): """ @@ -646,7 +650,7 @@ class Circuit: Measures all qubits in the circuit that have not yet been measured. """ measured_qubits = set() - for ins in self._instruction_sequence: + for ins in self._circuit_data: if isinstance(ins.instruction, Measure): for qubit in ins.qubits: measured_qubits.add(qubit) @@ -739,7 +743,7 @@ class Circuit: """ Generates a qcis string of all instructions in the circuit. """ - return self._export_circuit_str(self._instruction_sequence, True, self._parameters) + return self._export_circuit_str(self._circuit_data, True, self._parameters) def as_str(self, qcis_compliant: bool = False): """ @@ -750,7 +754,7 @@ class Circuit: if False, it will retain the original format with composite gates. """ params = self._parameters - return self._export_circuit_str(self._instruction_sequence, qcis_compliant, params) + return self._export_circuit_str(self._circuit_data, qcis_compliant, params) @classmethod def _export_circuit_str( @@ -931,6 +935,6 @@ class Circuit: Copy the circuit. """ circuit = Circuit(qubits=self.qubits, parameters=self.parameters) - for item in self._instruction_sequence: + for item in self._circuit_data: circuit.append(item.instruction.copy(), qubits=item.qubits) return circuit diff --git a/cqlib/circuits/dag.py b/cqlib/circuits/dag.py index f25c93d..6682172 100644 --- a/cqlib/circuits/dag.py +++ b/cqlib/circuits/dag.py @@ -13,14 +13,15 @@ """Circuits as Directed Acyclic Graphs.""" -import networkx as nx - +# pylint: disable=E0611 +from rustworkx import PyDiGraph, topological_sort, DAGHasCycle from cqlib.circuits.circuit import Circuit from cqlib.circuits.instruction_data import InstructionData from cqlib.circuits.parameter import Parameter +from cqlib.exceptions import CqlibError -def circuit_to_dag(circuit: Circuit) -> nx.DiGraph: +def circuit_to_dag(circuit: Circuit) -> PyDiGraph: """ Convert a quantum circuit into a Directed Acyclic Graph (DAG). @@ -35,22 +36,24 @@ def circuit_to_dag(circuit: Circuit) -> nx.DiGraph: Returns: nx.DiGraph: The directed acyclic graph representation of the circuit. """ - dag = nx.DiGraph() + dag = PyDiGraph(check_cycle=True) qubit_last_nodes = {} - + node_ids = {} for op in circuit.instruction_sequence: if not isinstance(op, InstructionData): raise TypeError(f"{op} must be instance of InstructionData") - dag.add_node(op, label=str(op)) + node_id = dag.add_node(op) + node_ids[op] = node_id for qubit in op.qubits: if qubit in qubit_last_nodes: - dag.add_edge(qubit_last_nodes[qubit], op) + last_node = qubit_last_nodes[qubit] + dag.add_edge(node_ids[last_node], node_id, f'{last_node}-{op}') qubit_last_nodes[qubit] = op return dag -def dag_to_circuit(dag: nx.DiGraph) -> Circuit: +def dag_to_circuit(dag: PyDiGraph) -> Circuit: """ Converts a Directed Acyclic Graph (DAG) back into a quantum circuit. The DAG is expected to have nodes representing quantum operations @@ -63,15 +66,17 @@ def dag_to_circuit(dag: nx.DiGraph) -> Circuit: Returns: Circuit: A quantum circuit reconstructed from the DAG. """ - if not nx.is_directed_acyclic_graph(dag): - raise ValueError("The provided graph must be acyclic to form a valid quantum circuit.") - + try: + topological_order = topological_sort(dag) + except DAGHasCycle as e: + raise CqlibError("The provided graph must be acyclic to form a valid" + " quantum circuit.") from e circuit = Circuit(0) - topological_order: list[InstructionData] = list(nx.topological_sort(dag)) - - for node in topological_order: + nodes = dag.nodes() + for index in topological_order: + node = nodes[index] if not isinstance(node, InstructionData): - raise ValueError(f"{node} in the DAG must be instance of InstructionData") + raise CqlibError(f"{node} in the DAG must be instance of InstructionData") for qubit in node.qubits: if qubit not in circuit.qubits: circuit.add_qubit(qubit) diff --git a/requirements.txt b/requirements.txt index 11f026e..07f6331 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ requests>=2.31.0 sympy>=1.12.0 tabulate>=0.9.0 openqasm3[parser]>=1.0.0 -antlr4-python3-runtime>=4.13.2 \ No newline at end of file +antlr4-python3-runtime>=4.13.2 +rustworkx>=0.16.0 \ No newline at end of file diff --git a/tests/circuit/__init__.py b/tests/circuit/__init__.py index e69de29..c32c814 100644 --- a/tests/circuit/__init__.py +++ b/tests/circuit/__init__.py @@ -0,0 +1,12 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/tests/circuit/test_dag.py b/tests/circuit/test_dag.py new file mode 100644 index 0000000..fda2af3 --- /dev/null +++ b/tests/circuit/test_dag.py @@ -0,0 +1,92 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Test DAG Circuit """ +import pytest +# pylint: disable=E0611 +from rustworkx import PyDiGraph + +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.instruction_data import InstructionData +from cqlib.circuits.gates import H, CX +from cqlib.circuits.qubit import Qubit +from cqlib.circuits.dag import circuit_to_dag, dag_to_circuit +from cqlib.exceptions import CqlibError + + +class TestCircuitToDAG: + """test Circuit object to dag""" + + def test_basic(self): + """ basic """ + circuit = Circuit(0) + dag = circuit_to_dag(circuit) + assert len(dag.nodes()) == 0 + + circuit = Circuit(2) + circuit.h(0) + circuit.h(1) + circuit.cx(0, 1) + dag = circuit_to_dag(circuit) + assert str(dag.nodes()) == '[H Q0, H Q1, CX Q0 Q1]' + assert str(dag.edges()) == "['H Q0-CX Q0 Q1', 'H Q1-CX Q0 Q1']" + + def test_same_ins(self): + """ test same instruction """ + circuit = Circuit(3) + circuit.h(0) + circuit.h(1) + circuit.h(0) + circuit.h(1) + circuit.cx(0, 1) + circuit.measure_all() + dag = circuit_to_dag(circuit) + assert len(dag.nodes()) == 8 + assert len(dag.edges()) == 6 + assert str(dag.nodes()) == '[H Q0, H Q1, H Q0, H Q1, CX Q0 Q1, M Q0, M Q1, M Q2]' + assert str(dag.edges()) == ("['H Q0-H Q0', 'H Q1-H Q1', 'H Q0-CX Q0 Q1', " + "'H Q1-CX Q0 Q1', 'CX Q0 Q1-M Q0', 'CX Q0 Q1-M Q1']") + + +class TestDAGToCircuit: + """test dag to Circuit object """ + + def test_basic(self): + """ basic """ + dag = PyDiGraph(check_cycle=True) + h0 = dag.add_node(InstructionData(H(), [Qubit(0)])) + h1 = dag.add_node(InstructionData(H(), [Qubit(1)])) + cx_0_1 = dag.add_node(InstructionData(CX(), [Qubit(1), Qubit(0)])) + dag.add_edge(h0, cx_0_1, edge='e1') + dag.add_edge(h1, cx_0_1, edge='e2') + + circuit = dag_to_circuit(dag) + + assert len(circuit.qubits) == 2 + assert set(circuit.qubits) == {Qubit(0), Qubit(1)} + assert len(circuit.circuit_data()) == 3 + assert circuit.as_str() == 'H Q1\nH Q0\nCX Q1 Q0' + + def test_cycle_error(self): + """ + test cycle graph error + """ + dag = PyDiGraph() + h0 = dag.add_node(InstructionData(H(), [Qubit(0)])) + cx_0_1 = dag.add_node(InstructionData(CX(), [Qubit(1), Qubit(0)])) + dag.add_edge(h0, cx_0_1, edge='e1') + dag.add_edge(cx_0_1, h0, edge='e2') + + with pytest.raises(CqlibError) as exc_info: + dag_to_circuit(dag) + assert "The provided graph must be acyclic to form a valid" in str(exc_info.value) -- Gitee From c5afabd834c7dcc7946f286d277c33376f13b412 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Wed, 19 Feb 2025 13:44:56 +0800 Subject: [PATCH 06/30] feat(cqlib/circuits/dag.py): Add topological_layers function and improve node retrieval --- cqlib/circuits/dag.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/cqlib/circuits/dag.py b/cqlib/circuits/dag.py index 6682172..9484d33 100644 --- a/cqlib/circuits/dag.py +++ b/cqlib/circuits/dag.py @@ -14,7 +14,7 @@ """Circuits as Directed Acyclic Graphs.""" # pylint: disable=E0611 -from rustworkx import PyDiGraph, topological_sort, DAGHasCycle +from rustworkx import PyDiGraph, topological_sort, DAGHasCycle, NodeIndices from cqlib.circuits.circuit import Circuit from cqlib.circuits.instruction_data import InstructionData from cqlib.circuits.parameter import Parameter @@ -72,9 +72,9 @@ def dag_to_circuit(dag: PyDiGraph) -> Circuit: raise CqlibError("The provided graph must be acyclic to form a valid" " quantum circuit.") from e circuit = Circuit(0) - nodes = dag.nodes() + for index in topological_order: - node = nodes[index] + node = dag.get_node_data(index) if not isinstance(node, InstructionData): raise CqlibError(f"{node} in the DAG must be instance of InstructionData") for qubit in node.qubits: @@ -86,3 +86,34 @@ def dag_to_circuit(dag: PyDiGraph) -> Circuit: circuit.append_instruction_data(node) return circuit + + +def topological_layers(graph: PyDiGraph) -> list[list[NodeIndices | int]]: + """ + Perform topological sorting to decompose a directed acyclic graph + (DAG) into layers of nodes. + + This function implements a Kahn's algorithm-based approach to determine + the hierarchical layers of nodes in a DAG. Each layer contains nodes that + can be processed simultaneously in dependency resolution scenarios. + + Args: + graph (PyDiGraph): The input directed acyclic graph represented as + a `PyDiGraph` object from the `rustworkx` library. + + Returns: + list[list[NodeIndices]]: A list of lists where each inner list represents + a layer of node IDs. + """ + node_in_degree = {node: graph.in_degree(node) for node in graph.node_indices()} + front_layer = [node_id for node_id, in_degree in node_in_degree.items() if in_degree == 0] + + while front_layer: + yield front_layer + next_layer = [] + for node_id in front_layer: + for successor in graph.successor_indices(node_id): + node_in_degree[successor] -= 1 + if node_in_degree[successor] == 0: + next_layer.append(successor) + front_layer = next_layer -- Gitee From bda39b153e0b5b9a8d7de5ea43da904104dfd72f Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Wed, 19 Feb 2025 13:49:45 +0800 Subject: [PATCH 07/30] refactor(cqlib/circuits/gates): Update controlled gate initialization, add `control_index` and `base_gate`. --- cqlib/circuits/gates/gate.py | 11 ++++++++++- cqlib/circuits/gates/rx.py | 9 ++++++++- cqlib/circuits/gates/ry.py | 9 ++++++++- cqlib/circuits/gates/rz.py | 9 ++++++++- cqlib/circuits/gates/x.py | 12 ++++++++++-- cqlib/circuits/gates/y.py | 3 ++- cqlib/circuits/gates/z.py | 3 ++- 7 files changed, 48 insertions(+), 8 deletions(-) diff --git a/cqlib/circuits/gates/gate.py b/cqlib/circuits/gates/gate.py index 9ee30b0..36c5445 100644 --- a/cqlib/circuits/gates/gate.py +++ b/cqlib/circuits/gates/gate.py @@ -66,11 +66,13 @@ class ControlledGate(Gate): This class represents a quantum gate that has control qubits in addition to target qubits. """ - # pylint: disable=useless-parent-delegation + # pylint: disable=useless-parent-delegation,too-many-arguments,too-many-positional-arguments def __init__( self, name: str, num_qubits: int, + control_index: list[int], + base_gate: Gate, params: List[Union[Parameter, float, complex]], label: Optional[str] = None ): @@ -80,10 +82,17 @@ class ControlledGate(Gate): Args: name (str): The name of the controlled gate. num_qubits (int): The number of qubits the gate acts on. + control_index (list[int]): A list of indices specifying the control qubits + for the gate. Each index represents a qubit within the range of + `[0, num_qubits - 1]` that acts as a control for the gate operation. + base_gate (Gate): Gate object to be controlled. params (list[Parameter | float | complex]): A list of parameters for the gate. label (str | None, optional): An optional label for the gate. Defaults to None. """ super().__init__(name, num_qubits, params, label) + self.control_index = control_index + self.base_gate = base_gate + assert all(map(lambda s: s < self.num_qubits, self.control_index)) def __array__(self, dtype=None): """ diff --git a/cqlib/circuits/gates/rx.py b/cqlib/circuits/gates/rx.py index 30ee91d..3834942 100644 --- a/cqlib/circuits/gates/rx.py +++ b/cqlib/circuits/gates/rx.py @@ -76,7 +76,14 @@ class CRX(ControlledGate): theta (float | Parameter): The rotation angle in radians around the X-axis. label (str | None, optional): An optional label for the CRX gate. Defaults to None. """ - super().__init__('CRX', 2, [theta], label=label) + super().__init__( + 'CRX', + 2, + control_index=[0], + base_gate=RX(theta), + params=[theta], + label=label + ) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/gates/ry.py b/cqlib/circuits/gates/ry.py index ed57070..e2a02ff 100644 --- a/cqlib/circuits/gates/ry.py +++ b/cqlib/circuits/gates/ry.py @@ -75,7 +75,14 @@ class CRY(ControlledGate): theta (float | Parameter): The rotation angle in radians around the Y-axis. label (str | None, optional): An optional label for the CRY gate. Defaults to None. """ - super().__init__('CRY', 2, [theta], label=label) + super().__init__( + 'CRY', + 2, + control_index=[0], + base_gate=RY(theta), + params=[theta], + label=label + ) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/gates/rz.py b/cqlib/circuits/gates/rz.py index 5769a5f..bc05321 100644 --- a/cqlib/circuits/gates/rz.py +++ b/cqlib/circuits/gates/rz.py @@ -75,7 +75,14 @@ class CRZ(ControlledGate): theta (float | Parameter): The rotation angle in radians around the Z-axis. label (str | None, optional): An optional label for the CRZ gate. Defaults to None. """ - super().__init__('CRZ', 2, [theta], label=label) + super().__init__( + 'CRZ', + 2, + control_index=[0], + base_gate=RZ(theta), + params=[theta], + label=label + ) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/gates/x.py b/cqlib/circuits/gates/x.py index fed8c13..e14df5c 100644 --- a/cqlib/circuits/gates/x.py +++ b/cqlib/circuits/gates/x.py @@ -89,7 +89,14 @@ class CX(ControlledGate): Args: label (str | None, optional): An optional label for the CX gate. Defaults to None. """ - super().__init__('CX', 2, [], label=label) + super().__init__( + 'CX', + 2, + control_index=[0], + base_gate=X(), + params=[], + label=label + ) def __array__(self, dtype=np.complex128): """ @@ -165,7 +172,8 @@ class CCX(ControlledGate): label (str | None, optional): An optional label for the CCX gate. Defaults to None. """ - super().__init__('CCX', 3, [], label=label) + super().__init__('CCX', 3, control_index=[0, 1], + base_gate=X(), params=[], label=label) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/gates/y.py b/cqlib/circuits/gates/y.py index e07778f..ccb1cf1 100644 --- a/cqlib/circuits/gates/y.py +++ b/cqlib/circuits/gates/y.py @@ -79,7 +79,8 @@ class CY(ControlledGate): Args: label (str | None, optional): An optional label for the CY gate. Defaults to None. """ - super().__init__('CY', 2, [], label=label) + super().__init__('CY', 2, control_index=[0], + base_gate=Y(), params=[], label=label) def __array__(self, dtype: np.dtype = np.complex128): """ diff --git a/cqlib/circuits/gates/z.py b/cqlib/circuits/gates/z.py index ee71a3c..a501871 100644 --- a/cqlib/circuits/gates/z.py +++ b/cqlib/circuits/gates/z.py @@ -77,7 +77,8 @@ class CZ(ControlledGate): Args: label (str | None, optional): An optional label for the CZ gate. Defaults to None. """ - super().__init__('CZ', 2, [], label=label) + super().__init__('CZ', 2, control_index=[0], + base_gate=Z(), params=[], label=label) def __array__(self, dtype=np.complex128): """ -- Gitee From f3a2f226ee8e9459817d694c205c4c6cbd8d4372 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Wed, 19 Feb 2025 14:08:02 +0800 Subject: [PATCH 08/30] test(circuit): Improve commutation test robustness, Replace networkx with rustworkx. --- cqlib/circuits/commutation/circuit_commutate.py | 14 +++++++++----- tests/circuit/test_commutation.py | 6 ++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cqlib/circuits/commutation/circuit_commutate.py b/cqlib/circuits/commutation/circuit_commutate.py index 186cfc3..5da33e8 100644 --- a/cqlib/circuits/commutation/circuit_commutate.py +++ b/cqlib/circuits/commutation/circuit_commutate.py @@ -13,7 +13,7 @@ """ Quantum circuit commutation checker """ -import networkx as nx +from rustworkx.rustworkx import topological_sort from cqlib.circuits.circuit import Circuit from cqlib.circuits.instruction_data import InstructionData @@ -24,8 +24,11 @@ from .commutation import check_commutation from ._gates_commutations import gates_commutations -def circuit_commutator(circuit: Circuit, use_cache: bool = True, cache_commutations: dict = None) \ - -> list[tuple[InstructionData, InstructionData]]: +def circuit_commutator( + circuit: Circuit, + use_cache: bool = True, + cache_commutations: dict = None +) -> list[tuple[InstructionData, InstructionData]]: """ Check whether there is a commutation relationship between consecutive instructions in the circuit. @@ -42,8 +45,9 @@ def circuit_commutator(circuit: Circuit, use_cache: bool = True, cache_commutati cache_commutations = gates_commutations dag = circuit_to_dag(circuit) data = [] - for node in nx.topological_sort(dag): - for next_node in dag[node]: + for node_idx in topological_sort(dag): + node = dag.get_node_data(node_idx) + for next_node in dag.successors(node_idx): if use_cache: res = query_commute(node, next_node, cache_commutations) if res is not None: diff --git a/tests/circuit/test_commutation.py b/tests/circuit/test_commutation.py index da0414c..d87ba55 100644 --- a/tests/circuit/test_commutation.py +++ b/tests/circuit/test_commutation.py @@ -153,8 +153,10 @@ M Q1 M Q2""" c1 = Circuit.load(qcis) data = circuit_commutator(c1) - res = '[(CZ Q0 Q1, CZ Q1 Q2), (H Q2, H Q2), (CZ Q0 Q1, CZ Q1 Q2), (CZ Q0 Q1, CZ Q1 Q2)]' - assert str(data) == res + res = ['(CZ Q0 Q1, CZ Q1 Q2)', '(H Q2, H Q2)', '(CZ Q0 Q1, CZ Q1 Q2)', '(CZ Q0 Q1, CZ Q1 Q2)'] + assert len(data) == len(res) + for item in data: + assert str(item) in res # pylint: disable=redefined-outer-name -- Gitee From a12a48d99e59edf885891304262a1474a80720b3 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Wed, 19 Feb 2025 16:56:33 +0800 Subject: [PATCH 09/30] feat(visualization/circuit): Add base and text drawer for circuit visualization --- cqlib/visualization/__init__.py | 3 +- cqlib/visualization/circuit/__init__.py | 23 +++ cqlib/visualization/circuit/base.py | 139 ++++++++++++++ cqlib/visualization/circuit/text.py | 234 ++++++++++++++++++++++++ 4 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 cqlib/visualization/circuit/__init__.py create mode 100644 cqlib/visualization/circuit/base.py create mode 100644 cqlib/visualization/circuit/text.py diff --git a/cqlib/visualization/__init__.py b/cqlib/visualization/__init__.py index 2bc382b..361407e 100644 --- a/cqlib/visualization/__init__.py +++ b/cqlib/visualization/__init__.py @@ -15,6 +15,7 @@ visualization module defines methods to visualize qcis circuits and plotting experiment results. """ -from .circuit import draw_circuit +# from .circuit import draw_circuit +from .circuit import draw_text from .gplot import draw_gplot from .result import draw_histogram diff --git a/cqlib/visualization/circuit/__init__.py b/cqlib/visualization/circuit/__init__.py new file mode 100644 index 0000000..fe04304 --- /dev/null +++ b/cqlib/visualization/circuit/__init__.py @@ -0,0 +1,23 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Quantum circuit visualization +""" + +from .text import draw_text, TextDrawer + +__all__ = [ + 'draw_text', + 'TextDrawer' +] diff --git a/cqlib/visualization/circuit/base.py b/cqlib/visualization/circuit/base.py new file mode 100644 index 0000000..e240af0 --- /dev/null +++ b/cqlib/visualization/circuit/base.py @@ -0,0 +1,139 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Quantum circuit visualization +""" + +import logging +from abc import ABC, abstractmethod +from typing import Iterator + +from cqlib import Qubit +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.dag import circuit_to_dag, topological_layers +from cqlib.circuits.instruction_data import InstructionData +from cqlib.exceptions import CqlibError + +logger = logging.getLogger('cqlib.vis') + + +class BaseDrawer(ABC): + """ + Abstract Quantum circuit Drawer. + """ + + def __init__(self, circuit: Circuit, qubit_order: list[int | Qubit] = None): + self.circuit = circuit + self.qubit_order = qubit_order + self.sorted_qubits = None + self.qubit_mapping = None + + self.order_qubits() + + def order_qubits(self): + """ + Determines the display order of qubits and initializes related attributes. + Uses specified qubit_order if provided (supplementing with remaining circuit qubits), + otherwise sorts qubits by their indices. Initializes: + - sorted_qubits: List of qubits in display order + - qubit_mapping: Dictionary mapping qubits to display line numbers + """ + if self.qubit_order is None: + sorted_qubits = sorted(self.circuit.qubits, key=lambda s: s.index) + else: + sorted_qubits = [q if isinstance(q, Qubit) else Qubit(q) for q in self.qubit_order] + for qubit in self.circuit.qubits: + if qubit not in sorted_qubits: + sorted_qubits.append(qubit) + self.sorted_qubits = sorted_qubits + self.qubit_mapping = {q: i * 2 + 1 for i, q in enumerate(sorted_qubits)} + + def generate_moment(self) -> Iterator[list[InstructionData]]: + """ + Generates an iterator of topological layers (moments) from the circuit DAG. + Each moment contains non-overlapping operations that can be executed in parallel. + + Yields: + list[InstructionData]: A moment containing parallel-compatible operations + + Raises: + CqlibError: If qubit conflicts are detected within a topological layer + """ + dag_circuit = circuit_to_dag(self.circuit) + for layer in topological_layers(dag_circuit): + moment = [] + used_qubits = set() + for node_id in layer: + node: InstructionData = dag_circuit.get_node_data(node_id) + if any(q in used_qubits for q in node.qubits): + raise CqlibError(f"Qubit conflict in topological layer: {node.qubits}") + used_qubits.update(node.qubits) + moment.append(node) + yield moment + + @staticmethod + def moment_to_columns(moment: list[InstructionData]) -> list[list[InstructionData]]: + """ + Organizes a moment into display columns by analyzing qubit ranges. + Operations with overlapping qubit ranges are placed in separate columns. + + Args: + moment (list[InstructionData]): A collection of operations from one topological layer + + Returns: + list[list[InstructionData]]: Column-based structure for visualization, + where each sublist represents a display column + """ + + qubit_ranges = [] + qubit_min_to_ins = {} + for ins in moment: + qubits_index = [q.index for q in ins.qubits] + qubit_min_to_ins[min(qubits_index)] = ins + qubit_ranges.append([min(qubits_index), max(qubits_index)]) + + moment_columns = [] + for qubit_range in qubit_ranges: + min_index, max_index = qubit_range + for column in moment_columns: + for item in column: + mi, ma = item + if max_index >= mi and min_index <= ma: + break + else: + column.append(qubit_range) + break + else: + moment_columns.append([qubit_range]) + return [[qubit_min_to_ins[r[0]] for r in col] for col in moment_columns] + + def qubit_line_no(self, qubit: Qubit) -> int: + """ + Retrieves the display line number for a given qubit in the visualization + + Args: + qubit (Qubit): Target qubit to query + + Returns: + int: Odd-numbered line position corresponding to the qubit's display order + """ + return self.qubit_mapping[qubit] + + @abstractmethod + def drawer(self) -> str: + """ start draw lines""" + + @abstractmethod + def draw_column(self, column: list[InstructionData]) -> list[list[str]]: + """ draw column lines""" diff --git a/cqlib/visualization/circuit/text.py b/cqlib/visualization/circuit/text.py new file mode 100644 index 0000000..3e0126a --- /dev/null +++ b/cqlib/visualization/circuit/text.py @@ -0,0 +1,234 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Text-based quantum circuit visualization module + +Provides ASCII/Unicode art representation of quantum circuits using box-drawing characters. +""" + +import logging +from enum import Enum + +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.gates import SWAP, CZ +from cqlib.circuits.barrier import Barrier +from cqlib.circuits.gates.gate import ControlledGate +from cqlib.circuits.instruction_data import InstructionData +from cqlib.circuits.qubit import Qubit + +from .base import BaseDrawer + +logger = logging.getLogger('cqlib.vis') + + +class BoxChar(str, Enum): + """ + Unicode box-drawing characters collection for circuit visualization + """ + TOP = '╵' + BOTTOM = '╷' + LEFT = '╴' + RIGHT = '╶' + TOP_BOTTOM = '│' + LEFT_RIGHT = '─' + + TOP_LEFT = '┘' + TOP_RIGHT = '└' + BOTTOM_LEFT = '┐' + BOTTOM_RIGHT = '┌' + + TOP_BOTTOM_LEFT = '┤' + TOP_BOTTOM_RIGHT = '├' + TOP_LEFT_RIGHT = '┴' + BOTTOM_LEFT_RIGHT = '┬' + TOP_BOTTOM_LEFT_RIGHT = '┼' + + DOT = '■' + CONNECT = 'X' + + +class TextDrawer(BaseDrawer): + """ + Renders quantum circuits as text diagrams using box-drawing characters + """ + + # pylint: disable=too-many-locals + def drawer(self) -> str: + """ + Main entry point for circuit visualization + + Returns: + str: Complete ASCII representation of the circuit + """ + lines = [] + qubit_len = max(len(str(q.index)) for q in self.sorted_qubits) + empty_line = ' ' * (qubit_len + 6) + + # qubits + for qubit in self.sorted_qubits: + lines.extend([ + [empty_line], + [''.join([' ', f'Q{qubit.index}'.rjust(qubit_len + 1), + ': ', BoxChar.LEFT_RIGHT * 2])] + ]) + lines.append([empty_line]) + + # instructions + for moment in self.generate_moment(): + # before moment, add one symbol + for i, line in enumerate(lines): + line.append(BoxChar.LEFT_RIGHT if i % 2 == 1 else ' ') + + # drawer one moment, maybe multi columns + columns = self.moment_to_columns(moment) + for column in columns: + column_lines = self.draw_column(column) + for line_i, line in enumerate(column_lines): + lines[line_i].extend(line) + + # after moment, add one symbol + for i, line in enumerate(lines): + line.append(BoxChar.LEFT_RIGHT if i % 2 == 1 else ' ') + + # mark many column as one moment + col_len = len(columns) + if col_len > 1: + top = [BoxChar.BOTTOM_RIGHT] + bottom = [BoxChar.TOP_RIGHT] + for i in range(col_len): + old = lines[0][-col_len - 1 + i] + s = BoxChar.LEFT_RIGHT * len(old) + top.append(s) + bottom.append(s) + top.append(BoxChar.BOTTOM_LEFT) + bottom.append(BoxChar.TOP_LEFT) + lines[0][-col_len - 2:] = top + lines[-1][-col_len - 2:] = bottom + return '\n'.join([''.join(l) for l in lines]) + + def draw_column(self, column: list[InstructionData]): + """ + Processes a vertical column of parallel operations + + Args: + column: Group of non-overlapping operations from the same moment + + Returns: + list: Formatted lines ready for insertion into main drawing + """ + max_width = 1 + + # container + lines = [[] for _ in range(len(self.sorted_qubits) * 2 + 1)] + # draw every InstructionData + for ins in column: + match len(ins.qubits): + case 1: + lines = self.draw_single_gate(ins, lines) + case _: + lines = self.draw_multi_gate(ins, lines) + # calculate max width + for line in lines: + for s in line: + max_width = max(max_width, len(s)) + # fit max width + empty_line = ' ' * max_width + left_right_line = BoxChar.LEFT_RIGHT * max_width + for i, line in enumerate(lines): + if i % 2 == 0: + if line: + lines[i] = [line[0].center(max_width)] + else: + lines[i].append(empty_line) + else: + if line: + lines[i] = [line[0].center(max_width, BoxChar.LEFT_RIGHT)] + else: + lines[i].append(left_right_line) + + return lines + + def draw_single_gate(self, ins: InstructionData, lines: list[list[str]]): + """ + Handles single-qubit gate visualization + + Args: + ins: Gate instruction to render + lines: Current drawing lines state + + Returns: + Updated lines with gate symbol placed on target qubit line + """ + lines[self.qubit_line_no(ins.qubits[0])].append(str(ins.instruction)) + return lines + + def draw_multi_gate(self, ins: InstructionData, lines: list[list[str]]): + """ + Visualizes multi-qubit operations with vertical connections + + Special cases: + - SWAP: Uses 'X' connection symbols + - CZ: Uses solid dots + - Barriers: Vertical lines spanning involved qubits + - Controlled gates: Differentiates control/target qubits + + Args: + ins: Multi-qubit instruction to render + lines: Current drawing lines state + + Returns: + Updated lines with gate symbols and connection lines + """ + if isinstance(ins.instruction, SWAP): + lines[self.qubit_line_no(ins.qubits[0])].append(BoxChar.CONNECT) + lines[self.qubit_line_no(ins.qubits[1])].append(BoxChar.CONNECT) + elif isinstance(ins.instruction, CZ): + lines[self.qubit_line_no(ins.qubits[0])].append(BoxChar.DOT) + lines[self.qubit_line_no(ins.qubits[1])].append(BoxChar.DOT) + elif isinstance(ins.instruction, Barrier): + for qubit in ins.qubits: + idx = self.qubit_line_no(qubit) + if idx not in (1, len(lines) - 1): + lines[idx - 1].append(BoxChar.TOP_BOTTOM) + lines[idx].append(BoxChar.TOP_BOTTOM) + # Barrier, No vertical connections required + return lines + elif isinstance(ins.instruction, ControlledGate): + for index, qubit in enumerate(ins.qubits): + s = BoxChar.DOT if index in ins.instruction.control_index \ + else str(ins.instruction.base_gate) + lines[self.qubit_line_no(qubit)].append(s) + + # add connect vertical line + qubit_index = [self.qubit_line_no(q) for q in ins.qubits] + min_index, max_index = min(qubit_index), max(qubit_index) + for idx in range(min_index + 1, max_index): + lines[idx].append(BoxChar.TOP_BOTTOM_LEFT_RIGHT if idx % 2 == 1 + else BoxChar.TOP_BOTTOM) + + return lines + + +def draw_text(circuit: Circuit, qubit_order: list[int | Qubit] = None): + """ + Quick-access function for text circuit visualization + + Args: + circuit: Quantum circuit to visualize + qubit_order: Optional custom qubit arrangement + + Returns: + str: Ready-to-print circuit diagram + """ + return TextDrawer(circuit, qubit_order).drawer() -- Gitee From 66f5ab31d43a60dfc0ba9705daffd79baffdb338 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Thu, 20 Feb 2025 19:28:39 +0800 Subject: [PATCH 10/30] feat(cqlib/visualization/circuit/text.py): Add line width handling and improve text drawer --- cqlib/visualization/circuit/text.py | 146 ++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 28 deletions(-) diff --git a/cqlib/visualization/circuit/text.py b/cqlib/visualization/circuit/text.py index 3e0126a..85bfb7c 100644 --- a/cqlib/visualization/circuit/text.py +++ b/cqlib/visualization/circuit/text.py @@ -16,8 +16,9 @@ Text-based quantum circuit visualization module Provides ASCII/Unicode art representation of quantum circuits using box-drawing characters. """ - +import copy import logging +import shutil from enum import Enum from cqlib.circuits.circuit import Circuit @@ -56,66 +57,130 @@ class BoxChar(str, Enum): DOT = '■' CONNECT = 'X' + LEFT_ARROW = '«' + RIGHT_ARROW = '»' class TextDrawer(BaseDrawer): """ Renders quantum circuits as text diagrams using box-drawing characters """ + MIN_LINE_WIDTH = 10 + DEFAULT_LINE_WIDTH = 30 + + def __init__( + self, + circuit: Circuit, + qubit_order: list[int | Qubit] = None, + line_width: int = None, + ): + """ + Initialization Parameters + + Args: + circuit (Circuit): Quantum circuit to visualize + qubit_order (list[int | Qubit]): Custom ordering of qubits (optional) + line_width (int): Force specific output width (default: auto-detect terminal width) + """ + super().__init__(circuit, qubit_order) + self.line_width = line_width - # pylint: disable=too-many-locals def drawer(self) -> str: """ - Main entry point for circuit visualization + Generates the complete text-based circuit diagram. + + Returns: + str: Full circuit representation characters. + """ + max_line_width = self.get_line_width() + lines = self.make_lines() + data = [] + lines_count = len(self.sorted_qubits) * 2 + 1 + start_qubits = copy.deepcopy(lines[0]) + current_data = [lines[0][i] for i in range(lines_count)] + current_width = 0 + for moment in lines[1:]: + + moment_len = len(''.join(moment[0])) + if moment_len + current_width > max_line_width: + data.append(current_data) + for d in current_data: + d.append(BoxChar.RIGHT_ARROW) + current_data = [] + for i in range(lines_count): + s = [BoxChar.LEFT_ARROW] + s.extend(start_qubits[i]) + current_data.append(s) + current_width = 0 + for i, line in enumerate(moment): + current_data[i].extend(line) + current_width += moment_len + data.append(current_data) + t = [] + for lines in data: + for line in lines: + t.extend(line) + t.append('\n') + t.append('\n') + return ''.join(t) + + # pylint: disable=too-many-locals + def make_lines(self) -> list[list[list[str]]]: + """ + Constructs the circuit visualization line structure. + + Builds a 3-level nested list representing the circuit layout: + - Outer list: Moments (vertical slices of parallel operations) + - Middle list: Text lines per moment (including qubit labels and connections) + - Inner list: Character sequences for each line segment Returns: - str: Complete ASCII representation of the circuit + List of moments, each containing lists of text lines with box characters. + Structure details: + - Even indices: Spacer lines between qubits + - Odd indices: Qubit lines with labels and gate symbols + - First element: Initial qubit label headers + - Subsequent elements: Circuit moments with gate representations """ lines = [] qubit_len = max(len(str(q.index)) for q in self.sorted_qubits) empty_line = ' ' * (qubit_len + 6) - + lines_count = len(self.sorted_qubits) * 2 + 1 # qubits + start_lines = [] for qubit in self.sorted_qubits: - lines.extend([ + start_lines.extend([ [empty_line], [''.join([' ', f'Q{qubit.index}'.rjust(qubit_len + 1), ': ', BoxChar.LEFT_RIGHT * 2])] ]) - lines.append([empty_line]) + start_lines.append([empty_line]) + lines.append(start_lines) # instructions for moment in self.generate_moment(): # before moment, add one symbol - for i, line in enumerate(lines): - line.append(BoxChar.LEFT_RIGHT if i % 2 == 1 else ' ') + moment_lines = [[BoxChar.LEFT_RIGHT if i % 2 == 1 else ' '] for i in range(lines_count)] # drawer one moment, maybe multi columns columns = self.moment_to_columns(moment) for column in columns: column_lines = self.draw_column(column) for line_i, line in enumerate(column_lines): - lines[line_i].extend(line) + moment_lines[line_i].extend(line) # after moment, add one symbol - for i, line in enumerate(lines): + for i, line in enumerate(moment_lines): line.append(BoxChar.LEFT_RIGHT if i % 2 == 1 else ' ') # mark many column as one moment col_len = len(columns) if col_len > 1: - top = [BoxChar.BOTTOM_RIGHT] - bottom = [BoxChar.TOP_RIGHT] - for i in range(col_len): - old = lines[0][-col_len - 1 + i] - s = BoxChar.LEFT_RIGHT * len(old) - top.append(s) - bottom.append(s) - top.append(BoxChar.BOTTOM_LEFT) - bottom.append(BoxChar.TOP_LEFT) - lines[0][-col_len - 2:] = top - lines[-1][-col_len - 2:] = bottom - return '\n'.join([''.join(l) for l in lines]) + s = BoxChar.LEFT_RIGHT * (len(''.join(moment_lines[0])) - 2) + moment_lines[0] = [BoxChar.BOTTOM_RIGHT, s, BoxChar.BOTTOM_LEFT] + moment_lines[-1] = [BoxChar.TOP_RIGHT, s, BoxChar.TOP_LEFT] + lines.append(moment_lines) + return lines def draw_column(self, column: list[InstructionData]): """ @@ -219,16 +284,41 @@ class TextDrawer(BaseDrawer): return lines + def get_line_width(self): + """ + Determines the effective display width for circuit rendering. + + Priority order for width determination: + 1. User-specified line_width (if > MIN_LINE_WIDTH) + 2. Current terminal width + 3. DEFAULT_LINE_WIDTH fallback -def draw_text(circuit: Circuit, qubit_order: list[int | Qubit] = None): + Returns: + int: display width in characters. + """ + if self.line_width and self.line_width > self.MIN_LINE_WIDTH: + return self.line_width + # try to get terminal size + width, _ = shutil.get_terminal_size() + if not width: + width = self.DEFAULT_LINE_WIDTH + return width + + +def draw_text( + circuit: Circuit, + qubit_order: list[int | Qubit] = None, + line_width: int = None, +): """ - Quick-access function for text circuit visualization + Quick-access function for text circuit visualization. Args: - circuit: Quantum circuit to visualize - qubit_order: Optional custom qubit arrangement + circuit (Circuit): Quantum circuit to visualize + qubit_order (list[int | Qubit]): Optional custom qubit arrangement + line_width (int): Returns: str: Ready-to-print circuit diagram """ - return TextDrawer(circuit, qubit_order).drawer() + return TextDrawer(circuit, qubit_order, line_width).drawer() -- Gitee From 9ec663d353f0f4e2c74413c57e13da9fd53dcdb4 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Fri, 21 Feb 2025 13:48:17 +0800 Subject: [PATCH 11/30] feat(cqlib/circuits): Introduce Bit class and inherit Qubit from it --- cqlib/circuits/bit.py | 70 +++++++++++++++++++++++++++++++++++++++++ cqlib/circuits/qubit.py | 55 +++++--------------------------- 2 files changed, 77 insertions(+), 48 deletions(-) create mode 100644 cqlib/circuits/bit.py diff --git a/cqlib/circuits/bit.py b/cqlib/circuits/bit.py new file mode 100644 index 0000000..30a8c85 --- /dev/null +++ b/cqlib/circuits/bit.py @@ -0,0 +1,70 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" quantum bit base class""" + +from __future__ import annotations + + +class Bit: + """Quantum bit.""" + __slots__ = ["_index", '_initialized', '_hash'] + + def __init__(self, index: int): + """ + Initialize a new Qubit instance. + + Args: + index: logical index of the qubit + """ + if index < 0: + raise ValueError("Index must be non-negative.") + if not hasattr(self, '_initialized'): + self._index = index + self._hash = None + self._initialized = True + + @property + def index(self) -> int: + """Returns the logical index of the qubit.""" + return self._index + + def __repr__(self): + return f"{self.__class__.__name__}({self.index})" + + def __str__(self): + return f'Bit{self.index}' + + def __copy__(self): + """ + Returns a reference to the same qubit instance since qubits + should be unique. + """ + return self + + def __deepcopy__(self, memo=None): + return self + + def __eq__(self, other: Bit) -> bool: + """Check equality with another qubit based on the index.""" + return (isinstance(other, self.__class__) and + self.index == other.index) + + def __hash__(self) -> int: + """ + Return the hash based on the qubit's index, used for collections + that depend on hashable items. + """ + if self._hash is None: + self._hash = hash(f"{self.__class__.__name__}({self._index})") + return self._hash diff --git a/cqlib/circuits/qubit.py b/cqlib/circuits/qubit.py index 7e4fff0..879efa2 100644 --- a/cqlib/circuits/qubit.py +++ b/cqlib/circuits/qubit.py @@ -17,11 +17,13 @@ from __future__ import annotations import weakref +from .bit import Bit -class Qubit: + +class Qubit(Bit): """Quantum bit.""" - __slots__ = ["_index", '_initialized', '_hash', '__weakref__'] - _cache = weakref.WeakValueDictionary[int, 'QuBit']() + __slots__ = ["__weakref__"] + _cache = weakref.WeakValueDictionary[int, 'Qubit']() def __new__(cls, index: int) -> Qubit: """ @@ -36,58 +38,15 @@ class Qubit: """ if index < 0: raise ValueError("Qubit index must be non-negative.") + inst = cls._cache.get(index) if inst is None: inst = super().__new__(cls) inst._index = index inst._hash = None + inst._initialized = True cls._cache[index] = inst return inst - def __init__(self, index: int): - """ - Initialize a new Qubit instance. - - Args: - index: logical index of the qubit - """ - if not hasattr(self, '_initialized'): - self._index = index - self._hash = None - self._initialized = True - - @property - def index(self) -> int: - """Returns the logical index of the qubit.""" - return self._index - - def __repr__(self): - return f"{self.__class__.__name__}({self.index})" - def __str__(self): return f'Q{self.index}' - - def __copy__(self): - """ - Returns a reference to the same qubit instance since qubits - should be unique. - """ - return self - - def __deepcopy__(self, memodict=None): - return self - - def __eq__(self, other: Qubit) -> bool: - """Check equality with another qubit based on the index.""" - if isinstance(other, Qubit): - return self.index == other.index - return False - - def __hash__(self) -> int: - """ - Return the hash based on the qubit's index, used for collections - that depend on hashable items. - """ - if self._hash is None: - self._hash = hash(f"{self.__class__.__name__}({self._index})") - return self._hash -- Gitee From ea77d96ba799813b3b14a1c06d0687fa3ef6685c Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Fri, 21 Feb 2025 15:37:25 +0800 Subject: [PATCH 12/30] feat(pulse/waveform.py): Add waveform type enumerations and parameter classes --- cqlib/pulse/waveform.py | 200 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 cqlib/pulse/waveform.py diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py new file mode 100644 index 0000000..a652449 --- /dev/null +++ b/cqlib/pulse/waveform.py @@ -0,0 +1,200 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Waveform + +This module defines waveform type enumerations and parameter classes +for generating quantum pulse sequences. It provides a standardized +interface to configure and validate waveform parameters across different +waveform types. +""" +from dataclasses import dataclass +from enum import IntEnum, unique + + +@unique +class WaveformType(IntEnum): + """ + Enumeration of supported waveform types for quantum pulse sequences. + + Members: + COSINE (0): Cosine-shaped waveform. + FLATTOP (1): Flat-top waveform with customizable edge length. + SLEPIAN (2): Slepian tapering waveform defined by time-bandwidth + product parameters. + NUMERIC (3): Arbitrary numeric waveform specified via sample array. + """ + COSINE = 0 + FLATTOP = 1 + SLEPIAN = 2 + NUMERIC = 3 + + +@dataclass +class WaveformParams: + """ + Base dataclass for waveform parameter validation and string representation. + + Attributes: + waveform (WaveformType): Enumerated waveform type. + length (int): Total waveform duration (1-1e5). + amplitude (float): Normalized signal amplitude (0.0-1.0). + phase (float, optional): Phase shift for PXY modulations. + drag_alpha (float, optional): Drag coefficient for PXY modulations. + """ + waveform: WaveformType + length: int + amplitude: float + phase: float = None # PXY only + drag_alpha: float = None # PXY only + + def validate(self): + """Performs basic parameter validation.""" + if not isinstance(self.length, int) or self.length <= 0 or self.length > 1e5: + raise ValueError(f"Invalid length: {self.length}. Must be in [0, 1e5]") + + if not 0.0 <= self.amplitude <= 1.0: + raise ValueError(f"Invalid amplitude: {self.amplitude}. Must be in [0, 1]") + + def __str__(self): + ps = [self.waveform.value, self.length, self.amplitude] + if self.phase is not None and self.drag_alpha is not None: + ps.extend([self.phase, self.drag_alpha]) + return ' '.join(map(str, ps)) + + +@dataclass +class CosineParams(WaveformParams): + """Cosine波形参数""" + + def __post_init__(self): + self.validate() + + def __str__(self): + return f'{super().__str__()}' + + +@dataclass +class FlattopParams(WaveformParams): + """ + Specialized parameters for flat-top waveforms with edge control. + + Additional Attributes: + edge (int): Length of rising/falling edges (must be < total length/2). + """ + edge: int = None + + def __post_init__(self): + self.validate() + if 2 * self.edge >= self.length: + raise ValueError(f"Edge ({self.edge}) too large for length {self.length}") + + def __str__(self): + return f'{super().__str__()} {self.edge}' + + +@dataclass +class SlepianParams(WaveformParams): + """ + Specialized parameters for slepian waveforms + + Additional Attributes: + - thf: + - thi: + - lam2: + - lam3: + """ + thf: float = None + thi: float = None + lam2: float = None + lam3: float = None + + def __str__(self): + return f'{super().__str__()} {self.thf} {self.thi} {self.lam2} {self.lam3}' + + +@dataclass +class NumericParams(WaveformParams): + """ + Specialized parameters for numeric waveforms. + + Additional Attributes: + - data: List of floats, Data values must be in [0.0, 1.0], + Minimum length of 3 samples; + """ + data: list[float] = None + + def __post_init__(self): + super().validate() + if any(not (0.0 <= x <= 1.0) for x in self.data): + raise ValueError("All data values must be in [0,1]") + if len(self.data) < 3: + raise ValueError("") + + def __str__(self): + return f'{super().__str__()} {" ".join(map(str, self.data))}' + + +# pylint: disable=too-few-public-methods +class Waveform: + """ + Factory class for creating waveform parameter objects based on type. + + Usage: + Instantiate through static method __new__ with type-specific parameters. + + Example: + >>> from cqlib.pulse.waveform import Waveform + >>> wave = Waveform( + >>> w_type=WaveformType.COSINE, + >>> length=1000, + >>> amplitude=0.8 + >>> ) + >>> print(f'Cosine 波形: {wave}') + """ + + # pylint: disable=too-many-arguments, too-many-positional-arguments + def __new__(cls, + w_type: WaveformType, + length: int, + amplitude: float, + phase: float = None, + drag_alpha: float = None, + **kwargs) -> CosineParams | FlattopParams | SlepianParams | NumericParams: + """ + 创建波形参数对象 + + :param w_type: 波形类型枚举值 + :param length: 波形总时长 + :param amplitude: 幅度 + :param kwargs: 类型特定参数 + """ + param_classes = { + WaveformType.COSINE: CosineParams, + WaveformType.FLATTOP: FlattopParams, + WaveformType.SLEPIAN: SlepianParams, + WaveformType.NUMERIC: NumericParams + } + + if w_type not in param_classes: + raise ValueError(f"Unsupported waveform type: {w_type}") + + return param_classes[w_type]( + waveform=w_type, + length=length, + amplitude=amplitude, + phase=phase, + drag_alpha=drag_alpha, + **kwargs + ) -- Gitee From 2b430757789a450d1760500770c1ea1e0ad0b6b3 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 24 Feb 2025 10:17:19 +0800 Subject: [PATCH 13/30] fix(waveform.py): Update waveform parameter handling --- cqlib/pulse/waveform.py | 44 ++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py index a652449..8e36542 100644 --- a/cqlib/pulse/waveform.py +++ b/cqlib/pulse/waveform.py @@ -1,6 +1,6 @@ # This code is part of cqlib. # -# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., # Center for Excellence in Quantum Information and Quantum Physics. # # This code is licensed under the Apache License, Version 2.0. You may @@ -19,6 +19,7 @@ for generating quantum pulse sequences. It provides a standardized interface to configure and validate waveform parameters across different waveform types. """ + from dataclasses import dataclass from enum import IntEnum, unique @@ -68,22 +69,24 @@ class WaveformParams: raise ValueError(f"Invalid amplitude: {self.amplitude}. Must be in [0, 1]") def __str__(self): + return ' '.join(map(str, self.data)) + + @property + def data(self) -> list[float]: + """Data list""" ps = [self.waveform.value, self.length, self.amplitude] if self.phase is not None and self.drag_alpha is not None: ps.extend([self.phase, self.drag_alpha]) - return ' '.join(map(str, ps)) + return ps @dataclass class CosineParams(WaveformParams): - """Cosine波形参数""" + """Configuration parameters for cosine-shaped waveforms.""" def __post_init__(self): self.validate() - def __str__(self): - return f'{super().__str__()}' - @dataclass class FlattopParams(WaveformParams): @@ -100,8 +103,11 @@ class FlattopParams(WaveformParams): if 2 * self.edge >= self.length: raise ValueError(f"Edge ({self.edge}) too large for length {self.length}") - def __str__(self): - return f'{super().__str__()} {self.edge}' + @property + def data(self) -> list[float]: + ps = super().data + ps.append(self.edge) + return ps @dataclass @@ -120,8 +126,11 @@ class SlepianParams(WaveformParams): lam2: float = None lam3: float = None - def __str__(self): - return f'{super().__str__()} {self.thf} {self.thi} {self.lam2} {self.lam3}' + @property + def data(self) -> list[float]: + ps = super().data + ps.extend([self.thf, self.thi, self.lam2, self.lam3]) + return ps @dataclass @@ -130,20 +139,23 @@ class NumericParams(WaveformParams): Specialized parameters for numeric waveforms. Additional Attributes: - - data: List of floats, Data values must be in [0.0, 1.0], + - params: List of floats, Values must be in [0.0, 1.0], Minimum length of 3 samples; """ - data: list[float] = None + params: list[float] = None def __post_init__(self): super().validate() - if any(not (0.0 <= x <= 1.0) for x in self.data): + if any(not (0.0 <= x <= 1.0) for x in self.params): raise ValueError("All data values must be in [0,1]") - if len(self.data) < 3: + if len(self.params) < 3: raise ValueError("") - def __str__(self): - return f'{super().__str__()} {" ".join(map(str, self.data))}' + @property + def data(self) -> list[float]: + ps = super().data + ps.extend(self.params) + return ps # pylint: disable=too-few-public-methods -- Gitee From c752816c277b74c6737cef604eb2e59614df2049 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 24 Feb 2025 15:01:11 +0800 Subject: [PATCH 14/30] fix(pulse/waveform.py): Improve parameter validation and error handling --- cqlib/pulse/waveform.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py index 8e36542..ef944e9 100644 --- a/cqlib/pulse/waveform.py +++ b/cqlib/pulse/waveform.py @@ -22,6 +22,9 @@ waveform types. from dataclasses import dataclass from enum import IntEnum, unique +from math import pi + +from cqlib.exceptions import CqlibError @unique @@ -63,10 +66,13 @@ class WaveformParams: def validate(self): """Performs basic parameter validation.""" if not isinstance(self.length, int) or self.length <= 0 or self.length > 1e5: - raise ValueError(f"Invalid length: {self.length}. Must be in [0, 1e5]") - + raise CqlibError(f"Invalid length: {self.length}. Must be in [0, 1e5]") if not 0.0 <= self.amplitude <= 1.0: - raise ValueError(f"Invalid amplitude: {self.amplitude}. Must be in [0, 1]") + raise CqlibError(f"Invalid amplitude: {self.amplitude}. Must be in [0, 1]") + if self.phase is not None and (self.phase <= -pi or self.phase >= pi): + raise CqlibError("The phase value must be within (-pi, pi].") + if self.drag_alpha is not None and self.drag_alpha < 0: + raise CqlibError("The drag_alpha value must be positive float.") def __str__(self): return ' '.join(map(str, self.data)) @@ -149,7 +155,8 @@ class NumericParams(WaveformParams): if any(not (0.0 <= x <= 1.0) for x in self.params): raise ValueError("All data values must be in [0,1]") if len(self.params) < 3: - raise ValueError("") + raise ValueError("The numerical list of data pulses is in the form" + " of [d0, d1, ..., dn], and the list length must be >= 3") @property def data(self) -> list[float]: -- Gitee From 0c4a6a2af7374505fc28797b8cef0f1e4e6de34a Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 24 Feb 2025 15:01:45 +0800 Subject: [PATCH 15/30] feat(pulse/coupler_qubit.py): Add CouplerQubit class for quantum system --- cqlib/pulse/coupler_qubit.py | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 cqlib/pulse/coupler_qubit.py diff --git a/cqlib/pulse/coupler_qubit.py b/cqlib/pulse/coupler_qubit.py new file mode 100644 index 0000000..42be3d9 --- /dev/null +++ b/cqlib/pulse/coupler_qubit.py @@ -0,0 +1,41 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Coupler Qubit""" + +from __future__ import annotations + +import weakref + +from cqlib.circuits.bit import Bit + + +class CouplerQubit(Bit): + """A class representing a coupled qubit in a quantum system.""" + + _cache = weakref.WeakValueDictionary[int, 'CouplerQubit']() + + def __new__(cls, index: int) -> CouplerQubit: + if index < 0: + raise ValueError("CouplerQubit index must be non-negative.") + inst = cls._cache.get(index) + if inst is None: + inst = super().__new__(cls, index) + inst._index = index + inst._initialized = True + inst._hash = None + cls._cache[index] = inst + return inst + + def __str__(self): + return f'G{self.index}' -- Gitee From a1597316dba2d2997d1840b8c7f7ce5e6f670544 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 24 Feb 2025 15:02:42 +0800 Subject: [PATCH 16/30] feat(pulse): Add base pulse and specialized pulse classes. `G`, `PXY`, `PZ`, and `PZ0` pulses. --- cqlib/pulse/base_pulse.py | 55 +++++++++++++++++++++++++++++++++++++++ cqlib/pulse/g.py | 48 ++++++++++++++++++++++++++++++++++ cqlib/pulse/pxy.py | 47 +++++++++++++++++++++++++++++++++ cqlib/pulse/pz.py | 32 +++++++++++++++++++++++ cqlib/pulse/pz0.py | 43 ++++++++++++++++++++++++++++++ 5 files changed, 225 insertions(+) create mode 100644 cqlib/pulse/base_pulse.py create mode 100644 cqlib/pulse/g.py create mode 100644 cqlib/pulse/pxy.py create mode 100644 cqlib/pulse/pz.py create mode 100644 cqlib/pulse/pz0.py diff --git a/cqlib/pulse/base_pulse.py b/cqlib/pulse/base_pulse.py new file mode 100644 index 0000000..99b1a85 --- /dev/null +++ b/cqlib/pulse/base_pulse.py @@ -0,0 +1,55 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Base Class for QCIS Pulse instruction +""" + +from abc import ABC, abstractmethod + +from cqlib.circuits.instruction import Instruction + +from .waveform import WaveformParams + + +class BasePulse(Instruction, ABC): + """ + Abstract base class for Quantum Control Instruction System (QCIS) pulse commands. + """ + + def __init__( + self, + name: str, + params: WaveformParams = None, + label: str = None + ): + """Initialize base pulse instance + + Args: + name (str): Pulse type identifier (e.g. PZ/PZ0/PXY/G) + params (WaveformParams): Waveform parameter container storing physical values. + label (str): Optional operational label for experimental tracking + """ + self._params = params + if params: + params = params.data + else: + params = [] + super().__init__(name=name, num_qubits=1, params=params, label=label) + self.validate() + + @abstractmethod + def validate(self): + """ + Abstract template method for parameter validation + """ diff --git a/cqlib/pulse/g.py b/cqlib/pulse/g.py new file mode 100644 index 0000000..196261d --- /dev/null +++ b/cqlib/pulse/g.py @@ -0,0 +1,48 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" G: Adjust and control the coupling function of the coupling qubit. """ +from cqlib.exceptions import CqlibError +from .base_pulse import BasePulse + + +class G(BasePulse): + """ + Coupling strength controller for coupling qubits via sequential DC(Direct Current) pulses. + Applies tunable interaction between adjacent qubits by setting duration (DAC cycles) + and coupling amplitude (Hz). Exclusively operates on coupling qubits. + + Parameters: + - length: Coupling activation duration in DAC sampling cycles (1 cycle=0.5ns) + - coupling_strength: Interaction magnitude in Hertz (Hz) + + Example: + `G G107 100 -3E6` sets 3MHz coupling on G107 for 50ns (100 cycles). + """ + + def __init__(self, length: int, coupling_strength: int, label: str = None): + self.length = length + self.coupling_strength = coupling_strength + super().__init__('G', params=None, label=label) + + def validate(self): + """ + Validate pulse parameters for G-type coupling operations. + + Ensures: + - Duration (length) is within valid range [0, 1e5] DAC cycles + - Coupling strength meets implementation constraints + """ + if self.length < 0 or self.length > 1e5: + raise CqlibError(f"Invalid duration {self.length}: " + f"Must be 0-100,000 DAC cycles (1 cycle=0.5ns)") diff --git a/cqlib/pulse/pxy.py b/cqlib/pulse/pxy.py new file mode 100644 index 0000000..edf9e9e --- /dev/null +++ b/cqlib/pulse/pxy.py @@ -0,0 +1,47 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Sequential AC(Alternating Current) pulse controller for data qubit XY channels. +""" +from cqlib.exceptions import CqlibError +from .base_pulse import BasePulse +from .waveform import WaveformParams, NumericParams + + +class PXY(BasePulse): + """ + Pulse controller for data qubit XY channels. + + For PXY pulses, the numerical list of data pulses is in the form of + [i0, i1, ..., in, q0, q1, ..., qn]. The first half describes the + pulse values of I channel, and the second half describes the + pulse values of Q channel. The length of its list is >=6 and + must be even. + """ + + def __init__(self, params: WaveformParams, label: str = None): + """ + Initialize XY channel pulse with waveform parameters + """ + super().__init__('PXY', params, label=label) + + def validate(self): + """ + Verify PXY-specific waveform constraints. + """ + p = self._params + if p.phase is None or p.drag_alpha is None: + raise CqlibError('PXY must have phase and drag_alpha params') + if isinstance(p, NumericParams) and (len(p.params) < 6 or len(p.params) % 2 == 1): + raise CqlibError('The length of its list must >=6 and must be even.') diff --git a/cqlib/pulse/pz.py b/cqlib/pulse/pz.py new file mode 100644 index 0000000..13cc061 --- /dev/null +++ b/cqlib/pulse/pz.py @@ -0,0 +1,32 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Sequential DC pulse controller for data/coupling qubit Z channels. """ +from cqlib.exceptions import CqlibError +from .base_pulse import BasePulse +from .waveform import WaveformParams + + +class PZ(BasePulse): + """ + Sequential DC pulse controller for data/coupling qubit Z channels. + """ + + def __init__(self, params: WaveformParams, label: str = None): + """Initialize Z-axis DC pulse with Stark shift parameters""" + super().__init__('PZ', params, label=label) + + def validate(self): + p = self._params + if p.phase is not None or p.drag_alpha is not None: + raise CqlibError('PZ must not have phase and drag_alpha params') diff --git a/cqlib/pulse/pz0.py b/cqlib/pulse/pz0.py new file mode 100644 index 0000000..6c90813 --- /dev/null +++ b/cqlib/pulse/pz0.py @@ -0,0 +1,43 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +"""Parallel DC pulse controller for data/coupling qubit Z channels.""" +from cqlib.exceptions import CqlibError +from .base_pulse import BasePulse +from .waveform import WaveformParams + + +class PZ0(BasePulse): + """ + Parallel DC pulse controller for data/coupling qubit Z channels. + + Enables synchronized multi-channel DC pulse generation for simultaneous + frequency adjustments. + """ + + def __init__(self, params: WaveformParams, label: str = None): + """ + Initialize parallel Z-axis DC pulse controller. + + Args: + params (WaveformParams): Waveform params. + label (str, optional): Operational identifier for batch processing + """ + super().__init__('PZ0', params, label=label) + + def validate(self): + """ Verify parallel DC pulse constraints. """ + p = self._params + if p.phase is not None or p.drag_alpha is not None: + raise CqlibError('PZ0 must not have phase and drag_alpha params') -- Gitee From ad0e316a092bb6248ebe1b07eec034bec0b8e33c Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 24 Feb 2025 15:07:22 +0800 Subject: [PATCH 17/30] feat(cqlib/pulse): Add initial pulse module implementation --- cqlib/pulse/__init__.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 cqlib/pulse/__init__.py diff --git a/cqlib/pulse/__init__.py b/cqlib/pulse/__init__.py new file mode 100644 index 0000000..82a6624 --- /dev/null +++ b/cqlib/pulse/__init__.py @@ -0,0 +1,31 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Pulse module based on the pulse instruction in the QCIS instruction. +""" + +from .coupler_qubit import CouplerQubit +from .waveform import Waveform, WaveformType, CosineParams, \ + FlattopParams, SlepianParams, NumericParams +from .g import G +from .pz import PZ +from .pz0 import PZ0 +from .pxy import PXY + +__all__ = [ + 'CouplerQubit', + 'Waveform', 'WaveformType', + 'CosineParams', 'FlattopParams', 'SlepianParams', 'NumericParams', + 'G', 'PXY', 'PZ', 'PZ0' +] -- Gitee From 7a1cb6c688984c525e4e7b08cc550af836278c91 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 24 Feb 2025 17:22:41 +0800 Subject: [PATCH 18/30] refactor(cqlib/pulse): Rename WaveformParams to Waveform and update imports --- cqlib/pulse/__init__.py | 8 +-- cqlib/pulse/base_pulse.py | 4 +- cqlib/pulse/coupler_qubit.py | 2 +- cqlib/pulse/pxy.py | 6 +- cqlib/pulse/pz.py | 4 +- cqlib/pulse/pz0.py | 6 +- cqlib/pulse/waveform.py | 111 ++++++++++++++++------------------- 7 files changed, 66 insertions(+), 75 deletions(-) diff --git a/cqlib/pulse/__init__.py b/cqlib/pulse/__init__.py index 82a6624..817221a 100644 --- a/cqlib/pulse/__init__.py +++ b/cqlib/pulse/__init__.py @@ -16,8 +16,8 @@ Pulse module based on the pulse instruction in the QCIS instruction. """ from .coupler_qubit import CouplerQubit -from .waveform import Waveform, WaveformType, CosineParams, \ - FlattopParams, SlepianParams, NumericParams +from .waveform import Waveform, WaveformType, CosineWaveform, \ + FlattopWaveform, SlepianWaveform, NumericWaveform from .g import G from .pz import PZ from .pz0 import PZ0 @@ -26,6 +26,6 @@ from .pxy import PXY __all__ = [ 'CouplerQubit', 'Waveform', 'WaveformType', - 'CosineParams', 'FlattopParams', 'SlepianParams', 'NumericParams', - 'G', 'PXY', 'PZ', 'PZ0' + 'CosineWaveform', 'FlattopWaveform', 'SlepianWaveform', 'NumericWaveform', + 'G', 'PXY', 'PZ', 'PZ0', ] diff --git a/cqlib/pulse/base_pulse.py b/cqlib/pulse/base_pulse.py index 99b1a85..e18202c 100644 --- a/cqlib/pulse/base_pulse.py +++ b/cqlib/pulse/base_pulse.py @@ -19,7 +19,7 @@ from abc import ABC, abstractmethod from cqlib.circuits.instruction import Instruction -from .waveform import WaveformParams +from .waveform import Waveform class BasePulse(Instruction, ABC): @@ -30,7 +30,7 @@ class BasePulse(Instruction, ABC): def __init__( self, name: str, - params: WaveformParams = None, + params: Waveform = None, label: str = None ): """Initialize base pulse instance diff --git a/cqlib/pulse/coupler_qubit.py b/cqlib/pulse/coupler_qubit.py index 42be3d9..cc8e685 100644 --- a/cqlib/pulse/coupler_qubit.py +++ b/cqlib/pulse/coupler_qubit.py @@ -30,7 +30,7 @@ class CouplerQubit(Bit): raise ValueError("CouplerQubit index must be non-negative.") inst = cls._cache.get(index) if inst is None: - inst = super().__new__(cls, index) + inst = super().__new__(cls) inst._index = index inst._initialized = True inst._hash = None diff --git a/cqlib/pulse/pxy.py b/cqlib/pulse/pxy.py index edf9e9e..1db8349 100644 --- a/cqlib/pulse/pxy.py +++ b/cqlib/pulse/pxy.py @@ -16,7 +16,7 @@ Sequential AC(Alternating Current) pulse controller for data qubit XY channels. """ from cqlib.exceptions import CqlibError from .base_pulse import BasePulse -from .waveform import WaveformParams, NumericParams +from .waveform import Waveform, NumericWaveform class PXY(BasePulse): @@ -30,7 +30,7 @@ class PXY(BasePulse): must be even. """ - def __init__(self, params: WaveformParams, label: str = None): + def __init__(self, params: Waveform, label: str = None): """ Initialize XY channel pulse with waveform parameters """ @@ -43,5 +43,5 @@ class PXY(BasePulse): p = self._params if p.phase is None or p.drag_alpha is None: raise CqlibError('PXY must have phase and drag_alpha params') - if isinstance(p, NumericParams) and (len(p.params) < 6 or len(p.params) % 2 == 1): + if isinstance(p, NumericWaveform) and (len(p.params) < 6 or len(p.params) % 2 == 1): raise CqlibError('The length of its list must >=6 and must be even.') diff --git a/cqlib/pulse/pz.py b/cqlib/pulse/pz.py index 13cc061..bbda609 100644 --- a/cqlib/pulse/pz.py +++ b/cqlib/pulse/pz.py @@ -14,7 +14,7 @@ """ Sequential DC pulse controller for data/coupling qubit Z channels. """ from cqlib.exceptions import CqlibError from .base_pulse import BasePulse -from .waveform import WaveformParams +from .waveform import Waveform class PZ(BasePulse): @@ -22,7 +22,7 @@ class PZ(BasePulse): Sequential DC pulse controller for data/coupling qubit Z channels. """ - def __init__(self, params: WaveformParams, label: str = None): + def __init__(self, params: Waveform, label: str = None): """Initialize Z-axis DC pulse with Stark shift parameters""" super().__init__('PZ', params, label=label) diff --git a/cqlib/pulse/pz0.py b/cqlib/pulse/pz0.py index 6c90813..1a7fbd4 100644 --- a/cqlib/pulse/pz0.py +++ b/cqlib/pulse/pz0.py @@ -15,7 +15,7 @@ """Parallel DC pulse controller for data/coupling qubit Z channels.""" from cqlib.exceptions import CqlibError from .base_pulse import BasePulse -from .waveform import WaveformParams +from .waveform import Waveform class PZ0(BasePulse): @@ -26,12 +26,12 @@ class PZ0(BasePulse): frequency adjustments. """ - def __init__(self, params: WaveformParams, label: str = None): + def __init__(self, params: Waveform, label: str = None): """ Initialize parallel Z-axis DC pulse controller. Args: - params (WaveformParams): Waveform params. + params (Waveform): Waveform. label (str, optional): Operational identifier for batch processing """ super().__init__('PZ0', params, label=label) diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py index ef944e9..780eaab 100644 --- a/cqlib/pulse/waveform.py +++ b/cqlib/pulse/waveform.py @@ -19,7 +19,7 @@ for generating quantum pulse sequences. It provides a standardized interface to configure and validate waveform parameters across different waveform types. """ - +from __future__ import annotations from dataclasses import dataclass from enum import IntEnum, unique from math import pi @@ -46,7 +46,7 @@ class WaveformType(IntEnum): @dataclass -class WaveformParams: +class Waveform: """ Base dataclass for waveform parameter validation and string representation. @@ -63,6 +63,51 @@ class WaveformParams: phase: float = None # PXY only drag_alpha: float = None # PXY only + @staticmethod + def create( + w_type: WaveformType, + length: int, + amplitude: float, + phase: float = None, + drag_alpha: float = None, + **kwargs + ) -> 'Waveform': + """ + Factory method to create waveform parameter objects based on the specified type. + + Instantiates a concrete waveform parameter class according to the given waveform type, + validates parameters, and returns the initialized object. + + Args: + w_type(WaveformType): Enumerated waveform type identifier. + length(int): Total duration of the waveform in samples (1-1e5). + amplitude: Normalized signal amplitude (0.0-1.0). + phase (float, optional): Phase shift for PXY. Must be in (-π, π] range. + drag_alpha (float, optional): DRAG correction coefficient for PXY. Must be positive. + **kwargs: Type-specific parameters for advanced waveform configurations: + - FLATTOP: edge (int) + - SLEPIAN: thf, thi, lam2, lam3 (float) + - NUMERIC: samples (list[float]) + """ + param_classes = { + WaveformType.COSINE: CosineWaveform, + WaveformType.FLATTOP: FlattopWaveform, + WaveformType.SLEPIAN: SlepianWaveform, + WaveformType.NUMERIC: NumericWaveform + } + + if w_type not in param_classes: + raise ValueError(f"Unsupported waveform type: {w_type}") + + return param_classes[w_type]( + waveform=w_type, + length=length, + amplitude=amplitude, + phase=phase, + drag_alpha=drag_alpha, + **kwargs + ) + def validate(self): """Performs basic parameter validation.""" if not isinstance(self.length, int) or self.length <= 0 or self.length > 1e5: @@ -87,7 +132,7 @@ class WaveformParams: @dataclass -class CosineParams(WaveformParams): +class CosineWaveform(Waveform): """Configuration parameters for cosine-shaped waveforms.""" def __post_init__(self): @@ -95,7 +140,7 @@ class CosineParams(WaveformParams): @dataclass -class FlattopParams(WaveformParams): +class FlattopWaveform(Waveform): """ Specialized parameters for flat-top waveforms with edge control. @@ -117,7 +162,7 @@ class FlattopParams(WaveformParams): @dataclass -class SlepianParams(WaveformParams): +class SlepianWaveform(Waveform): """ Specialized parameters for slepian waveforms @@ -140,7 +185,7 @@ class SlepianParams(WaveformParams): @dataclass -class NumericParams(WaveformParams): +class NumericWaveform(Waveform): """ Specialized parameters for numeric waveforms. @@ -163,57 +208,3 @@ class NumericParams(WaveformParams): ps = super().data ps.extend(self.params) return ps - - -# pylint: disable=too-few-public-methods -class Waveform: - """ - Factory class for creating waveform parameter objects based on type. - - Usage: - Instantiate through static method __new__ with type-specific parameters. - - Example: - >>> from cqlib.pulse.waveform import Waveform - >>> wave = Waveform( - >>> w_type=WaveformType.COSINE, - >>> length=1000, - >>> amplitude=0.8 - >>> ) - >>> print(f'Cosine 波形: {wave}') - """ - - # pylint: disable=too-many-arguments, too-many-positional-arguments - def __new__(cls, - w_type: WaveformType, - length: int, - amplitude: float, - phase: float = None, - drag_alpha: float = None, - **kwargs) -> CosineParams | FlattopParams | SlepianParams | NumericParams: - """ - 创建波形参数对象 - - :param w_type: 波形类型枚举值 - :param length: 波形总时长 - :param amplitude: 幅度 - :param kwargs: 类型特定参数 - """ - param_classes = { - WaveformType.COSINE: CosineParams, - WaveformType.FLATTOP: FlattopParams, - WaveformType.SLEPIAN: SlepianParams, - WaveformType.NUMERIC: NumericParams - } - - if w_type not in param_classes: - raise ValueError(f"Unsupported waveform type: {w_type}") - - return param_classes[w_type]( - waveform=w_type, - length=length, - amplitude=amplitude, - phase=phase, - drag_alpha=drag_alpha, - **kwargs - ) -- Gitee From c01990f0862a894c18692ac3bed6e411298050d3 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 24 Feb 2025 18:08:57 +0800 Subject: [PATCH 19/30] refactor(cqlib/pulse): Rename params to waveform in pulse classes --- cqlib/pulse/base_pulse.py | 8 ++++---- cqlib/pulse/g.py | 5 ++++- cqlib/pulse/pxy.py | 11 ++++++----- cqlib/pulse/pz.py | 6 +++--- cqlib/pulse/pz0.py | 8 ++++---- cqlib/pulse/waveform.py | 6 +++--- 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/cqlib/pulse/base_pulse.py b/cqlib/pulse/base_pulse.py index e18202c..61ecfbc 100644 --- a/cqlib/pulse/base_pulse.py +++ b/cqlib/pulse/base_pulse.py @@ -30,7 +30,7 @@ class BasePulse(Instruction, ABC): def __init__( self, name: str, - params: Waveform = None, + waveform: Waveform = None, label: str = None ): """Initialize base pulse instance @@ -40,9 +40,9 @@ class BasePulse(Instruction, ABC): params (WaveformParams): Waveform parameter container storing physical values. label (str): Optional operational label for experimental tracking """ - self._params = params - if params: - params = params.data + self._waveform = waveform + if waveform: + params = waveform.data else: params = [] super().__init__(name=name, num_qubits=1, params=params, label=label) diff --git a/cqlib/pulse/g.py b/cqlib/pulse/g.py index 196261d..1a0652b 100644 --- a/cqlib/pulse/g.py +++ b/cqlib/pulse/g.py @@ -33,7 +33,7 @@ class G(BasePulse): def __init__(self, length: int, coupling_strength: int, label: str = None): self.length = length self.coupling_strength = coupling_strength - super().__init__('G', params=None, label=label) + super().__init__('G', waveform=None, label=label) def validate(self): """ @@ -46,3 +46,6 @@ class G(BasePulse): if self.length < 0 or self.length > 1e5: raise CqlibError(f"Invalid duration {self.length}: " f"Must be 0-100,000 DAC cycles (1 cycle=0.5ns)") + + def __str__(self): + return f'{self.__class__.__name__}({self.length},{self.coupling_strength})' diff --git a/cqlib/pulse/pxy.py b/cqlib/pulse/pxy.py index 1db8349..9105968 100644 --- a/cqlib/pulse/pxy.py +++ b/cqlib/pulse/pxy.py @@ -30,18 +30,19 @@ class PXY(BasePulse): must be even. """ - def __init__(self, params: Waveform, label: str = None): + def __init__(self, waveform: Waveform, label: str = None): """ Initialize XY channel pulse with waveform parameters """ - super().__init__('PXY', params, label=label) + super().__init__('PXY', waveform=waveform, label=label) def validate(self): """ Verify PXY-specific waveform constraints. """ - p = self._params - if p.phase is None or p.drag_alpha is None: + waveform = self._waveform + if waveform.phase is None or waveform.drag_alpha is None: raise CqlibError('PXY must have phase and drag_alpha params') - if isinstance(p, NumericWaveform) and (len(p.params) < 6 or len(p.params) % 2 == 1): + if isinstance(waveform, NumericWaveform) \ + and (len(waveform.params) < 6 or len(waveform.params) % 2 == 1): raise CqlibError('The length of its list must >=6 and must be even.') diff --git a/cqlib/pulse/pz.py b/cqlib/pulse/pz.py index bbda609..d2e277d 100644 --- a/cqlib/pulse/pz.py +++ b/cqlib/pulse/pz.py @@ -22,11 +22,11 @@ class PZ(BasePulse): Sequential DC pulse controller for data/coupling qubit Z channels. """ - def __init__(self, params: Waveform, label: str = None): + def __init__(self, waveform: Waveform, label: str = None): """Initialize Z-axis DC pulse with Stark shift parameters""" - super().__init__('PZ', params, label=label) + super().__init__('PZ', waveform=waveform, label=label) def validate(self): - p = self._params + p = self._waveform if p.phase is not None or p.drag_alpha is not None: raise CqlibError('PZ must not have phase and drag_alpha params') diff --git a/cqlib/pulse/pz0.py b/cqlib/pulse/pz0.py index 1a7fbd4..20a8bd7 100644 --- a/cqlib/pulse/pz0.py +++ b/cqlib/pulse/pz0.py @@ -26,18 +26,18 @@ class PZ0(BasePulse): frequency adjustments. """ - def __init__(self, params: Waveform, label: str = None): + def __init__(self, waveform: Waveform, label: str = None): """ Initialize parallel Z-axis DC pulse controller. Args: - params (Waveform): Waveform. + waveform (Waveform): Waveform. label (str, optional): Operational identifier for batch processing """ - super().__init__('PZ0', params, label=label) + super().__init__('PZ0', waveform, label=label) def validate(self): """ Verify parallel DC pulse constraints. """ - p = self._params + p = self._waveform if p.phase is not None or p.drag_alpha is not None: raise CqlibError('PZ0 must not have phase and drag_alpha params') diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py index 780eaab..377e58c 100644 --- a/cqlib/pulse/waveform.py +++ b/cqlib/pulse/waveform.py @@ -152,7 +152,7 @@ class FlattopWaveform(Waveform): def __post_init__(self): self.validate() if 2 * self.edge >= self.length: - raise ValueError(f"Edge ({self.edge}) too large for length {self.length}") + raise CqlibError(f"Edge ({self.edge}) too large for length {self.length}") @property def data(self) -> list[float]: @@ -198,9 +198,9 @@ class NumericWaveform(Waveform): def __post_init__(self): super().validate() if any(not (0.0 <= x <= 1.0) for x in self.params): - raise ValueError("All data values must be in [0,1]") + raise CqlibError("All data values must be in [0,1]") if len(self.params) < 3: - raise ValueError("The numerical list of data pulses is in the form" + raise CqlibError("The numerical list of data pulses is in the form" " of [d0, d1, ..., dn], and the list length must be >= 3") @property -- Gitee From d6f923e32963de92a8a6a63b8edbea2eeed5c1c2 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Mon, 24 Feb 2025 18:11:24 +0800 Subject: [PATCH 20/30] test(pulse): Add test cases for pulse and waveform classes Add comprehensive test cases for the PXY, PZ, PZ0, and G pulse classes as well as the Waveform class. These tests cover various waveform types including cosine, flattop, slepian, and numeric. - feat(tests/pulse/test_instruction.py): Add test cases for PXY, PZ, PZ0, and G pulse classes - feat(tests/pulse/test_waveform.py): Add test cases for different waveform types in the Waveform class --- tests/pulse/test_instruction.py | 128 ++++++++++++++++++++++++++++++++ tests/pulse/test_waveform.py | 63 ++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 tests/pulse/test_instruction.py create mode 100644 tests/pulse/test_waveform.py diff --git a/tests/pulse/test_instruction.py b/tests/pulse/test_instruction.py new file mode 100644 index 0000000..0fe0221 --- /dev/null +++ b/tests/pulse/test_instruction.py @@ -0,0 +1,128 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Tests for pulse instruction""" + +import pytest + +from cqlib.exceptions import CqlibError +from cqlib.pulse import WaveformType, Waveform, PZ, PXY, PZ0, G + +# pylint: disable=missing-function-docstring + +class TestPXY: + """Test cases for the PXY pulse class.""" + + def test_cosine(self): + waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) + with pytest.raises(CqlibError) as exec_info: + PXY(waveform=waveform) + assert "PXY must have phase and drag_alpha params" in exec_info.value.args[0] + waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1, + phase=2, drag_alpha=0.2) + pxy = PXY(waveform=waveform) + assert str(pxy) == 'PXY(0,10,0.1,2,0.2)' + + def test_flattop(self): + waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1, + phase=2, drag_alpha=0.2) + pxy = PXY(waveform=waveform) + assert str(pxy) == 'PXY(1,10,0.1,2,0.2,1)' + + def test_slepian(self): + waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, phase=2, + drag_alpha=0.2, thf=0.1, thi=0.2, lam2=0.3, lam3=1) + pxy = PXY(waveform=waveform) + assert str(pxy) == 'PXY(2,10,0.1,2,0.2,0.1,0.2,0.3,1)' + + def test_numeric(self): + waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, phase=2, + drag_alpha=0.2, params=[0.1, 0.2, 0.3]) + with pytest.raises(CqlibError) as exec_info: + PXY(waveform=waveform) + assert 'The length of its list must >=6 and must be even.' in exec_info.value.args[0] + waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, phase=2, + drag_alpha=0.2, params=[0.1, 0.2, 0.3, 0.11, 0.22, 0.33]) + pxy = PXY(waveform=waveform) + assert str(pxy) == 'PXY(3,10,0.1,2,0.2,0.1,0.2,0.3,0.11,0.22,0.33)' + + +class TestPZ: + """Test cases for the PZ pulse class.""" + + def test_cosine(self): + waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) + pz = PZ(waveform=waveform) + assert str(pz) == 'PZ(0,10,0.1)' + + def test_flattop(self): + waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1) + pz = PZ(waveform=waveform) + assert str(pz) == 'PZ(1,10,0.1,1)' + + def test_slepian(self): + waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, + thf=0.1, thi=0.2, lam2=0.3, lam3=1) + pz = PZ(waveform=waveform) + assert str(pz) == 'PZ(2,10,0.1,0.1,0.2,0.3,1)' + + def test_numeric(self): + waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, + params=[0.1, 0.2, 0.3]) + pz = PZ(waveform=waveform) + assert str(pz) == 'PZ(3,10,0.1,0.1,0.2,0.3)' + + with pytest.raises(CqlibError) as exec_info: + Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, params=[0.1, 0.2]) + assert ('The numerical list of data pulses is in the form of [d0, d1, ..., dn]' + ', and the list length must be >= 3') in exec_info.value.args[0] + + +class TestPZ0: + """Test cases for the PZ0 pulse class.""" + + def test_cosine(self): + waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) + pz0 = PZ0(waveform=waveform) + assert str(pz0) == 'PZ0(0,10,0.1)' + + def test_flattop(self): + waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1) + pz = PZ0(waveform=waveform) + assert str(pz) == 'PZ0(1,10,0.1,1)' + + def test_slepian(self): + waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, + thf=0.1, thi=0.2, lam2=0.3, lam3=1) + pz0 = PZ0(waveform=waveform) + assert str(pz0) == 'PZ0(2,10,0.1,0.1,0.2,0.3,1)' + + def test_numeric(self): + waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, + params=[0.1, 0.2, 0.3]) + pz0 = PZ0(waveform=waveform) + assert str(pz0) == 'PZ0(3,10,0.1,0.1,0.2,0.3)' + + +class TestG: + """Test cases for the G (global coupling) pulse class.""" + + def test_g(self): + g = G(length=100, coupling_strength=-3) + assert str(g) == 'G(100,-3)' + + def test_g1(self): + with pytest.raises(TypeError) as exec_info: + # pylint: disable=unexpected-keyword-arg,no-value-for-parameter + G(waveform=100) + assert "got an unexpected keyword argument 'waveform'" in exec_info.value.args[0] diff --git a/tests/pulse/test_waveform.py b/tests/pulse/test_waveform.py new file mode 100644 index 0000000..16f2dfc --- /dev/null +++ b/tests/pulse/test_waveform.py @@ -0,0 +1,63 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""test waveform""" + +# pylint: disable=no-member + +from cqlib.pulse import Waveform, WaveformType + + +def test_cosine(): + """test cosine waveform""" + length = 10 + amplitude = 1 + cos = Waveform.create(WaveformType.COSINE, length=length, amplitude=amplitude) + assert cos.data == [WaveformType.COSINE, length, amplitude] + assert str(cos) == f'{WaveformType.COSINE.value} {length} {amplitude}' + + +def test_flattop(): + """test flattop waveform""" + length = 10 + amplitude = 1 + edge = 1 + waveform = Waveform.create(WaveformType.FLATTOP, length=length, amplitude=amplitude, edge=edge) + assert waveform.data == [WaveformType.FLATTOP, length, amplitude, edge] + assert str(waveform) == f'{WaveformType.FLATTOP.value} {length} {amplitude} {edge}' + + +def test_slepian(): + """test slepian waveform""" + length = 10 + amplitude = 1 + thf, thi, lam2, lam3 = 0.1, 0.2, 0.3, 0.4 + waveform = Waveform.create(WaveformType.SLEPIAN, length=length, amplitude=amplitude, + thf=thf, thi=thi, lam2=lam2, lam3=lam3) + assert waveform.data == [WaveformType.SLEPIAN, length, amplitude, thf, thi, lam2, lam3] + assert str(waveform) == (f'{WaveformType.SLEPIAN.value} {length} ' + f'{amplitude} {thf} {thi} {lam2} {lam3}') + + +def test_numeric(): + """test numeric waveform""" + length = 10 + amplitude = 1 + params = 0.1, 0.2, 0.3, 0.4 + waveform = Waveform.create(WaveformType.NUMERIC, length=length, amplitude=amplitude, + params=params) + d = [WaveformType.NUMERIC.value, length, amplitude] + d.extend(params) + assert waveform.data == d + assert str(waveform) == (f'{WaveformType.NUMERIC.value} {length} ' + f'{amplitude} {" ".join(map(str, params))}') -- Gitee From d01061092fcb7ab8cf2a9540dbaabd6b7a653095 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 09:52:45 +0800 Subject: [PATCH 21/30] feat(pulse): Add PulseCircuit class for pulse-level control Introduce a new class `PulseCircuit` in the `pulse` module to support pulse-level control of qubits and coupler qubits. This class extends the existing `Circuit` class and includes methods for appending pulse instructions, managing coupler qubits, and applying specific pulses like G, PXY, PZ, and PZ0. --- cqlib/pulse/__init__.py | 5 +- cqlib/pulse/pulse_circuit.py | 177 +++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 cqlib/pulse/pulse_circuit.py diff --git a/cqlib/pulse/__init__.py b/cqlib/pulse/__init__.py index 817221a..d634487 100644 --- a/cqlib/pulse/__init__.py +++ b/cqlib/pulse/__init__.py @@ -22,10 +22,13 @@ from .g import G from .pz import PZ from .pz0 import PZ0 from .pxy import PXY +from .pulse_circuit import PulseCircuit __all__ = [ 'CouplerQubit', 'Waveform', 'WaveformType', - 'CosineWaveform', 'FlattopWaveform', 'SlepianWaveform', 'NumericWaveform', + 'CosineWaveform', 'FlattopWaveform', + 'SlepianWaveform', 'NumericWaveform', 'G', 'PXY', 'PZ', 'PZ0', + 'PulseCircuit', ] diff --git a/cqlib/pulse/pulse_circuit.py b/cqlib/pulse/pulse_circuit.py new file mode 100644 index 0000000..b34a16f --- /dev/null +++ b/cqlib/pulse/pulse_circuit.py @@ -0,0 +1,177 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +A specialized quantum circuit class designed for pulse-level control +of qubits and coupler qubits. +""" +from collections.abc import Sequence + +from cqlib.circuits.circuit import Circuit, Qubits +from cqlib.circuits.instruction import Instruction +from cqlib.circuits.instruction_data import InstructionData +from cqlib.circuits.parameter import Parameter +from cqlib.circuits.qubit import Qubit +from cqlib.exceptions import CqlibError + +from .coupler_qubit import CouplerQubit +from .g import G +from .pxy import PXY +from .pz import PZ +from .pz0 import PZ0 +from .waveform import Waveform + +CouplerQubits = int | CouplerQubit | Sequence[int | CouplerQubit] + + +class PulseCircuit(Circuit): + """ + Circuit with pulse support. + """ + + def __init__( + self, + qubits: Qubits, + coupler_qubits: CouplerQubits, + parameters: Sequence[Parameter | str] | None = None + ) -> None: + """ + Initialize PulseCircuit. + + Args: + qubits(Qubits): Specification for standard qubits. + coupler_qubits(CouplerQubits): Specification for coupler qubits. + parameters(Sequence[Parameter | str] | None): Circuit parameters. + """ + super().__init__(qubits, parameters) + self._coupler_qubits = self._initialize_coupler_qubits(coupler_qubits) + + @property + def coupler_qubits(self) -> list[CouplerQubit]: + """ + A list of initialized coupler qubit objects in the circuit. + """ + return list(self._coupler_qubits.values()) + + def append_pulse(self, instruction: Instruction, qubit: CouplerQubit | Qubit): + """ + Appends a pulse instruction to the circuit. + + Args: + instruction (Instruction): Pulse instruction to add. + qubit (Qubit | CouplerQubit): Target qubit or coupler qubit. + """ + if isinstance(qubit, CouplerQubit) and str(qubit) not in self._coupler_qubits: + raise CqlibError(f"Coupler qubit {qubit} is not registered in the circuit. " + f"Available coupler qubits: {self.coupler_qubits}") + if isinstance(qubit, Qubit) and str(qubit) not in self._qubits: + raise CqlibError(f"Qubit {qubit} is not registered in the circuit. " + f"Available qubits: {self.qubits}") + self._circuit_data.append(InstructionData(instruction=instruction, qubits=[qubit])) + + def g(self, couple_qubit: CouplerQubit | int, length: int, coupling_strength: int): + """ + Applies a G-pulse (coupling pulse) to a coupler qubit. + + Args: + couple_qubit(CouplerQubit | int): Target coupler qubit or its index. + length(int): Duration of the pulse in time steps. + coupling_strength(int): Strength of the coupling interaction. + """ + if isinstance(couple_qubit, int): + couple_qubit = CouplerQubit(couple_qubit) + self.append_pulse(G(length, coupling_strength), couple_qubit) + + def pxy(self, qubit: Qubit | int, waveform: Waveform): + """ + Applies a PXY-pulse (XY control pulse) to a standard qubit. + + Args: + qubit(Qubit | int): Target qubit or its index. + waveform(Waveform): Waveform object defining the pulse shape. + """ + if isinstance(qubit, int): + qubit = Qubit(qubit) + self.append_pulse(PXY(waveform=waveform), qubit) + + def pz(self, qubit: CouplerQubit | Qubit, waveform: Waveform): + """ + Applies a PZ-pulse (Z control pulse) to a qubit or coupler qubit. + + Args: + qubit(CouplerQubit | Qubit): Target CouplerQubit or Qubit. + waveform (Waveform): Waveform object defining the pulse shape. + """ + self.append_pulse(PZ(waveform=waveform), qubit) + + def pz0(self, qubit: CouplerQubit | Qubit, waveform: Waveform): + """ + Applies a PZ0-pulse (parallel) to a qubit or coupler qubit. + + Args: + qubit(CouplerQubit | Qubit): Target CouplerQubit or Qubit. + waveform (Waveform): Waveform object defining the pulse shape. + """ + self.append_pulse(PZ0(waveform=waveform), qubit) + + def add_coupler_qubits(self, couple_qubits: CouplerQubits): + """ + Adds a coupler qubit(or list) to the circuit, ensuring it does not already exist. + + Args: + couple_qubits (CouplerQubits): The coupler qubits to add, specified as an + integer index or a Qubit object. + """ + if not isinstance(couple_qubits, Sequence): + couple_qubits = [couple_qubits] + + for qubit in couple_qubits: + if isinstance(qubit, int): + qubit = CouplerQubit(qubit) + elif not isinstance(qubit, CouplerQubit): + raise TypeError(f"{qubit} must be an int or CouplerQubit instance.") + if str(qubit) in self._qubits: + raise ValueError(f"CouplerQubit {qubit} already exists in the circuit.") + self._coupler_qubits[str(qubit)] = qubit + + @staticmethod + def _initialize_coupler_qubits(coupler_qubits: CouplerQubits) -> dict[str, CouplerQubit]: + """ + Helper function to initialize coupler_qubits. + + Args: + coupler_qubits (CouplerQubits): Input coupler qubits specification. + + Returns: + dict[int, CouplerQubit]: Dictionary of CouplerQubit objects. + """ + if isinstance(coupler_qubits, int): + if coupler_qubits < 0: + raise ValueError("Number of coupler qubits must be non-negative.") + return {str(Qubit(i)): CouplerQubit(i) for i in range(coupler_qubits)} + if isinstance(coupler_qubits, CouplerQubit): + return {str(coupler_qubits): coupler_qubits} + if not isinstance(coupler_qubits, (list, tuple)): + raise ValueError("Invalid coupler_qubits input. Expected int, CouplerQubit," + " or list/tuple of these.") + + qs = {} + for qubit in coupler_qubits: + if isinstance(qubit, int): + qubit = CouplerQubit(qubit) + elif not isinstance(qubit, CouplerQubit): + raise TypeError("CouplerQubit must be an int or CouplerQubit instance.") + if qubit.index in qs: + raise ValueError(f"Duplicate qubit detected: {qubit}") + qs[str(qubit)] = qubit + return qs -- Gitee From f9c22593fc00b1f84712e4c394da9bdc6a8d7f1a Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 10:28:18 +0800 Subject: [PATCH 22/30] refactor(cqlib/circuits): Remove unused imports and pulse-related methods - Removed unused imports for `CouplingQubit`, `PulseParameter`, and `Waveform`. - Removed unused methods related to pulse operations in `circuit.py` and `instruction.py`. --- cqlib/__init__.py | 2 +- cqlib/circuits/__init__.py | 4 - cqlib/circuits/circuit.py | 29 +------ cqlib/circuits/coupling_qubit.py | 40 --------- cqlib/circuits/instruction.py | 3 +- cqlib/circuits/pulse/__init__.py | 14 ---- cqlib/circuits/pulse/pulse.py | 103 ------------------------ cqlib/circuits/pulse/pulse_parameter.py | 83 ------------------- cqlib/circuits/pulse/waveform.py | 35 -------- 9 files changed, 5 insertions(+), 308 deletions(-) delete mode 100644 cqlib/circuits/coupling_qubit.py delete mode 100644 cqlib/circuits/pulse/__init__.py delete mode 100644 cqlib/circuits/pulse/pulse.py delete mode 100644 cqlib/circuits/pulse/pulse_parameter.py delete mode 100644 cqlib/circuits/pulse/waveform.py diff --git a/cqlib/__init__.py b/cqlib/__init__.py index c3e0aa5..909f2a7 100644 --- a/cqlib/__init__.py +++ b/cqlib/__init__.py @@ -14,7 +14,7 @@ """cqlib""" from cqlib.circuits import Circuit, Barrier, Qubit, Measure, Parameter, Instruction, \ - InstructionData, gates, CouplingQubit, Pulse, PulseParameter, Waveform + InstructionData, gates from cqlib.circuits import dag_to_circuit, circuit_to_dag from cqlib.quantum_platform import BasePlatform, TianYanPlatform, GuoDunPlatform, QuantumLanguage diff --git a/cqlib/circuits/__init__.py b/cqlib/circuits/__init__.py index 97541c4..4a60287 100644 --- a/cqlib/circuits/__init__.py +++ b/cqlib/circuits/__init__.py @@ -15,15 +15,11 @@ from .barrier import Barrier from .circuit import Circuit -from .coupling_qubit import CouplingQubit from .instruction_data import InstructionData from .measure import Measure from .parameter import Parameter from .qubit import Qubit from .instruction import Instruction from .dag import dag_to_circuit, circuit_to_dag -from .pulse.pulse_parameter import PulseParameter -from .pulse.waveform import Waveform -from .pulse.pulse import Pulse from .commutation import circuit_commutator, check_commutation, commute diff --git a/cqlib/circuits/circuit.py b/cqlib/circuits/circuit.py index 9a720d7..d1bea91 100644 --- a/cqlib/circuits/circuit.py +++ b/cqlib/circuits/circuit.py @@ -27,9 +27,6 @@ from cqlib.circuits.instruction_data import InstructionData from cqlib.circuits.measure import Measure from cqlib.circuits.parameter import Parameter from cqlib.circuits.qubit import Qubit -from cqlib.circuits.coupling_qubit import CouplingQubit -from cqlib.circuits.pulse.pulse import Pulse -from cqlib.circuits.pulse.pulse_parameter import PulseParameter # Type Alias Definition Qubits = Union[Qubit, int, Sequence[Union[Qubit, int]]] @@ -270,8 +267,8 @@ class Circuit: for q in qubits: if isinstance(q, int): q = Qubit(q) - if not isinstance(q, (Qubit, CouplingQubit)): - raise TypeError("Qubit must be an instance of QuBit CouplingQubit or an integer.") + if not isinstance(q, Qubit): + raise TypeError(f"{q} must be an instance of QuBit or an integer.") if str(q) not in self._qubits: raise ValueError(f"{q} not found in circuit.") qs.append(q) @@ -285,7 +282,7 @@ class Circuit: for param in instruction.params: if isinstance(param, str): param = Parameter(param) - if not isinstance(param, (Parameter, PulseParameter, float, int)): + if not isinstance(param, (Parameter, float, int)): raise TypeError("Parameter must be a float, int, str, or an instance " "of Parameter or PulseParameter.") if isinstance(param, Parameter): @@ -620,18 +617,6 @@ class Circuit: """ self.append(Barrier(len(qubits)), list(qubits)) - def pulse(self, qubit: Qubit | CouplingQubit, pulse_param: PulseParameter): - """ - - Args: - qubit (Qubit or CouplingQubit): The qubits to measure, can be a single - qubit or a list of qubits. - pulse_param (PulseParameter): - """ - if not isinstance(qubit, (Qubit, CouplingQubit)): - raise TypeError("pulse") - self.append(Pulse(pulse_param), [qubit]) - def measure(self, qubits: Qubits): """ Measures the specified qubits and collapses their quantum state into classical bits. @@ -656,8 +641,6 @@ class Circuit: measured_qubits.add(qubit) for _, qubit in self._qubits.items(): - if isinstance(qubit, CouplingQubit): - continue if qubit not in measured_qubits: self.append(Measure(), qubit) @@ -829,12 +812,6 @@ class Circuit: line = line.strip() if not line: continue - if line.startswith('PLS ') or line.startswith('PULSE '): - pls, coupling_qubit = Pulse.load(line) - if str(coupling_qubit) not in circuit.qubits: - circuit.add_qubit(coupling_qubit) - circuit.append(instruction=pls, qubits=coupling_qubit) - continue match = line_pattern.match(line) if not match: raise ValueError(f'Invalid line format: {line}') diff --git a/cqlib/circuits/coupling_qubit.py b/cqlib/circuits/coupling_qubit.py deleted file mode 100644 index 92762e1..0000000 --- a/cqlib/circuits/coupling_qubit.py +++ /dev/null @@ -1,40 +0,0 @@ -# This code is part of cqlib. -# -# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., -# Center for Excellence in Quantum Information and Quantum Physics. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Coupling Qubit""" - -from __future__ import annotations - -import weakref - -from .qubit import Qubit - - -class CouplingQubit(Qubit): - """A class representing a coupled qubit in a quantum system.""" - - _cache = weakref.WeakValueDictionary[int, 'CouplingQubit']() - - def __new__(cls, index: int) -> CouplingQubit: - if index < 0: - raise ValueError("Qubit index must be non-negative.") - inst = cls._cache.get(index) - if inst is None: - inst = super().__new__(cls, index) - inst._index = index - inst._hash = None - cls._cache[index] = inst - return inst - - def __str__(self): - return f'G{self.index}' diff --git a/cqlib/circuits/instruction.py b/cqlib/circuits/instruction.py index 2bfacf8..34b31b8 100644 --- a/cqlib/circuits/instruction.py +++ b/cqlib/circuits/instruction.py @@ -17,7 +17,6 @@ from __future__ import annotations from copy import copy from typing import List, Optional, Union, Sequence -from cqlib.circuits.pulse.pulse_parameter import PulseParameter from .parameter import Parameter @@ -31,7 +30,7 @@ class Instruction: self, name: str, num_qubits: int, - params: Optional[List[Union[Parameter, 'PulseParameter', float, complex]]] = None, + params: Optional[List[Union[Parameter, float, complex]]] = None, label: Optional[str] = None ): """ diff --git a/cqlib/circuits/pulse/__init__.py b/cqlib/circuits/pulse/__init__.py deleted file mode 100644 index 70f7be4..0000000 --- a/cqlib/circuits/pulse/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# This code is part of cqlib. -# -# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., -# Center for Excellence in Quantum Information and Quantum Physics. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Pulse""" diff --git a/cqlib/circuits/pulse/pulse.py b/cqlib/circuits/pulse/pulse.py deleted file mode 100644 index a142d23..0000000 --- a/cqlib/circuits/pulse/pulse.py +++ /dev/null @@ -1,103 +0,0 @@ -# This code is part of cqlib. -# -# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., -# Center for Excellence in Quantum Information and Quantum Physics. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Pulse operation command""" -import re -from typing import Optional - -from cqlib.circuits.coupling_qubit import CouplingQubit -from cqlib.circuits.instruction import Instruction -from cqlib.circuits.qubit import Qubit - -from .pulse_parameter import PulseParameter -from .waveform import Waveform - - -# pylint: disable=too-few-public-methods -class Pulse(Instruction): - """ - A class representing a quantum pulse instruction within a quantum circuit. - - The Pulse class allows for fine-grained control over quantum operations by applying - specific pulses to qubits. This class can be used to construct instructions that - directly manipulate qubit states through customized pulse sequences. It supports - the highest level of control for quantum operations, including single-qubit gates, - multi-qubit gates, and other operations. - """ - - def __init__( - self, - param: PulseParameter, - label: Optional[str] = None - ): - """ - Initialize a new Pulse instruction. - - Args: - param (PulseParameter): The parameters defining the pulse, such as waveform, - start time, length, amplitude, etc. - label (Optional[str]): An optional label for the pulse instruction. Default is None. - """ - super().__init__("PLS", 1, [param], label=label) - - @staticmethod - def load(line: str) -> ('Pulse', Qubit): - """ - load Pulse gate - Parameters - ---------- - line - - Returns - ------- - - """ - tokens = line.strip().split() - if len(tokens) < 9: - raise ValueError(f"Invalid instruction, expected at least 9 tokens, got {tokens}") - instruction, qubit, waveform, t_start, length, amplitude, frequency, phase, \ - drag_alpha, *other_params = tokens - if instruction not in ('PLS', 'PULSE'): - raise ValueError(f"Unknown instruction '{instruction}'") - - qubit_obj = Pulse._parse_qubit(qubit) - param = PulseParameter( - waveform=Waveform(int(waveform)), - t_start=int(t_start), - length=int(length), - amplitude=int(amplitude), - frequency=int(frequency), - phase=int(phase), - drag_alpha=int(drag_alpha), - others=[Pulse._parse_param(param) for param in other_params], - ) - - return Pulse(param=param), qubit_obj - - @staticmethod - def _parse_qubit(qubit: str) -> Qubit: - if not re.match(r'^[GQ]\d+$', qubit): - raise ValueError(f"Invalid qubit identifier '{qubit}'") - if qubit[0] == 'G': - return CouplingQubit(int(qubit[1:])) - if qubit[0] == 'Q': - return Qubit(int(qubit[1:])) - raise ValueError(f"Unknown qubit type '{qubit[0]}'") - - @staticmethod - def _parse_param(param: str) -> float: - try: - num = float(param) - return int(num) if num.is_integer() else num - except ValueError as exc: - raise ValueError(f"Invalid parameter '{param}'") from exc diff --git a/cqlib/circuits/pulse/pulse_parameter.py b/cqlib/circuits/pulse/pulse_parameter.py deleted file mode 100644 index 8683a5a..0000000 --- a/cqlib/circuits/pulse/pulse_parameter.py +++ /dev/null @@ -1,83 +0,0 @@ -# This code is part of cqlib. -# -# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., -# Center for Excellence in Quantum Information and Quantum Physics. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Pulse Parameter""" - -from .waveform import Waveform - - -# pylint: disable=too-many-instance-attributes, too-many-arguments, too-few-public-methods -class PulseParameter: - """A class representing the parameters of a pulse in a quantum control sequence.""" - - def __init__( - self, - waveform: Waveform, - t_start: int, - length: int = 0, - amplitude: int = 0, - frequency: int = 0, - phase: int = 0, - drag_alpha: int = 0, - others: list[float] = None - ): - """ - Initialize a pulse parameter. - - if waveform is Numerical waveform. Each point in the waveform is defined by a numerical - value provided by the user through the 'other parameters' list. This waveform type allows - for precise control over each sample point in the pulse. - Example usage: PULSE C02 -1 256 0 0 0 0 0 200 100 300 400 … 23466. The pulse starts at - time 256 and has the numerical waveform values 200, 100, 300, 400, … 23466. - For numerical waveforms, the 'length', 'amplitude', 'frequency', and 'phase' parameters - are not relevant. - - Args: - waveform (Waveform): The waveform type or identifier associated with the pulse. - t_start (int): The start time of the pulse relative to the control waveform sequence - start point. A t_start < 0 indicates that the pulse start time should be aligned - with the end time of the previous pulse or gate operation, enabling sequential - concatenation. A t_start >= 0 sets the pulse start time to an absolute value, - allowing the pulse to be inserted freely in the sequence, independent of previous - gate operations. - length (int, optional): The duration of the pulse in DAC sampling cycles. This value - might be irrelevant for certain waveform types, in which case it can be set to any - value. Default is 0. - amplitude (int, optional): The amplitude of the pulse, in DAC code values. The maximum - amplitude is 32768. For some waveform types, this parameter may be irrelevant and - can be set to any value. Default is 0. - frequency (int, optional): The sideband mixing frequency of the pulse in Hz. This value - may be irrelevant for certain waveform types. Default is 0. - phase (int, optional): The sideband mixing phase of the pulse in radians. This value may - be irrelevant for certain waveform types. Default is 0. - drag_alpha (int, optional): The DRAG correction coefficient, used to minimize leakage - errors during pulse execution. This value may be irrelevant for certain waveform - types. Default is 0. - others (list[int], optional): Additional parameters needed to generate the waveform, - specific to the waveform type. For numeric waveforms, this could be a sequence of - values defining each sample point of the waveform. For other waveform types, these - parameters might not be used. Default is an empty list. - """ - self.waveform = waveform - self.t_start = t_start - self.length = length - self.amplitude = amplitude - self.frequency = frequency - self.phase = phase - self.drag_alpha = drag_alpha - self.others = others or [] - - def __str__(self): - return (f"{self.waveform.value} {self.t_start} {self.length} {self.amplitude} " - f"{self.frequency} {self.phase} {self.drag_alpha} " - f"{' '.join(map(str, self.others))}") diff --git a/cqlib/circuits/pulse/waveform.py b/cqlib/circuits/pulse/waveform.py deleted file mode 100644 index ac833aa..0000000 --- a/cqlib/circuits/pulse/waveform.py +++ /dev/null @@ -1,35 +0,0 @@ -# This code is part of cqlib. -# -# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., -# Center for Excellence in Quantum Information and Quantum Physics. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Waveform""" - -from enum import Enum - - -class Waveform(Enum): - """ - An enumeration representing different types of waveforms that can be used - in quantum pulse sequences. - - 0: Numerical waveform. - 1: Flattop waveform. - 2: Cosine waveform. - - Note: - ---- - Additional waveform types may be added in the future, but currently only the three types - listed above are available. - """ - NUMERICAL = 0 - FLATTOP = 1 - COSINE = 2 -- Gitee From 25e6d7c2001b9fafe043f0a6e91d9dd73789a2ca Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 10:31:21 +0800 Subject: [PATCH 23/30] fix(tests): Remove unused imports and tests in test_circuit.py and test_qubit.py --- tests/circuit/test_circuit.py | 47 ++++------------------------------- tests/circuit/test_qubit.py | 16 +----------- 2 files changed, 6 insertions(+), 57 deletions(-) diff --git a/tests/circuit/test_circuit.py b/tests/circuit/test_circuit.py index 06c8ecd..67096c5 100644 --- a/tests/circuit/test_circuit.py +++ b/tests/circuit/test_circuit.py @@ -17,8 +17,7 @@ Test Circuit """ import pytest -from cqlib.circuits import Circuit, Qubit, gates, Parameter, CouplingQubit, \ - PulseParameter, Waveform +from cqlib.circuits import Circuit, Qubit, gates, Parameter def test_basic_circuit(): @@ -262,47 +261,8 @@ def test_load_circuit_params(): Circuit.load(qcis) -def test_pulse(): - """test load circuit with param, but will fail""" - q0 = Qubit(0) - q6 = Qubit(6) - g0 = CouplingQubit(0) - circuit = Circuit([q0, q6, g0]) - - circuit.h(q0) - circuit.h(q6) - - pp = PulseParameter( - waveform=Waveform.FLATTOP, - t_start=-1, - length=100, - amplitude=0, - frequency=0, - phase=0, - drag_alpha=0, - others=[4] - ) - circuit.pulse(qubit=g0, pulse_param=pp) - circuit.measure_all() - target = """H Q0 -H Q6 -PLS G0 1 -1 100 0 0 0 0 4 -M Q0 -M Q6""" - assert circuit.qcis == target - - -def test_load_pulse(): - qcis = """H Q0 -H Q6 -PLS G0 1 -1 100 0 0 0 0 4 -M Q0 -M Q6""" - circuit = Circuit.load(qcis) - assert circuit.qcis == qcis - - def test_load_e(): + """ test load Scientific notation """ qcis = """ Y2M Q0 RZ Q0 3.4641020674186507e-07 @@ -322,6 +282,7 @@ X2P Q2""" def test_add(): + """ test parameters add """ qcis = "H Q0\nCZ Q0 Q1" c1 = Circuit(2, parameters=['gamma']) c1.h(0) @@ -341,7 +302,9 @@ def test_add(): assert c1.qcis == '\n'.join([qcis, qcis]) assert c2.qcis == qcis + def test_copy(): + """ test copy circuit """ phi = Parameter('phi') theta = Parameter('theta') circuit = Circuit(2, [phi, theta]) diff --git a/tests/circuit/test_qubit.py b/tests/circuit/test_qubit.py index 3eb5157..25ea7b4 100644 --- a/tests/circuit/test_qubit.py +++ b/tests/circuit/test_qubit.py @@ -15,7 +15,7 @@ Test qubits in the circuit """ -from cqlib.circuits import Circuit, Qubit, CouplingQubit +from cqlib.circuits import Circuit, Qubit def test_number_of_qubit(): @@ -56,17 +56,3 @@ def test_qubit_singleton(): circuit = Circuit(1) assert q0 is q0_1 assert q0 is circuit.qubits[0] - - -def test_couple_qubit(): - """test couple Qubit""" - coupling_bit_0 = CouplingQubit(0) - coupling_bit_1 = CouplingQubit(0) - assert coupling_bit_0 is coupling_bit_1 - assert str(coupling_bit_0) == 'G0' - assert repr(coupling_bit_0) == 'CouplingQubit(0)' - qbit_0 = Qubit(0) - qbit_1 = Qubit(0) - assert qbit_0 is qbit_1 - assert repr(qbit_0) == 'Qubit(0)' - assert qbit_0 is not coupling_bit_0 -- Gitee From e3098f177679159207c5185fc6cf8d59e8e7b521 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 11:17:59 +0800 Subject: [PATCH 24/30] feat(cqlib/pulse/pulse_circuit.py): add to_qasm2 method for pulse circuit export --- cqlib/pulse/pulse_circuit.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cqlib/pulse/pulse_circuit.py b/cqlib/pulse/pulse_circuit.py index b34a16f..f5f1bf8 100644 --- a/cqlib/pulse/pulse_circuit.py +++ b/cqlib/pulse/pulse_circuit.py @@ -175,3 +175,23 @@ class PulseCircuit(Circuit): raise ValueError(f"Duplicate qubit detected: {qubit}") qs[str(qubit)] = qubit return qs + + def to_qasm2(self) -> str: + """ + Generate a QASM 2.0 string representation of the circuit. + + Note: + Circuits containing coupler qubits cannot be exported to QASM 2.0 format, + as the standard does not support coupler qubit operations. + + Returns: + str: QASM 2.0 compliant code representing the circuit + + Raises: + CqlibError: If the circuit contains coupler qubits, as they are not supported + in QASM 2.0. + """ + if self._coupler_qubits: + raise CqlibError(f"QASM 2.0 export not supported for circuits with coupler qubits. " + f"Found coupler(s): {self.coupler_qubits}") + return super().to_qasm2() -- Gitee From b729e1febca4e668e5f5dbf1be20315a0468cdd1 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 15:20:47 +0800 Subject: [PATCH 25/30] feat(cqlib/pulse/waveform.py): Add load method to Waveform class --- cqlib/pulse/waveform.py | 59 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py index 377e58c..8a1f4f3 100644 --- a/cqlib/pulse/waveform.py +++ b/cqlib/pulse/waveform.py @@ -130,6 +130,57 @@ class Waveform: ps.extend([self.phase, self.drag_alpha]) return ps + @classmethod + def load(cls, waveform: str | list[float | int], gate: str) -> Waveform: + """ + Constructs Waveform from serialized data. + + Args: + waveform (str | list[float | int]): Input data as either: + - Space-separated string of parameters + - List of numeric values + gate (str): Target gate operation (PXY/PZ/etc.) determining + parameter interpretation + + Returns: + Waveform: Instantiated waveform object + """ + if isinstance(waveform, str): + waveform = map(float, waveform.split()) + ps = waveform + try: + waveform_type = WaveformType(ps[0]) + except ValueError as e: + raise CqlibError(f"Invalid waveform type value: {ps[0]}") from e + length, amplitude = ps[1:3] + ps = ps[3:] + if gate == 'PXY': + phase, drag_alpha = ps[:2] + ps = ps[2:] + else: + phase, drag_alpha = None, None + kwargs = { + 'w_type': waveform_type, + 'length': int(length), + 'amplitude': amplitude, + 'phase': phase, + 'drag_alpha': drag_alpha + } + + if waveform_type == WaveformType.FLATTOP: + kwargs['edge'] = ps[0] + elif waveform_type == WaveformType.SLEPIAN: + thf, thi, lam2, lam3 = ps[:4] + kwargs.update({ + 'thf': thf, + 'thi': thi, + 'lam2': lam2, + 'lam3': lam3, + }) + elif waveform_type == WaveformType.NUMERIC: + kwargs['data_list'] = ps + return Waveform.create(**kwargs) + @dataclass class CosineWaveform(Waveform): @@ -193,18 +244,18 @@ class NumericWaveform(Waveform): - params: List of floats, Values must be in [0.0, 1.0], Minimum length of 3 samples; """ - params: list[float] = None + data_list: list[float] = None def __post_init__(self): super().validate() - if any(not (0.0 <= x <= 1.0) for x in self.params): + if any(not (0.0 <= x <= 1.0) for x in self.data_list): raise CqlibError("All data values must be in [0,1]") - if len(self.params) < 3: + if len(self.data_list) < 3: raise CqlibError("The numerical list of data pulses is in the form" " of [d0, d1, ..., dn], and the list length must be >= 3") @property def data(self) -> list[float]: ps = super().data - ps.extend(self.params) + ps.extend(self.data_list) return ps -- Gitee From ded7e3168314ec0e427b6cc11e89c9646ea8403f Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 15:22:51 +0800 Subject: [PATCH 26/30] refactor(cqlib/circuits/circuit.py): Convert static method to class method and enhance parsing --- cqlib/circuits/circuit.py | 104 ++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/cqlib/circuits/circuit.py b/cqlib/circuits/circuit.py index d1bea91..a9a6920 100644 --- a/cqlib/circuits/circuit.py +++ b/cqlib/circuits/circuit.py @@ -781,63 +781,91 @@ class Circuit: ops.append(' '.join(line)) return '\n'.join(ops) - @staticmethod - def load(qcis: str) -> Circuit: + @classmethod + def load(cls, qcis: str) -> Circuit: """ - Loads a quantum circuit from a formatted string. + Loads a quantum circuit from a QCIS string. - Args: - qcis (str): A string containing quantum circuit instructions, where each line - represents a circuit operation. The format for each line is - "GATE QUBITS [PARAMETERS]", e.g., "H Q0 Q1", "CX Q0 Q1", "RZ Q0 0.5". + Args: + qcis (str): A string containing quantum circuit instructions, where each line + represents a circuit operation. The format for each line is + "GATE QUBITS [PARAMETERS]", e.g., "H Q0 Q1", "CX Q0 Q1", "RZ Q0 0.5". - Returns: - Circuit: A quantum circuit object constructed based on the input string. + Returns: + Circuit: A quantum circuit object constructed based on the input string. - Raises: - ValueError: If the input string is improperly formatted or contains - unknown gate operations. + Raises: + ValueError: If the input string is improperly formatted or contains + unknown gate operations. - Example: - >>> circuit_description = "H Q0\\nCX Q0 Q1\\nM Q0" - >>> c = Circuit.load(circuit_description) - >>> print(c) - Circuit with 3 instructions - """ - circuit = Circuit(0) + Example: + >>> circuit_description = "H Q0\\nCX Q0 Q1\\nM Q0" + >>> c = Circuit.load(circuit_description) + >>> print(c) + Circuit with 3 instructions + """ + circuit = cls(qubits=[]) line_pattern = re.compile(r'^([A-Z][A-Z0-9]*)\s+((?:Q[0-9]+\s*)+)' r'((?:[+-]?(?:\d*\.\d+|\d+)(?:[Ee][+-]?\d+)?\s*)*)$') for line in qcis.split('\n'): - line = line.strip() + # Delete comments `#` `//`. + line = re.sub(r'(#|//).*', '', line).strip() if not line: continue match = line_pattern.match(line) if not match: - raise ValueError(f'Invalid line format: {line}') - + raise ValueError(f'Invalid instruction format: {line}') gate, qubits_str, params_str = match.groups() - qubits = [circuit._qubits.setdefault(str(Qubit(int(q[1:]))), Qubit(int(q[1:]))) - for q in qubits_str.split()] + qubits = circuit._parse_qubits_str(qubits_str) params = [float(p) for p in params_str.split()] if params_str else [] + circuit._process_instruction(gate, qubits, params) + return circuit - if gate == 'M': - for qubit in qubits: - circuit.append_instruction_data( - InstructionData(instruction=Measure(), qubits=[qubit])) - continue - if gate == 'B': - circuit.append_instruction_data( - InstructionData(instruction=Barrier(len(qubits)), qubits=qubits)) - continue + def _parse_qubits_str(self, qubits_str: str) -> list[Qubit]: + """ + Parses and initializes standard qubit identifiers from QCIS instruction. + + Converts space-separated qubit tokens (Q-prefixed) into Qubit objects, + creating new instances only when encountering previously unseen qubits. + + Args: + qubits_str(str): Raw qubit specification segment from QCIS line. + + Returns: + list[Qubit]: Initialized Qubit objects in order of appearance. + """ + qubits = [] + for q_str in qubits_str.split(): + if q_str.startswith('Q'): + qubit = self._qubits.setdefault(q_str, Qubit(int(q_str[1:]))) + qubits.append(qubit) + else: + raise ValueError(f"Invalid qubit format: {q_str}") + return qubits + + def _process_instruction(self, gate: str, qubits: list, params: list): + """ + Core dispatcher for converting parsed components into circuit operations. - if not hasattr(gates, gate): - raise ValueError(f'Unknown gate: {gate}') + Args: + gate: Uppercase gate identifier (e.g., 'H', 'CX', 'RZ') + qubits: Qubit targets from prior parsing stage + params: Numerical parameters for parameterized gates + """ + if gate == 'M': + for qubit in qubits: + self.append_instruction_data( + InstructionData(instruction=Measure(), qubits=[qubit])) + elif gate == 'B': + self.append_instruction_data( + InstructionData(instruction=Barrier(len(qubits)), qubits=qubits)) + elif hasattr(gates, gate): data = InstructionData(instruction=getattr(gates, gate)(*params, label=None), qubits=qubits) - circuit.append_instruction_data(data) - - return circuit + self.append_instruction_data(data) + else: + raise ValueError(f"Unsupported gate: {gate}") def __str__(self): return self.as_str() -- Gitee From 79dc1b8f268b93391bb630856e78333dbf69da87 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 15:24:12 +0800 Subject: [PATCH 27/30] feat(pulse_circuit.py): Add QCIS string loading support and parse pulse instructions --- cqlib/circuits/instruction_data.py | 4 +- cqlib/pulse/base_pulse.py | 2 +- cqlib/pulse/pulse_circuit.py | 127 +++++++++++++++++++++++++++++ cqlib/pulse/pxy.py | 2 +- 4 files changed, 131 insertions(+), 4 deletions(-) diff --git a/cqlib/circuits/instruction_data.py b/cqlib/circuits/instruction_data.py index 0a87578..bf9804f 100644 --- a/cqlib/circuits/instruction_data.py +++ b/cqlib/circuits/instruction_data.py @@ -15,7 +15,7 @@ from typing import NamedTuple, Sequence -from cqlib.circuits.qubit import Qubit +from cqlib.circuits.bit import Bit from cqlib.circuits.instruction import Instruction @@ -25,7 +25,7 @@ class InstructionData(NamedTuple): instruction and the qubits it affects. """ instruction: Instruction - qubits: Sequence[Qubit] + qubits: Sequence[Bit] def __repr__(self): s = [self.instruction.name, " ".join(map(str, self.qubits))] diff --git a/cqlib/pulse/base_pulse.py b/cqlib/pulse/base_pulse.py index 61ecfbc..dda4e2e 100644 --- a/cqlib/pulse/base_pulse.py +++ b/cqlib/pulse/base_pulse.py @@ -37,7 +37,7 @@ class BasePulse(Instruction, ABC): Args: name (str): Pulse type identifier (e.g. PZ/PZ0/PXY/G) - params (WaveformParams): Waveform parameter container storing physical values. + waveform (Waveform): Waveform parameter container storing physical values. label (str): Optional operational label for experimental tracking """ self._waveform = waveform diff --git a/cqlib/pulse/pulse_circuit.py b/cqlib/pulse/pulse_circuit.py index f5f1bf8..c7dc5d4 100644 --- a/cqlib/pulse/pulse_circuit.py +++ b/cqlib/pulse/pulse_circuit.py @@ -15,6 +15,7 @@ A specialized quantum circuit class designed for pulse-level control of qubits and coupler qubits. """ +import re from collections.abc import Sequence from cqlib.circuits.circuit import Circuit, Qubits @@ -30,9 +31,12 @@ from .pxy import PXY from .pz import PZ from .pz0 import PZ0 from .waveform import Waveform +from .base_pulse import BasePulse CouplerQubits = int | CouplerQubit | Sequence[int | CouplerQubit] +PULSE_GATES = ['PZ', 'PZ0', 'PXY', 'G'] + class PulseCircuit(Circuit): """ @@ -195,3 +199,126 @@ class PulseCircuit(Circuit): raise CqlibError(f"QASM 2.0 export not supported for circuits with coupler qubits. " f"Found coupler(s): {self.coupler_qubits}") return super().to_qasm2() + + @classmethod + def load(cls, qcis: str) -> 'PulseCircuit': + """ + Loads quantum circuit with pulse-level instructions from QCIS string. + + Extends base Circuit functionality with support for: + - Coupler qubits (G-prefixed identifiers) + - Pulse waveforms (PXY, PZ, PZ0) + - Hybrid circuits mixing gates and pulses + + Args: + qcis: String containing hybrid quantum instructions. + + Returns: + PulseCircuit: Circuit with pulse capabilities initialized. + """ + circuit = cls(qubits=[], coupler_qubits=[]) + pattern = re.compile(r'^([A-Z][A-Z0-9]*)\s+((?:[QG][0-9]+\s*)+)' + r'((?:[+-]?(?:\d*\.\d+|\d+)(?:[Ee][+-]?\d+)?\s*)*)$') + for line in qcis.split('\n'): + line = re.sub(r'(#|//).*', '', line).strip() + if not line: + continue + match = pattern.match(line) + if not match: + raise ValueError(f'Invalid instruction format: {line}') + gate, qubits_str, params_str = match.groups() + qubits, coupler_qubits = cls._parse_pulse_qubits_str(circuit, qubits_str) + params = [float(p) for p in params_str.split()] if params_str else [] + if gate in PULSE_GATES: + cls._process_pulse_instruction(circuit, gate, qubits, coupler_qubits, params) + else: + cls._process_instruction(circuit, gate, qubits, params) + + return circuit + + def _parse_pulse_qubits_str( + self, + qubits_str: str + ) -> tuple[list[Qubit], list[CouplerQubit]]: + """ + Parses and categorizes qubit identifiers from QCIS instruction string. + + Processes space-separated qubit tokens distinguishing between: + - Standard qubits (Q-prefixed) + - Coupler qubits (G-prefixed) + + Args: + qubits_str: Raw qubit specification from QCIS instruction line. + + Returns: + tuple: Contains two lists respectively holding: + [0] Parsed standard Qubit objects + [1] Parsed CouplerQubit objects + """ + qubits, coupler_qubits = [], [] + for q_str in qubits_str.split(): + if q_str.startswith('Q'): + qubit = self._qubits.setdefault(q_str, Qubit(int(q_str[1:]))) + qubits.append(qubit) + elif q_str.startswith('G'): + coupler_qubit = self._coupler_qubits.setdefault(q_str, CouplerQubit(int(q_str[1:]))) + coupler_qubits.append(coupler_qubit) + else: + raise ValueError(f"Invalid qubit format: {q_str}") + return qubits, coupler_qubits + + def _process_pulse_instruction( + self, + gate: str, + qubits: list[Qubit], + coupler_qubits: list[CouplerQubit], + params: list[float | int] + ): + """ + Processes pulse-specific quantum instructions from parsed components. + + Handles three categories of pulse operations: + 1. G-type coupling pulses + 2. PXY parametric XY control pulses + 3. General pulse operations (PZ/PZ0 and dynamically resolved instructions) + + Args: + gate(str): Uppercase gate identifier (e.g., 'G', 'PXY', 'PZ') + qubits( list[Qubit]): Standard qubit targets parsed from instruction + coupler_qubits(list[CouplerQubit]): Coupler qubit targets parsed from instruction + params(list[float | int]): Numerical parameters following the qubit specification + """ + if gate == 'G': + if len(params) != 2: + raise CqlibError("G gate requires exactly 2 parameters (length, coupling_strength)") + length, coupling_strength = params + self._circuit_data.append( + InstructionData(instruction=G(length, coupling_strength), qubits=coupler_qubits) + ) + return + # Create waveform for pulse operations + waveform = Waveform.load(params, gate) + if gate == 'PXY': + if not qubits: + raise CqlibError("PXY pulse requires valid qubit targets") + self._circuit_data.append( + InstructionData(instruction=PXY(waveform), qubits=coupler_qubits) + ) + return + # For PZ/PZ0, validate mutual exclusivity of qubit types + if bool(qubits) == bool(coupler_qubits): + raise CqlibError("Must provide exactly one of qubits or coupler_qubits for PZ/PZ0") + qubits = qubits if qubits is not None else coupler_qubits + + # Dynamic gate resolution + try: + # pylint: disable=import-outside-toplevel, cyclic-import + from cqlib import pulse + instruction_class = getattr(pulse, gate) + except AttributeError as ex: + raise CqlibError(f"Unknown pulse instruction: {gate}") from ex + if not issubclass(instruction_class, BasePulse): + raise CqlibError(f"{gate} is not a valid pulse instruction") + self._circuit_data.append( + InstructionData(instruction=instruction_class(waveform=waveform), qubits=qubits) + ) diff --git a/cqlib/pulse/pxy.py b/cqlib/pulse/pxy.py index 9105968..e0944a0 100644 --- a/cqlib/pulse/pxy.py +++ b/cqlib/pulse/pxy.py @@ -44,5 +44,5 @@ class PXY(BasePulse): if waveform.phase is None or waveform.drag_alpha is None: raise CqlibError('PXY must have phase and drag_alpha params') if isinstance(waveform, NumericWaveform) \ - and (len(waveform.params) < 6 or len(waveform.params) % 2 == 1): + and (len(waveform.data_list) < 6 or len(waveform.data_list) % 2 == 1): raise CqlibError('The length of its list must >=6 and must be even.') -- Gitee From 8dc2ab192ff3c7213cd9ef447102c1395bcce836 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 16:37:33 +0800 Subject: [PATCH 28/30] fix(pulse): Ensure integer parameters for G gate and waveform --- cqlib/pulse/g.py | 1 + cqlib/pulse/pulse_circuit.py | 17 ++++++++++++++--- cqlib/pulse/waveform.py | 29 ++++++++++++++++++----------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/cqlib/pulse/g.py b/cqlib/pulse/g.py index 1a0652b..f0cb626 100644 --- a/cqlib/pulse/g.py +++ b/cqlib/pulse/g.py @@ -34,6 +34,7 @@ class G(BasePulse): self.length = length self.coupling_strength = coupling_strength super().__init__('G', waveform=None, label=label) + self.params = [length, coupling_strength] def validate(self): """ diff --git a/cqlib/pulse/pulse_circuit.py b/cqlib/pulse/pulse_circuit.py index c7dc5d4..02fbac5 100644 --- a/cqlib/pulse/pulse_circuit.py +++ b/cqlib/pulse/pulse_circuit.py @@ -292,8 +292,19 @@ class PulseCircuit(Circuit): if len(params) != 2: raise CqlibError("G gate requires exactly 2 parameters (length, coupling_strength)") length, coupling_strength = params + if length != int(length): + raise CqlibError( + f"G pulse length parameter must be integer value, " + f"received {type(length).__name__} {length}" + ) + if coupling_strength != int(coupling_strength): + raise CqlibError( + f"G pulse coupling_strength parameter requires integer value, " + f"got {type(coupling_strength).__name__} {coupling_strength}" + ) self._circuit_data.append( - InstructionData(instruction=G(length, coupling_strength), qubits=coupler_qubits) + InstructionData(instruction=G(int(length), int(coupling_strength)), + qubits=coupler_qubits) ) return # Create waveform for pulse operations @@ -302,13 +313,13 @@ class PulseCircuit(Circuit): if not qubits: raise CqlibError("PXY pulse requires valid qubit targets") self._circuit_data.append( - InstructionData(instruction=PXY(waveform), qubits=coupler_qubits) + InstructionData(instruction=PXY(waveform), qubits=qubits) ) return # For PZ/PZ0, validate mutual exclusivity of qubit types if bool(qubits) == bool(coupler_qubits): raise CqlibError("Must provide exactly one of qubits or coupler_qubits for PZ/PZ0") - qubits = qubits if qubits is not None else coupler_qubits + qubits = qubits if len(qubits) > 0 else coupler_qubits # Dynamic gate resolution try: diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py index 8a1f4f3..9ecd8f6 100644 --- a/cqlib/pulse/waveform.py +++ b/cqlib/pulse/waveform.py @@ -168,17 +168,21 @@ class Waveform: } if waveform_type == WaveformType.FLATTOP: - kwargs['edge'] = ps[0] - elif waveform_type == WaveformType.SLEPIAN: - thf, thi, lam2, lam3 = ps[:4] - kwargs.update({ - 'thf': thf, - 'thi': thi, - 'lam2': lam2, - 'lam3': lam3, - }) - elif waveform_type == WaveformType.NUMERIC: - kwargs['data_list'] = ps + edge = ps[0] + if edge != int(edge): + raise CqlibError('') + kwargs['edge'] = int(edge) + else: + ps = [int(p) if p == int(p) else p for p in ps] + if waveform_type == WaveformType.SLEPIAN: + kwargs.update({ + 'thf': ps[0], + 'thi': ps[1], + 'lam2': ps[2], + 'lam3': ps[3], + }) + elif waveform_type == WaveformType.NUMERIC: + kwargs['data_list'] = ps return Waveform.create(**kwargs) @@ -202,6 +206,9 @@ class FlattopWaveform(Waveform): def __post_init__(self): self.validate() + if self.edge < 0 or not isinstance(self.edge, int): + raise CqlibError(f"Flattop waveform edge parameter must be a positive integer, " + f"got {type(self.edge).__name__} {self.edge}") if 2 * self.edge >= self.length: raise CqlibError(f"Edge ({self.edge}) too large for length {self.length}") -- Gitee From 46b5623d20f204e81ada100a74a88cbe3bc2844f Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Tue, 25 Feb 2025 17:31:56 +0800 Subject: [PATCH 29/30] test(test_circuit.py,test_pulse_circuit.py): Add pulse circuit tests and improve error message for invalid circuit instruction --- tests/circuit/test_circuit.py | 3 +- tests/pulse/test_pulse_circuit.py | 58 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/pulse/test_pulse_circuit.py diff --git a/tests/circuit/test_circuit.py b/tests/circuit/test_circuit.py index 67096c5..205b1e4 100644 --- a/tests/circuit/test_circuit.py +++ b/tests/circuit/test_circuit.py @@ -257,8 +257,9 @@ def test_load_circuit(): def test_load_circuit_params(): """test load circuit with param, but will fail""" qcis = "H Q1\nRX Q0 theta\nRX Q1 2.2\nM Q0\nM Q1" - with pytest.raises(ValueError): + with pytest.raises(ValueError) as exec_info: Circuit.load(qcis) + assert 'Invalid instruction format: RX Q0 theta' in exec_info.value.args[0] def test_load_e(): diff --git a/tests/pulse/test_pulse_circuit.py b/tests/pulse/test_pulse_circuit.py new file mode 100644 index 0000000..ea0d4e4 --- /dev/null +++ b/tests/pulse/test_pulse_circuit.py @@ -0,0 +1,58 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" testcase for pulse circuit """ + +from cqlib.circuits.qubit import Qubit +from cqlib.pulse import PulseCircuit, CouplerQubit, Waveform, WaveformType + + +def test_coupler_qubit(): + """ test coupler qubits""" + cq = CouplerQubit(1) + assert cq.index == 1 + assert str(cq) == 'G1' + + +def test_pulse_circuit(): + """ test base pulse circuit """ + pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) + assert pc.qubits == [Qubit(0)] + assert pc.coupler_qubits == [CouplerQubit(0)] + + +def test_base_qcis(): + """ test base qcis """ + pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) + pc.h(0) + pc.measure(0) + assert pc.qcis == 'H Q0\nM Q0' + + pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) + waveform = Waveform.create(WaveformType.COSINE, length=100, amplitude=0.2, + phase=1.1, drag_alpha=0.1) + pc.pxy(0, waveform) + pc.pz(CouplerQubit(0), Waveform.create(WaveformType.COSINE, length=100, amplitude=0.2)) + pc.measure(0) + assert pc.qcis == 'PXY Q0 0 100 0.2 1.1 0.1\nPZ G0 0 100 0.2\nM Q0' + + +def test_load_pulse(): + """ test load pulse qcis """ + qcis = """PXY Q0 0 100 0.2 1.1 0.1 +PZ G0 0 100 0.2 +PZ0 Q1 1 100 0.2 10 +G G0 10 1000 +M Q0""" + qc = PulseCircuit.load(qcis) + assert qc.as_str() == qcis -- Gitee From 62926f61fd2e5b4b65e40ff777a9698b326ca857 Mon Sep 17 00:00:00 2001 From: Gao Jianjian Date: Thu, 27 Feb 2025 15:11:24 +0800 Subject: [PATCH 30/30] refactor(cqlib/visualization/circuit/text.py): Move BoxChar to base.py. Update the `BaseDrawer` class to use `Bit` instead of `Qubit` for qubit_order and related attributes. --- cqlib/visualization/circuit/base.py | 40 +++++++++++++++++++++++++---- cqlib/visualization/circuit/text.py | 31 +--------------------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/cqlib/visualization/circuit/base.py b/cqlib/visualization/circuit/base.py index e240af0..3a90389 100644 --- a/cqlib/visualization/circuit/base.py +++ b/cqlib/visualization/circuit/base.py @@ -17,9 +17,11 @@ Quantum circuit visualization import logging from abc import ABC, abstractmethod +from enum import Enum from typing import Iterator -from cqlib import Qubit +from cqlib.circuits.bit import Bit +from cqlib.circuits.qubit import Qubit from cqlib.circuits.circuit import Circuit from cqlib.circuits.dag import circuit_to_dag, topological_layers from cqlib.circuits.instruction_data import InstructionData @@ -28,16 +30,44 @@ from cqlib.exceptions import CqlibError logger = logging.getLogger('cqlib.vis') +class BoxChar(str, Enum): + """ + Unicode box-drawing characters collection for circuit visualization + """ + TOP = '╵' + BOTTOM = '╷' + LEFT = '╴' + RIGHT = '╶' + TOP_BOTTOM = '│' + LEFT_RIGHT = '─' + + TOP_LEFT = '┘' + TOP_RIGHT = '└' + BOTTOM_LEFT = '┐' + BOTTOM_RIGHT = '┌' + + TOP_BOTTOM_LEFT = '┤' + TOP_BOTTOM_RIGHT = '├' + TOP_LEFT_RIGHT = '┴' + BOTTOM_LEFT_RIGHT = '┬' + TOP_BOTTOM_LEFT_RIGHT = '┼' + + DOT = '■' + CONNECT = 'X' + LEFT_ARROW = '«' + RIGHT_ARROW = '»' + + class BaseDrawer(ABC): """ Abstract Quantum circuit Drawer. """ - def __init__(self, circuit: Circuit, qubit_order: list[int | Qubit] = None): + def __init__(self, circuit: Circuit, qubit_order: list[int | Bit] = None): self.circuit = circuit self.qubit_order = qubit_order - self.sorted_qubits = None - self.qubit_mapping = None + self.sorted_qubits: list[Bit] = [] + self.qubit_mapping: dict[Bit, int] = {} self.order_qubits() @@ -118,7 +148,7 @@ class BaseDrawer(ABC): moment_columns.append([qubit_range]) return [[qubit_min_to_ins[r[0]] for r in col] for col in moment_columns] - def qubit_line_no(self, qubit: Qubit) -> int: + def qubit_line_no(self, qubit: Bit) -> int: """ Retrieves the display line number for a given qubit in the visualization diff --git a/cqlib/visualization/circuit/text.py b/cqlib/visualization/circuit/text.py index 85bfb7c..5bbaeec 100644 --- a/cqlib/visualization/circuit/text.py +++ b/cqlib/visualization/circuit/text.py @@ -19,7 +19,6 @@ Provides ASCII/Unicode art representation of quantum circuits using box-drawing import copy import logging import shutil -from enum import Enum from cqlib.circuits.circuit import Circuit from cqlib.circuits.gates import SWAP, CZ @@ -28,39 +27,11 @@ from cqlib.circuits.gates.gate import ControlledGate from cqlib.circuits.instruction_data import InstructionData from cqlib.circuits.qubit import Qubit -from .base import BaseDrawer +from .base import BaseDrawer, BoxChar logger = logging.getLogger('cqlib.vis') -class BoxChar(str, Enum): - """ - Unicode box-drawing characters collection for circuit visualization - """ - TOP = '╵' - BOTTOM = '╷' - LEFT = '╴' - RIGHT = '╶' - TOP_BOTTOM = '│' - LEFT_RIGHT = '─' - - TOP_LEFT = '┘' - TOP_RIGHT = '└' - BOTTOM_LEFT = '┐' - BOTTOM_RIGHT = '┌' - - TOP_BOTTOM_LEFT = '┤' - TOP_BOTTOM_RIGHT = '├' - TOP_LEFT_RIGHT = '┴' - BOTTOM_LEFT_RIGHT = '┬' - TOP_BOTTOM_LEFT_RIGHT = '┼' - - DOT = '■' - CONNECT = 'X' - LEFT_ARROW = '«' - RIGHT_ARROW = '»' - - class TextDrawer(BaseDrawer): """ Renders quantum circuits as text diagrams using box-drawing characters -- Gitee