diff --git a/docs/_ext/qpy_version_history.py b/docs/_ext/qpy_version_history.py new file mode 100644 index 000000000000..3e064661620a --- /dev/null +++ b/docs/_ext/qpy_version_history.py @@ -0,0 +1,131 @@ +# This file is derived from Qiskit (https://github.com/Qiskit/qiskit-metapackage) +# +# Original source: +# https://github.com/Qiskit/qiskit-metapackage/blob/0.43.3/docs/versionutils.py +# +# Copyright IBM 2023 +# Licensed under the Apache License, Version 2.0 + +import re +from docutils import nodes +from docutils.parsers.rst import Directive +from docutils.statemachine import ViewList +from sphinx.util.nodes import nested_parse_with_titles +from dulwich.repo import Repo +from packaging import version +from pathlib import Path + +from qiskit.qpy.common import QPY_VERSION_HISTORY + + +def setup(app): + app.add_directive("qpy-version-history", QPYVersionHistory) + +class QPYVersionHistory(Directive): + has_content = False + + def run(self): + data = self._build_data() + if not data: + return [nodes.paragraph(text="QPY data not found")] + return [self._build_table(data)] + + def _get_repo(self): + path = Path(__file__).resolve() + for parent in path.parents: + if (parent / ".git").exists(): + return Repo(str(parent)) + raise RuntimeError("Repository not found") + + def _get_tags(self): + repo = self._get_repo() + tags = [] + + for ref in repo.refs.keys(): + if ref.startswith(b"refs/tags/"): + tag = ref.decode().split("/")[-1] + tags.append(tag) + + return tags + + def _is_valid_version(self, tag): + return re.fullmatch(r"\d+\.\d+\.\d+", tag) + + def _build_data(self): + tags = self._get_tags() + versions = sorted((version.parse(t) for t in tags if self._is_valid_version(t))) + history = sorted( + [(max_, min_, version.parse(first)) for max_, min_, first in QPY_VERSION_HISTORY], + key=lambda x: x[2] + ) + + def get_qpy_range(cur): + for max_, min_, first in reversed(history): + if cur >= first: + return list(range(min_, max_ + 1)) + return None + + data = {} + for v in versions: + qpy_range = get_qpy_range(v) + if not qpy_range: + continue + + data[str(v)] = { + "dump": qpy_range, + "load": max(qpy_range), + } + + return dict(sorted(data.items(), key=lambda x: version.parse(x[0]), reverse=True)) + + def _build_table(self, data): + table = nodes.table() + table["classes"] += ["colwidths-auto"] + tgroup = nodes.tgroup(cols=3) + table += tgroup + + for _ in range(3): + tgroup += nodes.colspec() + + thead = nodes.thead() + tbody = nodes.tbody() + tgroup += thead + tgroup += tbody + + headers = [ + "Qiskit (qiskit-terra for < 1.0.0) version", + ":func:`.dump` format(s) output versions", + ":func:`.load` maximum supported version (older format versions can always be read)", + ] + + row = nodes.row() + for h in headers: + entry = nodes.entry() + + vl = ViewList() + vl.append(h, "") + + node = nodes.paragraph() + nested_parse_with_titles(self.state, vl, node) + + entry += node + row += entry + + thead += row + + for tag, info in data.items(): + row = nodes.row() + entry = nodes.entry() + entry += nodes.paragraph(text=tag) + row += entry + entry = nodes.entry() + entry += nodes.paragraph( + text=", ".join(map(str, info["dump"])) + ) + row += entry + entry = nodes.entry() + entry += nodes.paragraph(text=str(info["load"])) + row += entry + tbody += row + + return table \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index f9ffbe92a6d9..278eda08d6a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,9 +19,9 @@ import os import re from pathlib import Path - +import sys import qiskit - +sys.path.insert(0, os.path.abspath("./_ext")) # pylint: disable=invalid-name,missing-function-docstring,missing-module-docstring @@ -50,6 +50,7 @@ "reno.sphinxext", "sphinxcontrib.katex", "breathe", + "qpy_version_history", ] breathe_projects = {"qiskit": "xml/"} diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index 3224a5ca2ac6..b31d7cdde26d 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -193,201 +193,7 @@ def open(*args): Qiskit (and qiskit-terra prior to Qiskit 1.0.0) release going back to the introduction of QPY in qiskit-terra 0.18.0. -.. list-table:: QPY Format Version History - :header-rows: 1 - - * - Qiskit (qiskit-terra for < 1.0.0) version - - :func:`.dump` format(s) output versions - - :func:`.load` maximum supported version (older format versions can always be read) - * - 2.4.1 - - 13, 14, 15, 16, 17 - - 17 - * - 2.4.0 - - 13, 14, 15, 16, 17 - - 17 - * - 2.3.1 - - 13, 14, 15, 16, 17 - - 17 - * - 2.3.0 - - 13, 14, 15, 16, 17 - - 17 - * - 2.2.2 - - 13, 14, 15, 16 - - 16 - * - 2.2.1 - - 13, 14, 15, 16 - - 16 - * - 2.2.0 - - 13, 14, 15, 16 - - 16 - * - 2.1.2 - - 13, 14, 15, 16 - - 16 - * - 2.1.1 - - 13, 14, 15, 16 - - 16 - * - 2.1.0 - - 13, 14, 15 - - 15 - * - 2.0.2 - - 13, 14 - - 14 - * - 2.0.1 - - 13, 14 - - 14 - * - 2.0.0 - - 13, 14 - - 14 - * - 1.4.3 - - 10, 11, 12, 13 - - 13 - * - 1.4.2 - - 10, 11, 12, 13 - - 13 - * - 1.4.1 - - 10, 11, 12, 13 - - 13 - * - 1.4.0 - - 10, 11, 12, 13 - - 13 - * - 1.3.3 - - 10, 11, 12, 13 - - 13 - * - 1.3.2 - - 10, 11, 12, 13 - - 13 - * - 1.3.1 - - 10, 11, 12, 13 - - 13 - * - 1.3.0 - - 10, 11, 12, 13 - - 13 - * - 1.2.4 - - 10, 11, 12 - - 12 - * - 1.2.3 (yanked) - - 10, 11, 12 - - 12 - * - 1.2.2 - - 10, 11, 12 - - 12 - * - 1.2.1 - - 10, 11, 12 - - 12 - * - 1.2.0 - - 10, 11, 12 - - 12 - * - 1.1.0 - - 10, 11, 12 - - 12 - * - 1.0.2 - - 10, 11 - - 11 - * - 1.0.1 - - 10, 11 - - 11 - * - 1.0.0 - - 10, 11 - - 11 - * - 0.46.1 - - 10 - - 10 - * - 0.45.3 - - 10 - - 10 - * - 0.45.2 - - 10 - - 10 - * - 0.45.1 - - 10 - - 10 - * - 0.45.0 - - 10 - - 10 - * - 0.25.3 - - 9 - - 9 - * - 0.25.2 - - 9 - - 9 - * - 0.25.1 - - 9 - - 9 - * - 0.24.2 - - 8 - - 8 - * - 0.24.1 - - 7 - - 7 - * - 0.24.0 - - 7 - - 7 - * - 0.23.3 - - 6 - - 6 - * - 0.23.2 - - 6 - - 6 - * - 0.23.1 - - 6 - - 6 - * - 0.23.0 - - 6 - - 6 - * - 0.22.4 - - 5 - - 5 - * - 0.22.3 - - 5 - - 5 - * - 0.22.2 - - 5 - - 5 - * - 0.22.1 - - 5 - - 5 - * - 0.22.0 - - 5 - - 5 - * - 0.21.2 - - 5 - - 5 - * - 0.21.1 - - 5 - - 5 - * - 0.21.0 - - 5 - - 5 - * - 0.20.2 - - 4 - - 4 - * - 0.20.1 - - 4 - - 4 - * - 0.20.0 - - 4 - - 4 - * - 0.19.2 - - 4 - - 4 - * - 0.19.1 - - 3 - - 3 - * - 0.19.0 - - 2 - - 2 - * - 0.18.3 - - 1 - - 1 - * - 0.18.2 - - 1 - - 1 - * - 0.18.1 - - 1 - - 1 - * - 0.18.0 - - 1 - - 1 +.. qpy-version-history:: .. _qpy_format: diff --git a/qiskit/qpy/common.py b/qiskit/qpy/common.py index 5c96b10a5349..6ef07a47c240 100644 --- a/qiskit/qpy/common.py +++ b/qiskit/qpy/common.py @@ -29,6 +29,28 @@ QPY_RUST_WRITE_MIN_VERSION = 17 ENCODE = "utf8" +# only update the points at which either of the two QPY constants change (QPY_VERSION, QPY_COMPATIBILITY_VERSION) +QPY_VERSION_HISTORY = [ + # (max_qpy_version, min_qpy_version, release_introduced) + (17, 13, "2.3.0"), + (16, 13, "2.2.0"), + (15, 13, "2.1.0"), + (14, 13, "2.0.0"), + (13, 10, "1.3.0"), + (12, 10, "1.1.0"), + (11, 10, "1.0.0"), + (10, 10, "0.45.0"), + (9, 9, "0.25.0"), + (8, 8, "0.24.2"), + (7, 7, "0.24.0"), + (6, 6, "0.23.0"), + (5, 5, "0.21.0"), + (4, 4, "0.19.2"), + (3, 3, "0.19.1"), + (2, 2, "0.19.0"), + (1, 1, "0.18.0"), +] + def read_generic_typed_data(file_obj): """Read a single data chunk from the file like object.