From 3d7685b7983926e79d19cdb65547196a1f54034b Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:14:27 +0000 Subject: [PATCH] Update `docmain` to align with `main` (#1688) --- .github/workflows/build-without-conan.yml | 6 +- .github/workflows/build_and_test.yml | 2 +- .github/workflows/lint.yml | 8 +- .github/workflows/pytket_benchmarking.yml | 10 +- build-without-conan.md | 6 +- pytket/binders/circuit/Circuit/add_op.cpp | 47 +- pytket/binders/circuit/Circuit/main.cpp | 20 +- pytket/binders/circuit/clexpr.cpp | 4 +- pytket/binders/circuit/main.cpp | 4 +- pytket/binders/passes.cpp | 83 ++- pytket/binders/transform.cpp | 21 +- pytket/conanfile.py | 6 +- pytket/docs/README.md | 22 +- pytket/docs/changelog.rst | 38 ++ pytket/docs/check_circuit_class_docs.py | 2 +- pytket/pytket/__init__.py | 12 +- pytket/pytket/_tket/circuit.pyi | 28 +- pytket/pytket/_tket/passes.pyi | 36 +- pytket/pytket/_tket/transform.pyi | 6 +- pytket/pytket/backends/__init__.py | 2 +- pytket/pytket/backends/backend.py | 80 ++- pytket/pytket/backends/backend_exceptions.py | 11 +- pytket/pytket/backends/backendinfo.py | 88 ++-- pytket/pytket/backends/backendresult.py | 133 +++-- pytket/pytket/backends/resulthandle.py | 14 +- pytket/pytket/backends/status.py | 31 +- pytket/pytket/circuit/__init__.py | 29 +- pytket/pytket/circuit/add_condition.py | 30 +- pytket/pytket/circuit/clexpr.py | 43 +- pytket/pytket/circuit/decompose_classical.py | 218 ++++++-- pytket/pytket/circuit/display/__init__.py | 9 +- pytket/pytket/circuit/logic_exp.py | 122 ++--- pytket/pytket/circuit/named_types.py | 5 +- pytket/pytket/circuit_library/__init__.py | 152 +++--- pytket/pytket/config/__init__.py | 2 +- pytket/pytket/config/pytket_config.py | 24 +- pytket/pytket/passes/__init__.py | 2 +- pytket/pytket/passes/auto_rebase.py | 10 +- pytket/pytket/passes/passselector.py | 10 +- pytket/pytket/passes/resizeregpass.py | 3 +- pytket/pytket/passes/script.py | 113 +++-- pytket/pytket/qasm/__init__.py | 8 +- pytket/pytket/qasm/grammar.py | 2 +- pytket/pytket/qasm/qasm.py | 478 ++++++++++-------- pytket/pytket/quipper/quipper.py | 134 ++--- pytket/pytket/transform/__init__.py | 2 +- pytket/pytket/unit_id/__init__.py | 17 +- pytket/pytket/utils/__init__.py | 34 +- pytket/pytket/utils/distribution.py | 38 +- pytket/pytket/utils/expectations.py | 134 +++-- pytket/pytket/utils/graph.py | 9 +- pytket/pytket/utils/measurements.py | 6 +- pytket/pytket/utils/operators.py | 56 +- pytket/pytket/utils/outcomearray.py | 16 +- pytket/pytket/utils/prepare.py | 5 +- pytket/pytket/utils/results.py | 36 +- pytket/pytket/utils/spam.py | 76 ++- pytket/pytket/utils/stats.py | 3 +- pytket/pytket/utils/symbolic.py | 27 +- pytket/pytket/utils/term_sequence.py | 9 +- pytket/pytket/wasm/wasm.py | 24 +- pytket/pytket/zx/tensor_eval.py | 56 +- pytket/ruff.toml | 81 +++ pytket/setup.py | 30 +- pytket/tests/add_circuit_test.py | 1 - pytket/tests/ansatz_sequence_test.py | 17 +- .../architecture_aware_synthesis_test.py | 2 +- pytket/tests/architecture_test.py | 18 +- pytket/tests/assertion_test.py | 10 +- pytket/tests/backend_test.py | 30 +- pytket/tests/backendinfo_test.py | 23 +- pytket/tests/boxes/phase_poly_box_test.py | 20 +- pytket/tests/characterisation_test.py | 4 +- pytket/tests/circuit_test.py | 147 +++--- pytket/tests/classical_test.py | 274 ++-------- pytket/tests/clexpr_test.py | 52 +- pytket/tests/compilation_test.py | 16 +- pytket/tests/distribution_test.py | 8 +- pytket/tests/logic_exp_test.py | 14 +- pytket/tests/mapping_test.py | 27 +- pytket/tests/measurement_setup_test.py | 10 +- pytket/tests/mitigation_test.py | 29 +- pytket/tests/passes_script_test.py | 10 +- pytket/tests/passes_serialisation_test.py | 68 ++- pytket/tests/placement_test.py | 31 +- pytket/tests/predicates_test.py | 177 ++++--- pytket/tests/pytket_config_test.py | 21 +- pytket/tests/qasm_test.py | 175 +++++-- pytket/tests/qasm_test_files/longreg.qasm | 83 +++ .../tests/qasm_test_files/test18_output.qasm | 16 +- pytket/tests/qubitpaulioperator_test.py | 16 +- pytket/tests/quipper_test.py | 8 +- pytket/tests/simulator/__init__.py | 2 +- pytket/tests/simulator/tket_sim_backend.py | 61 ++- pytket/tests/simulator/tket_sim_wrapper.py | 6 +- pytket/tests/strategies.py | 23 +- pytket/tests/tableau_test.py | 6 +- pytket/tests/tket_sim_test.py | 16 +- pytket/tests/transform_test.py | 83 ++- pytket/tests/unit_id/copy_test.py | 2 +- pytket/tests/utils_test.py | 42 +- pytket/tests/zx_diagram_test.py | 39 +- schemas/circuit_v1.json | 158 ++++++ schemas/compiler_pass_v1.json | 24 +- tket/conanfile.py | 4 +- tket/include/tket/Circuit/Boxes.hpp | 4 + tket/include/tket/Circuit/Circuit.hpp | 19 +- tket/include/tket/Ops/ClExpr.hpp | 3 +- tket/include/tket/Predicates/CompilerPass.hpp | 10 +- .../tket/Predicates/PassGenerators.hpp | 10 +- .../GreedyPauliOptimisation.hpp | 28 +- tket/src/Circuit/Boxes.cpp | 16 + tket/src/Circuit/CircPool.cpp | 12 - tket/src/Circuit/Circuit.cpp | 2 +- tket/src/Circuit/basic_circ_manip.cpp | 4 +- tket/src/Circuit/macro_circ_info.cpp | 10 +- tket/src/OpType/OpTypeFunctions.cpp | 7 +- tket/src/Ops/ClExpr.cpp | 7 +- tket/src/Predicates/CompilerPass.cpp | 51 +- tket/src/Predicates/PassGenerators.cpp | 42 +- .../GreedyPauliOptimisation.cpp | 127 ++++- tket/test/src/Circuit/test_Circ.cpp | 50 ++ tket/test/src/test_ClExpr.cpp | 11 +- tket/test/src/test_GreedyPauli.cpp | 113 ++++- tket/test/src/test_json.cpp | 48 +- 125 files changed, 3093 insertions(+), 2027 deletions(-) mode change 100755 => 100644 pytket/pytket/__init__.py create mode 100644 pytket/ruff.toml mode change 100755 => 100644 pytket/setup.py create mode 100644 pytket/tests/qasm_test_files/longreg.qasm diff --git a/.github/workflows/build-without-conan.yml b/.github/workflows/build-without-conan.yml index c31470cc7a..9544d442a3 100644 --- a/.github/workflows/build-without-conan.yml +++ b/.github/workflows/build-without-conan.yml @@ -40,9 +40,9 @@ jobs: - name: Install symengine run: | cd ${TMP_DIR} - wget https://github.com/symengine/symengine/archive/refs/tags/v0.12.0.tar.gz - tar xzvf v0.12.0.tar.gz - cd symengine-0.12.0/ + wget https://github.com/symengine/symengine/archive/refs/tags/v0.13.0.tar.gz + tar xzvf v0.13.0.tar.gz + cd symengine-0.13.0/ mkdir build cd build cmake -GNinja -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DBUILD_TESTS=OFF -DBUILD_BENCHMARKS=OFF .. diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5ada774af1..a870b45a61 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -428,7 +428,7 @@ jobs: python -m pip install -U mypy pybind11-stubgen cd pytket ./stub_generation/regenerate_stubs - git diff --quiet pytket/_tket && echo "Stubs are up-to-date" || exit 1 # fail if stubs change after regeneration + git diff pytket/_tket && echo "Stubs are up-to-date" || exit 1 # fail if stubs change after regeneration python -m mypy --config-file=mypy.ini --no-incremental -p pytket -p tests - name: Upload package if: github.event_name == 'push' && needs.check_changes.outputs.tket_changed == 'true' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2d2c8f7225..15f6514f5c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,8 +15,8 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.x' - - name: Install black and pylint - run: pip install black pylint + - name: Install black, pylint and ruff + run: pip install black pylint ruff - name: Check files are formatted with black run: | # Paths specified to avoid formatting submodules @@ -25,3 +25,7 @@ jobs: run: | cd pytket pylint --ignore-paths=pytket/_tket pytket/ + - name: ruff check + run: | + cd pytket + ruff check . diff --git a/.github/workflows/pytket_benchmarking.yml b/.github/workflows/pytket_benchmarking.yml index 001418b4ac..c9c9293cbf 100644 --- a/.github/workflows/pytket_benchmarking.yml +++ b/.github/workflows/pytket_benchmarking.yml @@ -100,10 +100,12 @@ jobs: - name: Post to a Slack channel id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2.0.0 with: - channel-id: 'G01CP0YFFA7' - slack-message: '${{ env.RETURN_TEST }} Release tag: ${{ github.event.release.tag_name }}.' + method: chat.postMessage + token: ${{ secrets.PYTKET_BENCHMARKING_SLACK_BOT_TOKEN }} + payload: | + channel: 'G01CP0YFFA7' + text: '${{ env.RETURN_TEST }} Release tag: ${{ github.event.release.tag_name }}.' env: - SLACK_BOT_TOKEN: ${{ secrets.PYTKET_BENCHMARKING_SLACK_BOT_TOKEN }} RETURN_TEST: ${{ env.RETURN_TEST }} diff --git a/build-without-conan.md b/build-without-conan.md index 9a91f1f50f..4663f65ed1 100644 --- a/build-without-conan.md +++ b/build-without-conan.md @@ -48,9 +48,9 @@ make install ``` cd ${TMP_DIR} -wget https://github.com/symengine/symengine/archive/refs/tags/v0.12.0.tar.gz -tar xzvf v0.12.0.tar.gz -cd symengine-0.12.0/ +wget https://github.com/symengine/symengine/archive/refs/tags/v0.13.0.tar.gz +tar xzvf v0.13.0.tar.gz +cd symengine-0.13.0/ mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DBUILD_TESTS=OFF -DBUILD_BENCHMARKS=OFF .. diff --git a/pytket/binders/circuit/Circuit/add_op.cpp b/pytket/binders/circuit/Circuit/add_op.cpp index c8c952d2f3..07cccadb2a 100644 --- a/pytket/binders/circuit/Circuit/add_op.cpp +++ b/pytket/binders/circuit/Circuit/add_op.cpp @@ -414,6 +414,11 @@ void init_circuit_add_op(py::class_> &c) { [](Circuit *circ, const py::tket_custom::BitLogicExpression &exp, const py::tket_custom::SequenceVec &outputs, const py::kwargs &kwargs) { + PyErr_WarnEx( + PyExc_DeprecationWarning, + "The add_classicalexpbox_bit method is deprecated. Please use " + "Circuit::add_clexpr() instead.", + 1); auto inputs = exp.attr("all_inputs")().cast>(); py::tket_custom::SequenceVec o_vec, io_vec; @@ -439,7 +444,9 @@ void init_circuit_add_op(py::class_> &c) { n_i, n_io, n_o, exp), o_vec, kwargs); }, - "Append a :py:class:`ClassicalExpBox` over Bit to the circuit.\n\n" + "Append a :py:class:`ClassicalExpBox` over Bit to the circuit.\n" + "DEPRECATED: Please use :py:meth:`add_clexpr` instead. This method " + "will be removed after pytket 1.40.\n\n" ":param classicalexpbox: The box to append\n" ":param args: Indices of the qubits to append the box to" "\n:return: the new :py:class:`Circuit`", @@ -450,6 +457,11 @@ void init_circuit_add_op(py::class_> &c) { const py::tket_custom::BitRegisterLogicExpression &exp, const py::tket_custom::SequenceVec &outputs, const py::kwargs &kwargs) { + PyErr_WarnEx( + PyExc_DeprecationWarning, + "The add_classicalexpbox_register method is deprecated. Please " + "use Circuit::add_clexpr() instead.", + 1); auto inputs = exp.attr("all_inputs")().cast>(); std::set all_bits; @@ -481,7 +493,9 @@ void init_circuit_add_op(py::class_> &c) { o_vec, kwargs); }, "Append a :py:class:`ClassicalExpBox` over BitRegister to the " - "circuit.\n\n" + "circuit.\n" + "DEPRECATED: Please use :py:meth:`add_clexpr` instead. This method " + "will be removed after pytket 1.40.\n\n" ":param classicalexpbox: The box to append\n" ":param args: Indices of the qubits to append the box to" "\n:return: the new :py:class:`Circuit`", @@ -499,6 +513,35 @@ void init_circuit_add_op(py::class_> &c) { ":param args: The bits to apply the expression to\n" ":return: the new :py:class:`Circuit`", py::arg("expr"), py::arg("args")) + .def( + "add_clexpr_from_logicexp", + [](Circuit *circ, const py::tket_custom::LogicExpression &exp, + const py::tket_custom::SequenceVec &output_bits, + const py::kwargs &kwargs) { + py::list outputs; + for (const auto &bit : output_bits) { + outputs.append(bit); + } + py::module clexpr = py::module::import("pytket.circuit.clexpr"); + py::object add_op = + clexpr.attr("_add_clexpr_to_circuit_from_logicexp"); + add_op(circ, exp, outputs, **kwargs); + return circ; + }, + "Append a :py:class:`ClExprOp` defined in terms of a logical " + "expression.\n\n" + "Example:\n" + ">>> c = Circuit()\n" + ">>> x_reg = c.add_c_register('x', 3)\n" + ">>> y_reg = c.add_c_register('y', 3)\n" + ">>> z_reg = c.add_c_register('z', 3)\n" + ">>> c.add_clexpr_from_logicexp(x_reg | y_reg, z_reg.to_list())\n" + ">>> [ClExpr x[0], x[1], x[2], y[0], y[1], y[2], z[0], z[1], z[2]; " + "]\n\n" + ":param exp: logical expression\n" + ":param output_bits: list of bits in output\n" + ":return: the updated circuit", + py::arg("exp"), py::arg("output_bits")) .def( "add_custom_gate", [](Circuit *circ, const composite_def_ptr_t &definition, diff --git a/pytket/binders/circuit/Circuit/main.cpp b/pytket/binders/circuit/Circuit/main.cpp index 867996beb1..38c6f7c2bb 100644 --- a/pytket/binders/circuit/Circuit/main.cpp +++ b/pytket/binders/circuit/Circuit/main.cpp @@ -322,7 +322,12 @@ void def_circuit(py::class_> &pyCircuit) { .def( "flatten_registers", &Circuit::flatten_registers, "Combines all qubits into a single register namespace with " - "the default name, and likewise for bits") + "the default name, and likewise for bits" + "\n\n:param relabel_classical_expression: Determines whether python " + "classical expressions held in `ClassicalExpBox` have their " + "expression " + "relabelled to match relabelled Bit.", + py::arg("relabel_classical_expression") = true) // Circuit composition: .def( @@ -472,15 +477,18 @@ void def_circuit(py::class_> &pyCircuit) { "\n\n:return: the circuit depth") .def( "n_gates_of_type", - [](const Circuit &circ, const OpType &_type) { - return circ.count_gates(_type); + [](const Circuit &circ, const OpType &_type, + const bool include_conditional) { + return circ.count_gates(_type, include_conditional); }, "Returns the number of vertices in the dag of a given " "operation type.\n\n>>> c.CX(0,1)\n>>> c.H(0)\n>>> " "c.CX(0,1)\n>>> c.n_gates_of_type(OpType.CX)\n2\n\n:param " - "type: The operation type to search for\n:return: the " - "number of operations matching `type`", - py::arg("type")) + "type: The operation type to search for\n\n:param " + "include_conditional: if set to true, conditional gates will " + "be counted, too\n\n:return: the number of operations " + "matching `type`", + py::arg("type"), py::arg("include_conditional") = false) .def( "n_1qb_gates", [](const Circuit &circ) { return circ.count_n_qubit_gates(1); }, diff --git a/pytket/binders/circuit/clexpr.cpp b/pytket/binders/circuit/clexpr.cpp index fa2f73c227..a0589cb393 100644 --- a/pytket/binders/circuit/clexpr.cpp +++ b/pytket/binders/circuit/clexpr.cpp @@ -34,7 +34,7 @@ namespace tket { static std::string qasm_bit_repr( const ClExprTerm &term, const std::map &input_bits) { - if (const int *n = std::get_if(&term)) { + if (const uint64_t *n = std::get_if(&term)) { switch (*n) { case 0: return "0"; @@ -56,7 +56,7 @@ static std::string qasm_bit_repr( static std::string qasm_reg_repr( const ClExprTerm &term, const std::map &input_regs) { - if (const int *n = std::get_if(&term)) { + if (const uint64_t *n = std::get_if(&term)) { std::stringstream ss; ss << *n; return ss.str(); diff --git a/pytket/binders/circuit/main.cpp b/pytket/binders/circuit/main.cpp index ba4d2853f3..cf32612f4d 100644 --- a/pytket/binders/circuit/main.cpp +++ b/pytket/binders/circuit/main.cpp @@ -525,7 +525,9 @@ PYBIND11_MODULE(circuit, m) { "A classical operation applied to multiple bits simultaneously") .value( "ClassicalExpBox", OpType::ClassicalExpBox, - "A box for holding compound classical operations on Bits.") + "A box for holding compound classical operations on Bits.\n" + "DEPRECATED: Please use :py:class:`WiredClExpr` instead. This class " + "will be removed after pytket 1.40.") .value( "MultiplexorBox", OpType::MultiplexorBox, "A multiplexor (i.e. uniformly controlled operations)") diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index d47181072f..ef398f8a0d 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -111,7 +111,7 @@ static PassPtr gen_default_aas_routing_pass( const PassPtr &DecomposeClassicalExp() { // a special box decomposer for Circuits containing - // ClassicalExpBox + // ClassicalExpBox and ClExprOp static const PassPtr pp([]() { Transform t = Transform([](Circuit &circ) { py::module decomposer = @@ -265,23 +265,35 @@ PYBIND11_MODULE(passes, m) { .def( "to_dict", [](const BasePass &base_pass) { - return py::object(base_pass.get_config()).cast(); + return py::cast(serialise(base_pass)); }, ":return: A JSON serializable dictionary representation of the Pass.") .def_static( "from_dict", - [](const py::dict &base_pass_dict) { - return json(base_pass_dict).get(); + [](const py::dict &base_pass_dict, + + std::map> + &custom_deserialisation) { + return deserialise(base_pass_dict, custom_deserialisation); }, "Construct a new Pass instance from a JSON serializable dictionary " - "representation.") + "representation. `custom_deserialisation` is a map between " + "`CustomPass` " + "label attributes and a Circuit to Circuit function matching the " + "`CustomPass` `transform` argument. This allows the construction of " + "some `CustomPass` from JSON. `CustomPass` without a matching entry " + "in " + "`custom_deserialisation` will be rejected.", + py::arg("base_pass_dict"), + py::arg("custom_deserialisation") = + std::map>{}) .def(py::pickle( [](py::object self) { // __getstate__ return py::make_tuple(self.attr("to_dict")()); }, [](const py::tuple &t) { // __setstate__ const json j = t[0].cast(); - return j.get(); + return deserialise(j); })); py::class_, BasePass>( m, "SequencePass", "A sequence of compilation passes.") @@ -296,9 +308,18 @@ PYBIND11_MODULE(passes, m) { "\n:return: a pass that applies the sequence", py::arg("pass_list"), py::arg("strict") = true) .def("__str__", [](const BasePass &) { return ""; }) + .def( + "to_dict", + [](const SequencePass &seq_pass) { + return py::cast( + serialise(std::make_shared(seq_pass))); + }, + ":return: A JSON serializable dictionary representation of the " + "SequencePass.") .def( "get_sequence", &SequencePass::get_sequence, ":return: The underlying sequence of passes."); + py::class_, BasePass>( m, "RepeatPass", "Repeat a pass until its `apply()` method returns False, or if " @@ -462,13 +483,22 @@ PYBIND11_MODULE(passes, m) { py::arg("excluded_opgroups") = std::unordered_set()); m.def( "DecomposeClassicalExp", &DecomposeClassicalExp, - "Replaces each :py:class:`ClassicalExpBox` by a sequence of " - "classical gates."); + "Replaces each :py:class:`ClassicalExpBox` and `ClExprOp` by a sequence " + "of classical gates."); m.def( "DecomposeMultiQubitsCX", &DecomposeMultiQubitsCX, "Converts all multi-qubit gates into CX and single-qubit gates."); m.def( - "GlobalisePhasedX", &GlobalisePhasedX, + "GlobalisePhasedX", + [](bool squash) { + PyErr_WarnEx( + PyExc_DeprecationWarning, + "The GlobalisePhasedX pass is unreliable and deprecated. It will " + "be removed no earlier that three months after the pytket 1.35 " + "release.", + 1); + return GlobalisePhasedX(squash); + }, "Turns all PhasedX and NPhasedX gates into global gates\n\n" "Replaces any PhasedX gates with global NPhasedX gates. " "By default, this transform will squash all single-qubit gates " @@ -478,6 +508,8 @@ PYBIND11_MODULE(passes, m) { "performance. If squashing is disabled, each non-global PhasedX gate " "will be replaced with two global NPhasedX, but any other gates will " "be left untouched." + "\n\nDEPRECATED: This pass will be removed no earlier than three months " + "after the pytket 1.35 release." "\n\n:param squash: Whether to squash the circuit in pre-processing " "(default: true)." "\n\nIf squash=true (default), the `GlobalisePhasedX` transform's " @@ -508,7 +540,7 @@ PYBIND11_MODULE(passes, m) { m.def( "RebaseTket", &RebaseTket, "Converts all gates to CX, TK1 and Phase. " - "(Any Measure, Reset and Collapse operations are left untouched; " + "(Any Measure and Reset operations are left untouched; " "Conditional gates are also allowed.)"); m.def( "RemoveRedundancies", &RemoveRedundancies, @@ -618,7 +650,7 @@ PYBIND11_MODULE(passes, m) { ":math:`(a,b,c)` to generate replacement circuits." "\n\n" ":param gateset: the allowed operations in the rebased circuit " - "(in addition, Measure, Reset and Collapse operations are always allowed " + "(in addition, Measure and Reset operations are always allowed " "and are left alone; conditional operations may be present; and Phase " "gates may also be introduced by the rebase)" "\n:param cx_replacement: the equivalent circuit to replace a CX gate " @@ -628,8 +660,7 @@ PYBIND11_MODULE(passes, m) { "Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired " "basis" "\n:return: a pass that rebases to the given gate set (possibly " - "including conditional and phase operations, and Measure, Reset and " - "Collapse)", + "including conditional and phase operations, and Measure and Reset", py::arg("gateset"), py::arg("cx_replacement"), py::arg("tk1_replacement")); @@ -647,7 +678,7 @@ PYBIND11_MODULE(passes, m) { "to each TK1(a,b,c)." "\n\n" ":param gateset: the allowed operations in the rebased circuit " - "(in addition, Measure, Reset and Collapse operations are always allowed " + "(in addition, Measure and Reset always allowed " "and are left alone; conditional operations may be present; and Phase " "gates may also be introduced by the rebase)\n" ":param tk2_replacement: a function which, given the parameters (a,b,c) " @@ -657,7 +688,7 @@ PYBIND11_MODULE(passes, m) { "of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the " "desired basis\n" ":return: a pass that rebases to the given gate set (possibly including " - "conditional and phase operations, and Measure, Reset and Collapse)", + "conditional and phase operations, and Measure and Reset)", py::arg("gateset"), py::arg("tk2_replacement"), py::arg("tk1_replacement")); m.def( @@ -669,7 +700,7 @@ PYBIND11_MODULE(passes, m) { "Raises an error if no known decompositions can be found, in which case " "try using :py:class:`RebaseCustom` with your own decompositions.\n\n" ":param gateset: Set of supported OpTypes, target gate set. " - "(in addition, Measure, Reset and Collapse operations are always allowed " + "(in addition, Measure and Reset operations are always allowed " "and are left alone; conditional operations may be present; and Phase " "gates may also be introduced by the rebase)\n" ":param allow_swaps: Whether to allow implicit wire swaps. Default to " @@ -731,9 +762,12 @@ PYBIND11_MODULE(passes, m) { "FlattenRelabelRegistersPass", &gen_flatten_relabel_registers_pass, "Removes empty Quantum wires from the Circuit and relabels all Qubit to " "a register from passed name. \n\n:param label: Name to relabel " - "remaining Qubit to, default 'q'.\n:return: A pass that removes empty " + "remaining Qubit to, default 'q'.\n:param relabel_classical_expressions: " + "Whether to relabel arguments of expressions held in `ClassicalExpBox`. " + "\n:return: A pass that removes empty " "wires and relabels.", - py::arg("label") = q_default_reg()); + py::arg("label") = q_default_reg(), + py::arg("relabel_classical_expressions") = true); m.def( "RenameQubitsPass", &gen_rename_qubits_pass, @@ -936,10 +970,19 @@ PYBIND11_MODULE(passes, m) { "\n:param allow_zzphase: If set to True, allows the algorithm to " "implement 2-qubit rotations using ZZPhase gates when deemed " "optimal. Defaults to False." + "\n:param thread_timeout: Sets maximum out of time spent finding a " + "single solution in one thread." + "\n:param only_reduce: Only returns modified circuit if it has " + "fewer two-qubit gates." + "\n:param trials: Sets maximum number of found solutions. The " + "smallest circuit is returned, prioritising the number of 2qb-gates, " + "then the number of gates, then the depth." "\n:return: a pass to perform the simplification", py::arg("discount_rate") = 0.7, py::arg("depth_weight") = 0.3, py::arg("max_lookahead") = 500, py::arg("max_tqe_candidates") = 500, - py::arg("seed") = 0, py::arg("allow_zzphase") = false); + py::arg("seed") = 0, py::arg("allow_zzphase") = false, + py::arg("thread_timeout") = 100, py::arg("only_reduce") = false, + py::arg("trials") = 1); m.def( "PauliSquash", &PauliSquash, "Applies :py:meth:`PauliSimp` followed by " @@ -1012,7 +1055,7 @@ PYBIND11_MODULE(passes, m) { m.def( "CnXPairwiseDecomposition", &CnXPairwiseDecomposition, - "Decompose CnX gates to 2-qubit gates and single qubit gates. " + "Decompose CnX gates to 2-qubit gates `fand single qubit gates. " "For every two CnX gates, reorder their control qubits to improve " "the chance of gate cancellation"); diff --git a/pytket/binders/transform.cpp b/pytket/binders/transform.cpp index 21724759c3..286fc6062f 100644 --- a/pytket/binders/transform.cpp +++ b/pytket/binders/transform.cpp @@ -386,7 +386,16 @@ PYBIND11_MODULE(transform, m) { "DecomposeNPhasedX", &Transforms::decompose_NPhasedX, "Decompose NPhasedX gates into single-qubit PhasedX gates.") .def_static( - "GlobalisePhasedX", &Transforms::globalise_PhasedX, + "GlobalisePhasedX", + [](bool squash) { + PyErr_WarnEx( + PyExc_DeprecationWarning, + "The GlobalisePhasedX transform is unreliable and deprecated. " + "It will be removed no earlier than three months after the " + "pytket 1.35 release.", + 1); + return Transforms::globalise_PhasedX(squash); + }, "Turns all PhasedX and NPhasedX gates into global gates\n\n" "Replaces any PhasedX gates with global NPhasedX gates. " "By default, this transform will squash all single-qubit gates " @@ -396,6 +405,8 @@ PYBIND11_MODULE(transform, m) { "performance. If squashing is disabled, each non-global PhasedX gate " "will be replaced with two global NPhasedX, but any other gates will " "be left untouched." + "\n\nDEPRECATED: This transform will be removed no earlier than " + "three months after the pytket 1.35 release." "\n\n:param squash: Whether to squash the circuit in pre-processing " "(default: true)." "\n\nIf squash=true (default), the `GlobalisePhasedX` transform's " @@ -440,10 +451,16 @@ PYBIND11_MODULE(transform, m) { "\n:param allow_zzphase: If set to True, allows the algorithm to " "implement 2-qubit rotations using ZZPhase gates when deemed " "optimal. Defaults to False." + "\n:param thread_timeout: Sets maximum out of time spent finding a " + "single solution in one thread." + "\n:param trials: Sets maximum number of found solutions. The " + "smallest circuit is returned, prioritising the number of 2qb-gates, " + "then the number of gates, then the depth." "\n:return: a pass to perform the simplification", py::arg("discount_rate") = 0.7, py::arg("depth_weight") = 0.3, py::arg("max_tqe_candidates") = 500, py::arg("max_lookahead") = 500, - py::arg("seed") = 0, py::arg("allow_zzphase") = false) + py::arg("seed") = 0, py::arg("allow_zzphase") = false, + py::arg("thread_timeout") = 100, py::arg("trials") = 1) .def_static( "ZZPhaseToRz", &Transforms::ZZPhase_to_Rz, "Fixes all ZZPhase gate angles to [-1, 1) half turns.") diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 80e7e0b31c..123ac8339a 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -1,5 +1,5 @@ from conan import ConanFile -from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout class pytketRecipe(ConanFile): @@ -36,9 +36,9 @@ def requirements(self): self.requires("nlohmann_json/3.11.3") self.requires("pybind11/2.13.6") self.requires("pybind11_json/0.2.14") - self.requires("symengine/0.12.0") + self.requires("symengine/0.13.0") self.requires("tkassert/0.3.4@tket/stable") - self.requires("tket/1.3.35@tket/stable") + self.requires("tket/1.3.48@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tktokenswap/0.3.9@tket/stable") diff --git a/pytket/docs/README.md b/pytket/docs/README.md index c4e8ae83db..4924fd60af 100644 --- a/pytket/docs/README.md +++ b/pytket/docs/README.md @@ -3,12 +3,10 @@ Pytket Docs # Clone repository ``` -git@github.com:CQCL/tket.git +git clone git@github.com:CQCL/tket.git --recurse-submodules ``` -``` -git submodule update --recursive --init -``` +# Move to docs directory ``` cd pytket/docs @@ -19,13 +17,23 @@ cd pytket/docs ``` poetry install ``` + +# Install pytket + +The pytket package is not installed as a poetry dependency so needs to be installed seperately + +``` +poetry run pip install -U pytket +``` +You can install a pypi version as above or an editable wheel. + # Build html ``` -poetry run make html +poetry run bash ./build-docs.sh ``` -# Serve build +# Serve built html locally ``` -npx serve ./build/html +npx serve build ``` diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 143d5592c8..bd7a814086 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,44 @@ Changelog ========= +1.35.0 (November 2024) +---------------------- + +Features: + +* Add `clexpr.check_register_alignments()` method to check register alignments + in `ClExprOp`. +* Use `ClExprOp` instead of `ClassicalExpBox` when deconstructing complex + conditions. +* Add `custom_deserialisation` argument to `BasePass` and `SequencePass` + `from_dict` method to support construction of `CustomPass` from json. +* Add `thread_timeout`, `only_reduce`, and `trials` arguments + to `GreedyPauliSimp`. +* Add option to not relabel `ClassicalExpBox` when calling `rename_units` + and `flatten_registers` +* Implement `dagger()` and `transpose()` for `CustomGate`. +* Use `ClExprOp` by default when converting from QASM. +* Extend `DecomposeClassicalExp` to handle `ClExprOp` as well as + `ClassicalExpBox`. +* Add convenience method `Circuit.add_clecpr_from_logicexp()`. +* Remove `OpType::Collapse` from the `GateSetPredicate` of `gen_auto_rebase_pass`. + +Deprecations: + +* Deprecate `ClassicalExpBox` and related methods, in favour of `ClExprOp`. +* Deprecate `GlobalisePhasedX` pass and transform. + +Fixes: + +* Fix `symbol_substitution` not preserving opgroups. +* Remove hardware inefficient circuit construction in `_tk1_to_rzsx` +* Support converting conditional `RangePredicate`s to QASM. +* Fix `maxwidth` parameter of `circuit_from_qasm_str` +* Add `scratch_reg_resize_pass` to `circuit_from_qasm_str` +* Reject incompete classical registers in pytket to qasm conversion +* Add parameter `include_conditional` to `n_gates_of_type` to include + conditional gates in the count + 1.34.0 (October 2024) --------------------- diff --git a/pytket/docs/check_circuit_class_docs.py b/pytket/docs/check_circuit_class_docs.py index 816bc4c35d..90b728bc54 100644 --- a/pytket/docs/check_circuit_class_docs.py +++ b/pytket/docs/check_circuit_class_docs.py @@ -6,7 +6,7 @@ # This script is used to check that no methods or properties of # the Circuit class are left out in the circuit_class.rst file. -with open("circuit_class.rst", "r", encoding="utf-8") as file: +with open("circuit_class.rst", encoding="utf-8") as file: rst = file.read() in_methods = {p[0] for p in inspect.getmembers(Circuit)} diff --git a/pytket/pytket/__init__.py b/pytket/pytket/__init__.py old mode 100755 new mode 100644 index 8b4b5fee3a..341ed1fd2f --- a/pytket/pytket/__init__.py +++ b/pytket/pytket/__init__.py @@ -14,20 +14,20 @@ """Python Interface to tket """ +import pytket.architecture # noqa: I001 from pytket.circuit import ( Circuit, OpType, ) -from pytket.unit_id import ( - Qubit, - Bit, -) import pytket.mapping -import pytket.architecture import pytket.placement import pytket.transform -from pytket.config import PytketConfig, get_config_file_path from pytket._version import __version__ +from pytket.config import PytketConfig, get_config_file_path +from pytket.unit_id import ( + Bit, + Qubit, +) # Create pytket config file if it does not exist: pytket_config_file = get_config_file_path() diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index 4cbc158def..6105ee6703 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -1438,6 +1438,7 @@ class Circuit: def add_classicalexpbox_bit(self, expression: pytket.circuit.logic_exp.BitLogicExp, target: typing.Sequence[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: """ Append a :py:class:`ClassicalExpBox` over Bit to the circuit. + DEPRECATED: Please use :py:meth:`add_clexpr` instead. This method will be removed after pytket 1.40. :param classicalexpbox: The box to append :param args: Indices of the qubits to append the box to @@ -1446,6 +1447,7 @@ class Circuit: def add_classicalexpbox_register(self, expression: pytket.circuit.logic_exp.RegLogicExp, target: typing.Sequence[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: """ Append a :py:class:`ClassicalExpBox` over BitRegister to the circuit. + DEPRECATED: Please use :py:meth:`add_clexpr` instead. This method will be removed after pytket 1.40. :param classicalexpbox: The box to append :param args: Indices of the qubits to append the box to @@ -1459,6 +1461,22 @@ class Circuit: :param args: The bits to apply the expression to :return: the new :py:class:`Circuit` """ + def add_clexpr_from_logicexp(self, exp: pytket.circuit.logic_exp.LogicExp, output_bits: typing.Sequence[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: + """ + Append a :py:class:`ClExprOp` defined in terms of a logical expression. + + Example: + >>> c = Circuit() + >>> x_reg = c.add_c_register('x', 3) + >>> y_reg = c.add_c_register('y', 3) + >>> z_reg = c.add_c_register('z', 3) + >>> c.add_clexpr_from_logicexp(x_reg | y_reg, z_reg.to_list()) + >>> [ClExpr x[0], x[1], x[2], y[0], y[1], y[2], z[0], z[1], z[2]; ] + + :param exp: logical expression + :param output_bits: list of bits in output + :return: the updated circuit + """ @typing.overload def add_conditional_barrier(self, barrier_qubits: typing.Sequence[int], barrier_bits: typing.Sequence[int], condition_bits: typing.Sequence[int], value: int, data: str = '') -> Circuit: """ @@ -2104,9 +2122,11 @@ class Circuit: :param types: the set of operation types of interest :return: the circuit depth with respect to operations matching an element of `types` """ - def flatten_registers(self) -> dict[pytket._tket.unit_id.UnitID, pytket._tket.unit_id.UnitID]: + def flatten_registers(self, relabel_classical_expression: bool = True) -> dict[pytket._tket.unit_id.UnitID, pytket._tket.unit_id.UnitID]: """ Combines all qubits into a single register namespace with the default name, and likewise for bits + + :param relabel_classical_expression: Determines whether python classical expressions held in `ClassicalExpBox` have their expression relabelled to match relabelled Bit. """ def free_symbols(self) -> set[sympy.Symbol]: """ @@ -2235,7 +2255,7 @@ class Circuit: """ Returns the number of vertices in the dag with two quantum edges.Ignores Input, Create, Output, Discard, Reset, Measure and Barrier vertices. """ - def n_gates_of_type(self, type: OpType) -> int: + def n_gates_of_type(self, type: OpType, include_conditional: bool = False) -> int: """ Returns the number of vertices in the dag of a given operation type. @@ -2246,6 +2266,9 @@ class Circuit: 2 :param type: The operation type to search for + + :param include_conditional: if set to true, conditional gates will be counted, too + :return: the number of operations matching `type` """ def n_nqb_gates(self, size: int) -> int: @@ -3614,6 +3637,7 @@ class OpType: MultiBit : A classical operation applied to multiple bits simultaneously ClassicalExpBox : A box for holding compound classical operations on Bits. + DEPRECATED: Please use :py:class:`WiredClExpr` instead. This class will be removed after pytket 1.40. MultiplexorBox : A multiplexor (i.e. uniformly controlled operations) diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index efc45514eb..9a59fae388 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -18,9 +18,9 @@ class BasePass: def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore ... @staticmethod - def from_dict(arg0: dict) -> BasePass: + def from_dict(base_pass_dict: dict, custom_deserialisation: dict[str, typing.Callable[[pytket._tket.circuit.Circuit], pytket._tket.circuit.Circuit]] = {}) -> BasePass: """ - Construct a new Pass instance from a JSON serializable dictionary representation. + Construct a new Pass instance from a JSON serializable dictionary representation. `custom_deserialisation` is a map between `CustomPass` label attributes and a Circuit to Circuit function matching the `CustomPass` `transform` argument. This allows the construction of some `CustomPass` from JSON. `CustomPass` without a matching entry in `custom_deserialisation` will be rejected. """ def __getstate__(self) -> tuple: ... @@ -54,7 +54,7 @@ class BasePass: :param after_apply: Invoked after a pass is applied. The CompilationUnit and a summary of the pass configuration are passed into the callback. :return: True if pass modified the circuit, else False """ - def to_dict(self) -> dict: + def to_dict(self) -> typing.Any: """ :return: A JSON serializable dictionary representation of the Pass. """ @@ -227,6 +227,10 @@ class SequencePass(BasePass): """ :return: The underlying sequence of passes. """ + def to_dict(self) -> typing.Any: + """ + :return: A JSON serializable dictionary representation of the SequencePass. + """ def AASRouting(arc: pytket._tket.architecture.Architecture, **kwargs: Any) -> BasePass: """ Construct a pass to relabel :py:class:`Circuit` Qubits to :py:class:`Device` Nodes, and then use architecture-aware synthesis to route the circuit. In the steps of the pass the circuit will be converted to CX, Rz, H gateset. The limited connectivity of the :py:class:`Architecture` is used for the routing. The direction of the edges is ignored. The placement used is GraphPlacement. This pass can take a few parameters for the routing, described below: @@ -245,7 +249,7 @@ def AutoRebase(gateset: set[pytket._tket.circuit.OpType], allow_swaps: bool = Fa Attempt to generate a rebase pass automatically for the given target gateset. Checks if there are known existing decompositions to target gateset and TK1 to target gateset and uses those to construct a custom rebase. Raises an error if no known decompositions can be found, in which case try using :py:class:`RebaseCustom` with your own decompositions. - :param gateset: Set of supported OpTypes, target gate set. (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) + :param gateset: Set of supported OpTypes, target gate set. (in addition, Measure and Reset operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) :param allow_swaps: Whether to allow implicit wire swaps. Default to False. """ def AutoSquash(singleqs: set[pytket._tket.circuit.OpType]) -> BasePass: @@ -286,7 +290,7 @@ def CliffordSimp(allow_swaps: bool = True) -> BasePass: """ def CnXPairwiseDecomposition() -> BasePass: """ - Decompose CnX gates to 2-qubit gates and single qubit gates. For every two CnX gates, reorder their control qubits to improve the chance of gate cancellation + Decompose CnX gates to 2-qubit gates `fand single qubit gates. For every two CnX gates, reorder their control qubits to improve the chance of gate cancellation """ def CommuteThroughMultis() -> BasePass: """ @@ -338,7 +342,7 @@ def DecomposeBoxes(excluded_types: set[pytket._tket.circuit.OpType] = set(), exc """ def DecomposeClassicalExp() -> BasePass: """ - Replaces each :py:class:`ClassicalExpBox` by a sequence of classical gates. + Replaces each :py:class:`ClassicalExpBox` and `ClExprOp` by a sequence of classical gates. """ def DecomposeMultiQubitsCX() -> BasePass: """ @@ -406,11 +410,12 @@ def FlattenRegisters() -> BasePass: """ Merges all quantum and classical registers into their respective default registers with contiguous indexing. """ -def FlattenRelabelRegistersPass(label: str = 'q') -> BasePass: +def FlattenRelabelRegistersPass(label: str = 'q', relabel_classical_expressions: bool = True) -> BasePass: """ Removes empty Quantum wires from the Circuit and relabels all Qubit to a register from passed name. :param label: Name to relabel remaining Qubit to, default 'q'. + :param relabel_classical_expressions: Whether to relabel arguments of expressions held in `ClassicalExpBox`. :return: A pass that removes empty wires and relabels. """ def FullMappingPass(arc: pytket._tket.architecture.Architecture, placer: pytket._tket.placement.Placement, config: typing.Sequence[pytket._tket.mapping.RoutingMethod]) -> BasePass: @@ -434,13 +439,15 @@ def GlobalisePhasedX(squash: bool = True) -> BasePass: Replaces any PhasedX gates with global NPhasedX gates. By default, this transform will squash all single-qubit gates to PhasedX and Rz gates before proceeding further. Existing non-global NPhasedX will not be preserved. This is the recommended setting for best performance. If squashing is disabled, each non-global PhasedX gate will be replaced with two global NPhasedX, but any other gates will be left untouched. + DEPRECATED: This pass will be removed no earlier than three months after the pytket 1.35 release. + :param squash: Whether to squash the circuit in pre-processing (default: true). If squash=true (default), the `GlobalisePhasedX` transform's `apply` method will always return true. For squash=false, `apply()` will return true if the circuit was changed and false otherwise. It is not recommended to use this pass with symbolic expressions, as in certain cases a blow-up in symbolic expression sizes may occur. """ -def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_lookahead: int = 500, max_tqe_candidates: int = 500, seed: int = 0, allow_zzphase: bool = False) -> BasePass: +def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_lookahead: int = 500, max_tqe_candidates: int = 500, seed: int = 0, allow_zzphase: bool = False, thread_timeout: int = 100, only_reduce: bool = False, trials: int = 1) -> BasePass: """ Construct a pass that converts a circuit into a graph of Pauli gadgets to account for commutation and phase folding, and resynthesises them using a greedy algorithm adapted from arxiv.org/abs/2103.08602. The method for synthesising the final Clifford operator is adapted from arxiv.org/abs/2305.10966. @@ -450,6 +457,9 @@ def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_l :param max_lookahead: Maximum lookahead when evaluating each Clifford gate candidate. Default to 500. :param seed: Unsigned integer seed used for sampling candidates and tie breaking. Default to 0. :param allow_zzphase: If set to True, allows the algorithm to implement 2-qubit rotations using ZZPhase gates when deemed optimal. Defaults to False. + :param thread_timeout: Sets maximum out of time spent finding a single solution in one thread. + :param only_reduce: Only returns modified circuit if it has fewer two-qubit gates. + :param trials: Sets maximum number of found solutions. The smallest circuit is returned, prioritising the number of 2qb-gates, then the number of gates, then the depth. :return: a pass to perform the simplification """ def GuidedPauliSimp(strat: pytket._tket.transform.PauliSynthStrat = pytket._tket.transform.PauliSynthStrat.Sets, cx_config: pytket._tket.circuit.CXConfigType = pytket._tket.circuit.CXConfigType.Snake) -> BasePass: @@ -555,10 +565,10 @@ def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], cx_replacement: pytk 3. converts any single-qubit gates not in the gate type set to the form :math:`\\mathrm{Rz}(a)\\mathrm{Rx}(b)\\mathrm{Rz}(c)` (in matrix-multiplication order, i.e. reverse order in the circuit); 4. applies the `tk1_replacement` function to each of these triples :math:`(a,b,c)` to generate replacement circuits. - :param gateset: the allowed operations in the rebased circuit (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) + :param gateset: the allowed operations in the rebased circuit (in addition, Measure and Reset operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) :param cx_replacement: the equivalent circuit to replace a CX gate using two qubit gates from the desired basis (can use any single qubit OpTypes) :param tk1_replacement: a function which, given the parameters of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired basis - :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure, Reset and Collapse) + :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure and Reset """ @typing.overload def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], tk2_replacement: typing.Callable[[sympy.Expr | float, sympy.Expr | float, sympy.Expr | float], pytket._tket.circuit.Circuit], tk1_replacement: typing.Callable[[sympy.Expr | float, sympy.Expr | float, sympy.Expr | float], pytket._tket.circuit.Circuit]) -> BasePass: @@ -570,14 +580,14 @@ def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], tk2_replacement: typ 3. converts any single-qubit gates not in the gate type set to TK1; 4. if TK2 is not in `gateset`. applies the `tk1_replacement` function to each TK1(a,b,c). - :param gateset: the allowed operations in the rebased circuit (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) + :param gateset: the allowed operations in the rebased circuit (in addition, Measure and Reset always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) :param tk2_replacement: a function which, given the parameters (a,b,c) of an XXPhase(a)YYPhase(b)ZZPhase(c) triple, returns an equivalent circuit in the desired basis :param tk1_replacement: a function which, given the parameters (a,b,c) of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired basis - :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure, Reset and Collapse) + :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure and Reset) """ def RebaseTket() -> BasePass: """ - Converts all gates to CX, TK1 and Phase. (Any Measure, Reset and Collapse operations are left untouched; Conditional gates are also allowed.) + Converts all gates to CX, TK1 and Phase. (Any Measure and Reset operations are left untouched; Conditional gates are also allowed.) """ def RemoveBarriers() -> BasePass: """ diff --git a/pytket/pytket/_tket/transform.pyi b/pytket/pytket/_tket/transform.pyi index 4801b4238c..4b137ff45f 100644 --- a/pytket/pytket/_tket/transform.pyi +++ b/pytket/pytket/_tket/transform.pyi @@ -158,6 +158,8 @@ class Transform: Replaces any PhasedX gates with global NPhasedX gates. By default, this transform will squash all single-qubit gates to PhasedX and Rz gates before proceeding further. Existing non-global NPhasedX will not be preserved. This is the recommended setting for best performance. If squashing is disabled, each non-global PhasedX gate will be replaced with two global NPhasedX, but any other gates will be left untouched. + DEPRECATED: This transform will be removed no earlier than three months after the pytket 1.35 release. + :param squash: Whether to squash the circuit in pre-processing (default: true). If squash=true (default), the `GlobalisePhasedX` transform's `apply` method will always return true. For squash=false, `apply()` will return true if the circuit was changed and false otherwise. @@ -165,7 +167,7 @@ class Transform: It is not recommended to use this transformation with symbolic expressions, as in certain cases a blow-up in symbolic expression sizes may occur. """ @staticmethod - def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_tqe_candidates: int = 500, max_lookahead: int = 500, seed: int = 0, allow_zzphase: bool = False) -> Transform: + def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_tqe_candidates: int = 500, max_lookahead: int = 500, seed: int = 0, allow_zzphase: bool = False, thread_timeout: int = 100, trials: int = 1) -> Transform: """ Convert a circuit into a graph of Pauli gadgets to account for commutation and phase folding, and resynthesises them using a greedy algorithm adapted from arxiv.org/abs/2103.08602. The method for synthesising the final Clifford operator is adapted from arxiv.org/abs/2305.10966. @@ -175,6 +177,8 @@ class Transform: :param max_lookahead: Maximum lookahead when evaluating each Clifford gate candidate. Default to 500. :param seed: Unsigned integer seed used for sampling candidates and tie breaking. Default to 0. :param allow_zzphase: If set to True, allows the algorithm to implement 2-qubit rotations using ZZPhase gates when deemed optimal. Defaults to False. + :param thread_timeout: Sets maximum out of time spent finding a single solution in one thread. + :param trials: Sets maximum number of found solutions. The smallest circuit is returned, prioritising the number of 2qb-gates, then the number of gates, then the depth. :return: a pass to perform the simplification """ @staticmethod diff --git a/pytket/pytket/backends/__init__.py b/pytket/pytket/backends/__init__.py index 1f838b2eab..f124c3f914 100644 --- a/pytket/pytket/backends/__init__.py +++ b/pytket/pytket/backends/__init__.py @@ -17,6 +17,6 @@ __path__ = __import__("pkgutil").extend_path(__path__, __name__) from .backend import Backend +from .backend_exceptions import CircuitNotRunError, CircuitNotValidError from .resulthandle import ResultHandle from .status import CircuitStatus, StatusEnum -from .backend_exceptions import CircuitNotRunError, CircuitNotValidError diff --git a/pytket/pytket/backends/backend.py b/pytket/pytket/backends/backend.py index 39ac667f1b..92d703972e 100644 --- a/pytket/pytket/backends/backend.py +++ b/pytket/pytket/backends/backend.py @@ -15,40 +15,29 @@ """ Abstract base class for all Backend encapsulations.""" import warnings from abc import ABC, abstractmethod -from typing import ( - Dict, - Iterable, - List, - Optional, - Sequence, - Union, - Any, - cast, - overload, -) +from collections.abc import Iterable, Sequence from importlib import import_module from types import ModuleType - -from typing_extensions import Literal +from typing import Any, Literal, cast, overload from pytket.circuit import Bit, Circuit, OpType from pytket.passes import BasePass from pytket.pauli import QubitPauliString from pytket.predicates import Predicate +from pytket.utils import QubitPauliOperator from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import KwargTypes -from pytket.utils import QubitPauliOperator from .backend_exceptions import ( - CircuitNotValidError, CircuitNotRunError, + CircuitNotValidError, ) from .backendinfo import BackendInfo from .backendresult import BackendResult from .resulthandle import ResultHandle, _ResultIdTuple from .status import CircuitStatus -ResultCache = Dict[str, Any] +ResultCache = dict[str, Any] class ResultHandleTypeError(Exception): @@ -73,7 +62,7 @@ class Backend(ABC): _persistent_handles = False def __init__(self) -> None: - self._cache: Dict[ResultHandle, ResultCache] = {} + self._cache: dict[ResultHandle, ResultCache] = {} @staticmethod def empty_result(circuit: Circuit, n_shots: int) -> BackendResult: @@ -85,7 +74,7 @@ def empty_result(circuit: Circuit, n_shots: int) -> BackendResult: @property @abstractmethod - def required_predicates(self) -> List[Predicate]: + def required_predicates(self) -> list[Predicate]: """ The minimum set of predicates that a circuit must satisfy before it can be successfully run on this backend. @@ -107,7 +96,7 @@ def valid_circuit(self, circuit: Circuit) -> bool: return all(pred.verify(circuit) for pred in self.required_predicates) def _check_all_circuits( - self, circuits: Iterable[Circuit], nomeasure_warn: Optional[bool] = None + self, circuits: Iterable[Circuit], nomeasure_warn: bool | None = None ) -> bool: if nomeasure_warn is None: nomeasure_warn = not ( @@ -185,7 +174,7 @@ def get_compiled_circuit( def get_compiled_circuits( self, circuits: Sequence[Circuit], optimisation_level: int = 2 - ) -> List[Circuit]: + ) -> list[Circuit]: """Compile a sequence of circuits with :py:meth:`default_compilation_pass` and return the list of compiled circuits (does not act in place). @@ -237,15 +226,14 @@ def _check_handle_type(self, reshandle: ResultHandle) -> None: isinstance(idval, ty) for idval, ty in zip(reshandle, self._result_id_type) ): raise ResultHandleTypeError( - "{0!r} does not match expected identifier types {1}".format( - reshandle, self._result_id_type - ) + f"{reshandle!r} does not match expected " + f"identifier types {self._result_id_type}" ) def process_circuit( self, circuit: Circuit, - n_shots: Optional[int] = None, + n_shots: int | None = None, valid_check: bool = True, **kwargs: KwargTypes, ) -> ResultHandle: @@ -262,10 +250,10 @@ def process_circuit( def process_circuits( self, circuits: Sequence[Circuit], - n_shots: Optional[Union[int, Sequence[int]]] = None, + n_shots: int | Sequence[int] | None = None, valid_check: bool = True, **kwargs: KwargTypes, - ) -> List[ResultHandle]: + ) -> list[ResultHandle]: """ Submit circuits to the backend for running. The results will be stored in the backend's result cache to be retrieved by the corresponding @@ -316,7 +304,7 @@ def empty_cache(self) -> None: """Manually empty the result cache on the backend.""" self._cache = {} - def pop_result(self, handle: ResultHandle) -> Optional[ResultCache]: + def pop_result(self, handle: ResultHandle) -> ResultCache | None: """Remove cache entry corresponding to handle from the cache and return. :param handle: ResultHandle object @@ -348,7 +336,7 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul def get_results( self, handles: Iterable[ResultHandle], **kwargs: KwargTypes - ) -> List[BackendResult]: + ) -> list[BackendResult]: """Return results corresponding to handles. :param handles: Iterable of handles @@ -372,7 +360,7 @@ def get_results( def run_circuit( self, circuit: Circuit, - n_shots: Optional[int] = None, + n_shots: int | None = None, valid_check: bool = True, **kwargs: KwargTypes, ) -> BackendResult: @@ -396,10 +384,10 @@ def run_circuit( def run_circuits( self, circuits: Sequence[Circuit], - n_shots: Optional[Union[int, Sequence[int]]] = None, + n_shots: int | Sequence[int] | None = None, valid_check: bool = True, **kwargs: KwargTypes, - ) -> List[BackendResult]: + ) -> list[BackendResult]: """ Submits circuits to the backend and returns results @@ -430,7 +418,7 @@ def cancel(self, handle: ResultHandle) -> None: raise NotImplementedError("Backend does not support job cancellation.") @property - def backend_info(self) -> Optional[BackendInfo]: + def backend_info(self) -> BackendInfo | None: """Retrieve all Backend properties in a BackendInfo object, including device architecture, supported gate set, gate errors and other hardware-specific information. @@ -441,7 +429,7 @@ def backend_info(self) -> Optional[BackendInfo]: raise NotImplementedError("Backend does not provide any device properties.") @classmethod - def available_devices(cls, **kwargs: Any) -> List[BackendInfo]: + def available_devices(cls, **kwargs: Any) -> list[BackendInfo]: """Retrieve all available devices as a list of BackendInfo objects, including device name, architecture, supported gate set, gate errors, and other hardware-specific information. @@ -516,7 +504,7 @@ def supports_contextual_optimisation(self) -> bool: See :py:meth:`process_circuits`.""" return self._supports_contextual_optimisation - def _get_extension_module(self) -> Optional[ModuleType]: + def _get_extension_module(self) -> ModuleType | None: """Return the extension module of the backend if it belongs to a pytket-extension package. @@ -530,7 +518,7 @@ def _get_extension_module(self) -> Optional[ModuleType]: return import_module(".".join(mod_parts)) @property - def __extension_name__(self) -> Optional[str]: + def __extension_name__(self) -> str | None: """Retrieve the extension name of the backend if it belongs to a pytket-extension package. @@ -544,7 +532,7 @@ def __extension_name__(self) -> Optional[str]: return None @property - def __extension_version__(self) -> Optional[str]: + def __extension_version__(self) -> str | None: """Retrieve the extension version of the backend if it belongs to a pytket-extension package. @@ -560,36 +548,36 @@ def __extension_version__(self) -> Optional[str]: @overload @staticmethod def _get_n_shots_as_list( - n_shots: Union[None, int, Sequence[Optional[int]]], + n_shots: None | int | Sequence[int | None], n_circuits: int, optional: Literal[False], - ) -> List[int]: ... + ) -> list[int]: ... @overload @staticmethod def _get_n_shots_as_list( - n_shots: Union[None, int, Sequence[Optional[int]]], + n_shots: None | int | Sequence[int | None], n_circuits: int, optional: Literal[True], set_zero: Literal[True], - ) -> List[int]: ... + ) -> list[int]: ... @overload @staticmethod def _get_n_shots_as_list( - n_shots: Union[None, int, Sequence[Optional[int]]], + n_shots: None | int | Sequence[int | None], n_circuits: int, optional: bool = True, set_zero: bool = False, - ) -> Union[List[Optional[int]], List[int]]: ... + ) -> list[int | None] | list[int]: ... @staticmethod def _get_n_shots_as_list( - n_shots: Union[None, int, Sequence[Optional[int]]], + n_shots: None | int | Sequence[int | None], n_circuits: int, optional: bool = True, set_zero: bool = False, - ) -> Union[List[Optional[int]], List[int]]: + ) -> list[int | None] | list[int]: """ Convert any admissible n_shots value into List[Optional[int]] format. @@ -609,9 +597,9 @@ def _get_n_shots_as_list( :return: a list of length `n_circuits`, the converted argument """ - n_shots_list: List[Optional[int]] = [] + n_shots_list: list[int | None] = [] - def validate_n_shots(n: Optional[int]) -> bool: + def validate_n_shots(n: int | None) -> bool: return optional or (n is not None and n > 0) if set_zero and not optional: diff --git a/pytket/pytket/backends/backend_exceptions.py b/pytket/pytket/backends/backend_exceptions.py index cf872edd1f..ddae83e519 100644 --- a/pytket/pytket/backends/backend_exceptions.py +++ b/pytket/pytket/backends/backend_exceptions.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Optional from .resulthandle import ResultHandle class CircuitNotValidError(Exception): """Raised when a submitted circuit does not satisfy all predicates""" - def __init__(self, message: Union[str, int], failed_pred: Optional[str] = None): + def __init__(self, message: str | int, failed_pred: str | None = None): if isinstance(message, int): message = ( "Circuit with index {0} in submitted does not satisfy " @@ -34,8 +33,8 @@ class CircuitNotRunError(Exception): def __init__(self, handle: ResultHandle): super().__init__( - "Circuit corresponding to {0!r} ".format(handle) - + "has not been run by this backend instance." + f"Circuit corresponding to {handle!r} " + "has not been run by this backend instance." ) @@ -43,6 +42,4 @@ class InvalidResultType(Exception): """Raised when a BackendResult instance cannot produce the required result type.""" def __init__(self, result_type: str): - super().__init__( - "BackendResult cannot produce result of type {}.".format(result_type) - ) + super().__init__(f"BackendResult cannot produce result of type {result_type}.") diff --git a/pytket/pytket/backends/backendinfo.py b/pytket/pytket/backends/backendinfo.py index 0b857587fa..8e293dad93 100644 --- a/pytket/pytket/backends/backendinfo.py +++ b/pytket/pytket/backends/backendinfo.py @@ -14,19 +14,19 @@ """ BackendInfo class: additional information on Backends """ -from dataclasses import dataclass, field, asdict -from typing import Any, Dict, List, Optional, Set, Tuple, Union +from dataclasses import asdict, dataclass, field +from typing import Any from pytket.architecture import Architecture, FullyConnected from pytket.circuit import Node, OpType -_OpTypeErrs = Dict[OpType, float] -_Edge = Tuple[Node, Node] +_OpTypeErrs = dict[OpType, float] +_Edge = tuple[Node, Node] def _serialize_all_node_gate_errors( - d: Optional[Dict[Node, _OpTypeErrs]] -) -> Optional[List[List]]: + d: dict[Node, _OpTypeErrs] | None +) -> list[list] | None: if d is None: return None return [ @@ -36,8 +36,8 @@ def _serialize_all_node_gate_errors( def _deserialize_all_node_gate_errors( - l: Optional[List[List]], -) -> Optional[Dict[Node, _OpTypeErrs]]: + l: list[list] | None, +) -> dict[Node, _OpTypeErrs] | None: if l is None: return None return { @@ -46,9 +46,7 @@ def _deserialize_all_node_gate_errors( } -def _serialize_all_edge_gate_errors( - d: Optional[Dict[_Edge, _OpTypeErrs]] -) -> Optional[List]: +def _serialize_all_edge_gate_errors(d: dict[_Edge, _OpTypeErrs] | None) -> list | None: if d is None: return None return [ @@ -58,8 +56,8 @@ def _serialize_all_edge_gate_errors( def _deserialize_all_edge_gate_errors( - l: Optional[List], -) -> Optional[Dict[_Edge, _OpTypeErrs]]: + l: list | None, +) -> dict[_Edge, _OpTypeErrs] | None: if l is None: return None return { @@ -71,64 +69,64 @@ def _deserialize_all_edge_gate_errors( def _serialize_all_readout_errors( - d: Optional[Dict[Node, List[List[float]]]] -) -> Optional[List[List]]: + d: dict[Node, list[list[float]]] | None +) -> list[list] | None: if d is None: return None return [[n.to_list(), errs] for n, errs in d.items()] def _deserialize_all_readout_errors( - l: Optional[List[List]], -) -> Optional[Dict[Node, List[List[float]]]]: + l: list[list] | None, +) -> dict[Node, list[list[float]]] | None: if l is None: return None return {Node.from_list(n): errs for n, errs in l} def _serialize_averaged_node_gate_errors( - d: Optional[Dict[Node, float]] -) -> Optional[List[List]]: + d: dict[Node, float] | None +) -> list[list] | None: if d is None: return None return [[n.to_list(), err] for n, err in d.items()] def _deserialize_averaged_node_gate_errors( - l: Optional[List[List]], -) -> Optional[Dict[Node, float]]: + l: list[list] | None, +) -> dict[Node, float] | None: if l is None: return None return {Node.from_list(n): err for n, err in l} def _serialize_averaged_edge_gate_errors( - d: Optional[Dict[_Edge, float]] -) -> Optional[List[List]]: + d: dict[_Edge, float] | None +) -> list[list] | None: if d is None: return None return [[[n0.to_list(), n1.to_list()], err] for (n0, n1), err in d.items()] def _deserialize_averaged_edge_gate_errors( - l: Optional[List[List]], -) -> Optional[Dict[Tuple, float]]: + l: list[list] | None, +) -> dict[tuple, float] | None: if l is None: return None return {(Node.from_list(n0), Node.from_list(n1)): err for (n0, n1), err in l} def _serialize_averaged_readout_errors( - d: Optional[Dict[Node, float]] -) -> Optional[List[List]]: + d: dict[Node, float] | None +) -> list[list] | None: if d is None: return None return [[n.to_list(), err] for n, err in d.items()] def _deserialize_averaged_readout_errors( - l: Optional[List[List]], -) -> Optional[Dict[Node, float]]: + l: list[list] | None, +) -> dict[Node, float] | None: if l is None: return None return {Node.from_list(n): err for n, err in l} @@ -169,30 +167,30 @@ class BackendInfo: # identifying information name: str - device_name: Optional[str] + device_name: str | None version: str # hardware constraints - architecture: Optional[Union[Architecture, FullyConnected]] - gate_set: Set[OpType] - n_cl_reg: Optional[int] = None + architecture: Architecture | FullyConnected | None + gate_set: set[OpType] + n_cl_reg: int | None = None # additional feature support supports_fast_feedforward: bool = False supports_reset: bool = False supports_midcircuit_measurement: bool = False # additional basic device characterisation information - all_node_gate_errors: Optional[Dict[Node, Dict[OpType, float]]] = None - all_edge_gate_errors: Optional[Dict[Tuple[Node, Node], Dict[OpType, float]]] = None - all_readout_errors: Optional[Dict[Node, List[List[float]]]] = None - averaged_node_gate_errors: Optional[Dict[Node, float]] = None - averaged_edge_gate_errors: Optional[Dict[Tuple[Node, Node], float]] = None - averaged_readout_errors: Optional[Dict[Node, float]] = None + all_node_gate_errors: dict[Node, dict[OpType, float]] | None = None + all_edge_gate_errors: dict[tuple[Node, Node], dict[OpType, float]] | None = None + all_readout_errors: dict[Node, list[list[float]]] | None = None + averaged_node_gate_errors: dict[Node, float] | None = None + averaged_edge_gate_errors: dict[tuple[Node, Node], float] | None = None + averaged_readout_errors: dict[Node, float] | None = None # miscellaneous, eg additional noise characterisation and provider-supplied # information - misc: Dict[str, Any] = field(default_factory=dict) + misc: dict[str, Any] = field(default_factory=dict) @property - def nodes(self) -> List[Node]: + def nodes(self) -> list[Node]: """ List of device nodes of the backend. Returns empty list if the `architecture` field is not provided. @@ -240,7 +238,7 @@ def get_misc(self, key: str) -> Any: """ return self.misc[key] - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """ Generate a dictionary serialized representation of BackendInfo, suitable for writing to JSON. @@ -274,7 +272,7 @@ def to_dict(self) -> Dict[str, Any]: return self_dict @classmethod - def from_dict(cls, d: Dict[str, Any]) -> "BackendInfo": + def from_dict(cls, d: dict[str, Any]) -> "BackendInfo": """ Construct BackendInfo object from JSON serializable dictionary representation, as generated by BackendInfo.to_dict. @@ -313,10 +311,10 @@ def from_dict(cls, d: Dict[str, Any]) -> "BackendInfo": def fully_connected_backendinfo( # type: ignore name: str, - device_name: Optional[str], + device_name: str | None, version: str, n_nodes: int, - gate_set: Set[OpType], + gate_set: set[OpType], **kwargs ) -> BackendInfo: """ diff --git a/pytket/pytket/backends/backendresult.py b/pytket/pytket/backends/backendresult.py index 95ef954b1b..d2d7794afa 100644 --- a/pytket/pytket/backends/backendresult.py +++ b/pytket/pytket/backends/backendresult.py @@ -13,44 +13,32 @@ # limitations under the License. """`BackendResult` class and associated methods.""" -from typing import ( - Optional, - Any, - Sequence, - Iterable, - List, - Tuple, - Dict, - Counter, - NamedTuple, - Collection, - Type, - TypeVar, - cast, -) import operator -from functools import reduce import warnings +from collections import Counter +from collections.abc import Collection, Iterable, Sequence +from functools import reduce +from typing import Any, NamedTuple, TypeVar, cast import numpy as np from pytket.circuit import ( + _DEBUG_ONE_REG_PREFIX, + _DEBUG_ZERO_REG_PREFIX, BasisOrder, Bit, Circuit, Qubit, UnitID, - _DEBUG_ZERO_REG_PREFIX, - _DEBUG_ONE_REG_PREFIX, ) from pytket.utils.distribution import EmpiricalDistribution, ProbabilityDistribution +from pytket.utils.outcomearray import OutcomeArray, readout_counts from pytket.utils.results import ( - probs_from_state, get_n_qb_from_statevector, permute_basis_indexing, permute_rows_cols_in_unitary, + probs_from_state, ) -from pytket.utils.outcomearray import OutcomeArray, readout_counts from .backend_exceptions import InvalidResultType @@ -58,11 +46,11 @@ class StoredResult(NamedTuple): """NamedTuple with optional fields for all result types.""" - counts: Optional[Counter[OutcomeArray]] = None - shots: Optional[OutcomeArray] = None - state: Optional[np.ndarray] = None - unitary: Optional[np.ndarray] = None - density_matrix: Optional[np.ndarray] = None + counts: Counter[OutcomeArray] | None = None + shots: OutcomeArray | None = None + state: np.ndarray | None = None + unitary: np.ndarray | None = None + density_matrix: np.ndarray | None = None class BackendResult: @@ -89,14 +77,14 @@ class BackendResult: def __init__( self, *, - q_bits: Optional[Sequence[Qubit]] = None, - c_bits: Optional[Sequence[Bit]] = None, - counts: Optional[Counter[OutcomeArray]] = None, - shots: Optional[OutcomeArray] = None, + q_bits: Sequence[Qubit] | None = None, + c_bits: Sequence[Bit] | None = None, + counts: Counter[OutcomeArray] | None = None, + shots: OutcomeArray | None = None, state: Any = None, unitary: Any = None, density_matrix: Any = None, - ppcirc: Optional[Circuit] = None, + ppcirc: Circuit | None = None, ): # deal with mutable defaults if q_bits is None: @@ -113,20 +101,18 @@ def __init__( self._ppcirc = ppcirc - self.c_bits: Dict[Bit, int] = dict() - self.q_bits: Dict[Qubit, int] = dict() + self.c_bits: dict[Bit, int] = dict() + self.q_bits: dict[Qubit, int] = dict() def _process_unitids( - var: Sequence[UnitID], attr: str, lent: int, uid: Type[UnitID] + var: Sequence[UnitID], attr: str, lent: int, uid: type[UnitID] ) -> None: if var: setattr(self, attr, dict((unit, i) for i, unit in enumerate(var))) if lent != len(var): raise ValueError( - ( - f"Length of {attr} ({len(var)}) does not" - f" match input data dimensions ({lent})." - ) + f"Length of {attr} ({len(var)}) does not" + f" match input data dimensions ({lent})." ) else: setattr(self, attr, dict((uid(i), i) for i in range(lent))) # type: ignore @@ -161,9 +147,9 @@ def _process_unitids( def __repr__(self) -> str: return ( - "BackendResult(q_bits={s.q_bits},c_bits={s.c_bits},counts={s._counts}," - "shots={s._shots},state={s._state},unitary={s._unitary}," - "density_matrix={s._density_matrix})".format(s=self) + f"BackendResult(q_bits={self.q_bits},c_bits={self.c_bits}," + f"counts={self._counts},shots={self._shots},state={self._state}," + f"unitary={self._unitary},density_matrix={self._density_matrix})" ) @property @@ -197,7 +183,7 @@ def __eq__(self, other: object) -> bool: and np.array_equal(self._density_matrix, other._density_matrix) ) - def get_bitlist(self) -> List[Bit]: + def get_bitlist(self) -> list[Bit]: """Return list of Bits in internal storage order. :raises AttributeError: BackendResult does not include a Bits list. @@ -206,7 +192,7 @@ def get_bitlist(self) -> List[Bit]: """ return _sort_keys_by_val(self.c_bits) - def get_qbitlist(self) -> List[Qubit]: + def get_qbitlist(self) -> list[Qubit]: """Return list of Qubits in internal storage order. :raises AttributeError: BackendResult does not include a Qubits list. @@ -217,9 +203,9 @@ def get_qbitlist(self) -> List[Qubit]: return _sort_keys_by_val(self.q_bits) def _get_measured_res( - self, bits: Sequence[Bit], ppcirc: Optional[Circuit] = None + self, bits: Sequence[Bit], ppcirc: Circuit | None = None ) -> StoredResult: - vals: Dict[str, Any] = {} + vals: dict[str, Any] = {} if not self.contains_measured_results: raise InvalidResultType("shots/counts") @@ -281,7 +267,7 @@ def _get_measured_res( def _permute_statearray_qb_labels( self, array: np.ndarray, - relabling_map: Dict[Qubit, Qubit], + relabling_map: dict[Qubit, Qubit], ) -> np.ndarray: """Permute statevector/unitary according to a relabelling of Qubits. @@ -292,7 +278,7 @@ def _permute_statearray_qb_labels( :return: Permuted array. :rtype: np.ndarray """ - original_labeling: Sequence["Qubit"] = self.get_qbitlist() + original_labeling: Sequence[Qubit] = self.get_qbitlist() n_labels = len(original_labeling) permutation = [0] * n_labels for i, orig_qb in enumerate(original_labeling): @@ -308,7 +294,7 @@ def _permute_statearray_qb_labels( return permuter(array, tuple(permutation)) def _get_state_res(self, qubits: Sequence[Qubit]) -> StoredResult: - vals: Dict[str, Any] = {} + vals: dict[str, Any] = {} if not self.contains_state_results: raise InvalidResultType("state/unitary/density_matrix") @@ -332,9 +318,9 @@ def _get_state_res(self, qubits: Sequence[Qubit]) -> StoredResult: def get_result( self, - request_ids: Optional[Sequence[UnitID]] = None, + request_ids: Sequence[UnitID] | None = None, basis: BasisOrder = BasisOrder.ilo, - ppcirc: Optional[Circuit] = None, + ppcirc: Circuit | None = None, ) -> StoredResult: """Retrieve all results, optionally according to a specified UnitID ordering or subset. @@ -384,9 +370,9 @@ def get_result( def get_shots( self, - cbits: Optional[Sequence[Bit]] = None, + cbits: Sequence[Bit] | None = None, basis: BasisOrder = BasisOrder.ilo, - ppcirc: Optional[Circuit] = None, + ppcirc: Circuit | None = None, ) -> np.ndarray: """Return shots if available. @@ -413,10 +399,10 @@ def get_shots( def get_counts( self, - cbits: Optional[Sequence[Bit]] = None, + cbits: Sequence[Bit] | None = None, basis: BasisOrder = BasisOrder.ilo, - ppcirc: Optional[Circuit] = None, - ) -> Counter[Tuple[int, ...]]: + ppcirc: Circuit | None = None, + ) -> Counter[tuple[int, ...]]: """Return counts of outcomes if available. :param cbits: ordered subset of Bits, returns all results by default, defaults @@ -441,7 +427,7 @@ def get_counts( def get_state( self, - qbits: Optional[Sequence[Qubit]] = None, + qbits: Sequence[Qubit] | None = None, basis: BasisOrder = BasisOrder.ilo, ) -> np.ndarray: """Return statevector if available. @@ -467,7 +453,7 @@ def get_state( def get_unitary( self, - qbits: Optional[Sequence[Qubit]] = None, + qbits: Sequence[Qubit] | None = None, basis: BasisOrder = BasisOrder.ilo, ) -> np.ndarray: """Return unitary if available. @@ -490,7 +476,7 @@ def get_unitary( def get_density_matrix( self, - qbits: Optional[Sequence[Qubit]] = None, + qbits: Sequence[Qubit] | None = None, basis: BasisOrder = BasisOrder.ilo, ) -> np.ndarray: """Return density_matrix if available. @@ -512,8 +498,8 @@ def get_density_matrix( raise InvalidResultType("density_matrix") def get_distribution( - self, units: Optional[Sequence[UnitID]] = None - ) -> Dict[Tuple[int, ...], float]: + self, units: Sequence[UnitID] | None = None + ) -> dict[tuple[int, ...], float]: """Calculate an exact or approximate probability distribution over outcomes. If the exact statevector is known, the exact probability distribution is @@ -543,12 +529,11 @@ def get_distribution( except InvalidResultType: counts = self.get_counts(units) # type: ignore total = sum(counts.values()) - dist = {outcome: count / total for outcome, count in counts.items()} - return dist + return {outcome: count / total for outcome, count in counts.items()} def get_empirical_distribution( - self, bits: Optional[Sequence[Bit]] = None - ) -> EmpiricalDistribution[Tuple[int, ...]]: + self, bits: Sequence[Bit] | None = None + ) -> EmpiricalDistribution[tuple[int, ...]]: """Convert to a :py:class:`pytket.utils.distribution.EmpiricalDistribution` where the observations are sequences of 0s and 1s. @@ -563,8 +548,8 @@ def get_empirical_distribution( return EmpiricalDistribution(self.get_counts(bits)) def get_probability_distribution( - self, qubits: Optional[Sequence[Qubit]] = None, min_p: float = 0.0 - ) -> ProbabilityDistribution[Tuple[int, ...]]: + self, qubits: Sequence[Qubit] | None = None, min_p: float = 0.0 + ) -> ProbabilityDistribution[tuple[int, ...]]: """Convert to a :py:class:`pytket.utils.distribution.ProbabilityDistribution` where the possible outcomes are sequences of 0s and 1s. @@ -582,7 +567,7 @@ def get_probability_distribution( state = self.get_state(qubits) return ProbabilityDistribution(probs_from_state(state), min_p=min_p) - def get_debug_info(self) -> Dict[str, float]: + def get_debug_info(self) -> dict[str, float]: """Calculate the success rate of each assertion averaged across shots. Each assertion in pytket is decomposed into a sequence of transformations @@ -594,7 +579,7 @@ def get_debug_info(self) -> Dict[str, float]: """ _tket_debug_zero_prefix = _DEBUG_ZERO_REG_PREFIX + "_" _tket_debug_one_prefix = _DEBUG_ONE_REG_PREFIX + "_" - debug_bit_dict: Dict[str, Dict[str, Any]] = {} + debug_bit_dict: dict[str, dict[str, Any]] = {} for bit in self.c_bits: if bit.reg_name.startswith(_tket_debug_zero_prefix): expectation = 0 @@ -609,7 +594,7 @@ def get_debug_info(self) -> Dict[str, float]: debug_bit_dict[assertion_name]["bits"].append(bit) debug_bit_dict[assertion_name]["expectations"].append(expectation) - debug_result_dict: Dict[str, float] = {} + debug_result_dict: dict[str, float] = {} for assertion_name, bits_info in debug_bit_dict.items(): counts = self.get_counts(bits_info["bits"]) debug_result_dict[assertion_name] = counts[ @@ -617,14 +602,14 @@ def get_debug_info(self) -> Dict[str, float]: ] / sum(counts.values()) return debug_result_dict - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Generate a dictionary serialized representation of BackendResult, suitable for writing to JSON. :return: JSON serializable dictionary. :rtype: Dict[str, Any] """ - outdict: Dict[str, Any] = dict() + outdict: dict[str, Any] = dict() outdict["qubits"] = [q.to_list() for q in self.get_qbitlist()] outdict["bits"] = [c.to_list() for c in self.get_bitlist()] if self._shots is not None: @@ -645,7 +630,7 @@ def to_dict(self) -> Dict[str, Any]: return outdict @classmethod - def from_dict(cls, res_dict: Dict[str, Any]) -> "BackendResult": + def from_dict(cls, res_dict: dict[str, Any]) -> "BackendResult": """Construct BackendResult object from JSON serializable dictionary representation, as generated by BackendResult.to_dict. @@ -692,7 +677,7 @@ def from_dict(cls, res_dict: Dict[str, Any]) -> "BackendResult": T = TypeVar("T") -def _sort_keys_by_val(dic: Dict[T, int]) -> List[T]: +def _sort_keys_by_val(dic: dict[T, int]) -> list[T]: if not dic: return [] vals, _ = zip(*sorted(dic.items(), key=lambda x: x[1])) @@ -703,12 +688,12 @@ def _check_permuted_sequence(first: Collection[Any], second: Collection[Any]) -> return len(first) == len(second) and set(first) == set(second) -def _complex_ar_to_dict(ar: np.ndarray) -> Dict[str, List]: +def _complex_ar_to_dict(ar: np.ndarray) -> dict[str, list]: """Dictionary of real, imaginary parts of complex array, each in list form.""" return {"real": ar.real.tolist(), "imag": ar.imag.tolist()} -def _complex_ar_from_dict(dic: Dict[str, List]) -> np.ndarray: +def _complex_ar_from_dict(dic: dict[str, list]) -> np.ndarray: """Construct complex array from dictionary of real and imaginary parts""" out = np.array(dic["real"], dtype=complex) diff --git a/pytket/pytket/backends/resulthandle.py b/pytket/pytket/backends/resulthandle.py index 415ec82b77..7d44b014fd 100644 --- a/pytket/pytket/backends/resulthandle.py +++ b/pytket/pytket/backends/resulthandle.py @@ -15,14 +15,14 @@ """ResultHandle class """ -from typing import Tuple, Type, Union, Iterator, overload from ast import literal_eval -from collections.abc import Sequence +from collections.abc import Iterator, Sequence +from typing import Union, overload # mypy doesn't think you can pass the tuple to Union BasicHashType = Union[int, float, complex, str, bool, bytes] -_ResultIdTuple = Tuple[ - Union[Type[int], Type[float], Type[complex], Type[str], Type[bool], Type[bytes]], +_ResultIdTuple = tuple[ + type[int] | type[float] | type[complex] | type[str] | type[bool] | type[bytes], ..., ] @@ -86,11 +86,11 @@ def __len__(self) -> int: def __getitem__(self, key: int) -> BasicHashType: ... @overload - def __getitem__(self, key: slice) -> Tuple[BasicHashType, ...]: ... + def __getitem__(self, key: slice) -> tuple[BasicHashType, ...]: ... def __getitem__( - self, key: Union[int, slice] - ) -> Union[BasicHashType, Tuple[BasicHashType, ...]]: + self, key: int | slice + ) -> BasicHashType | tuple[BasicHashType, ...]: # weird logic required to make mypy happy, can't just # return self._identifiers[key] if isinstance(key, slice): diff --git a/pytket/pytket/backends/status.py b/pytket/pytket/backends/status.py index 022f6387d6..543f1d0ad2 100644 --- a/pytket/pytket/backends/status.py +++ b/pytket/pytket/backends/status.py @@ -14,9 +14,10 @@ """Status classes for circuits submitted to backends. """ +from collections.abc import Callable from datetime import datetime -from typing import Any, Callable, Dict, NamedTuple, Optional from enum import Enum +from typing import Any, NamedTuple class StatusEnum(Enum): @@ -43,21 +44,21 @@ class CircuitStatus(NamedTuple): status: StatusEnum message: str = "" - error_detail: Optional[str] = None + error_detail: str | None = None # Timestamp for when a status was last entered. - completed_time: Optional[datetime] = None - queued_time: Optional[datetime] = None - submitted_time: Optional[datetime] = None - running_time: Optional[datetime] = None - cancelled_time: Optional[datetime] = None - error_time: Optional[datetime] = None + completed_time: datetime | None = None + queued_time: datetime | None = None + submitted_time: datetime | None = None + running_time: datetime | None = None + cancelled_time: datetime | None = None + error_time: datetime | None = None - queue_position: Optional[int] = None + queue_position: int | None = None - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Return JSON serializable dictionary representation.""" - circuit_status_dict: Dict[str, Any] = { + circuit_status_dict: dict[str, Any] = { "status": self.status.name, "message": self.message, } @@ -83,7 +84,7 @@ def to_dict(self) -> Dict[str, Any]: return circuit_status_dict @classmethod - def from_dict(cls, dic: Dict[str, Any]) -> "CircuitStatus": + def from_dict(cls, dic: dict[str, Any]) -> "CircuitStatus": """Construct from JSON serializable dictionary.""" invalid = ValueError(f"Dictionary invalid format for CircuitStatus: {dic}") if "message" not in dic or "status" not in dic: @@ -94,9 +95,9 @@ def from_dict(cls, dic: Dict[str, Any]) -> "CircuitStatus": except StopIteration as e: raise invalid from e - error_detail = dic.get("error_detail", None) + error_detail = dic.get("error_detail") - read_optional_datetime: Callable[[str], Optional[datetime]] = lambda key: ( + read_optional_datetime: Callable[[str], datetime | None] = lambda key: ( datetime.fromisoformat(x) if (x := dic.get(key)) is not None else None ) completed_time = read_optional_datetime("completed_time") @@ -106,7 +107,7 @@ def from_dict(cls, dic: Dict[str, Any]) -> "CircuitStatus": cancelled_time = read_optional_datetime("cancelled_time") error_time = read_optional_datetime("error_time") - queue_position = dic.get("queue_position", None) + queue_position = dic.get("queue_position") return cls( status, diff --git a/pytket/pytket/circuit/__init__.py b/pytket/pytket/circuit/__init__.py index 6040d896a0..3a3d799a15 100644 --- a/pytket/pytket/circuit/__init__.py +++ b/pytket/pytket/circuit/__init__.py @@ -16,37 +16,38 @@ tket :py:class:`Circuit` data structure. This module is provided in binary form during the PyPI installation.""" from typing import ( - TYPE_CHECKING, Any, - Tuple, - Type, - Union, Callable, Optional, Sequence, + Union, ) +from pytket import wasm from pytket._tket.circuit import * from pytket._tket.circuit import Circuit - +from pytket._tket.pauli import Pauli from pytket._tket.unit_id import * -from pytket._tket.unit_id import Bit, BitRegister # prefixes for assertion bits -from pytket._tket.unit_id import _DEBUG_ZERO_REG_PREFIX, _DEBUG_ONE_REG_PREFIX -from pytket._tket.pauli import Pauli - -from pytket import wasm +from pytket._tket.unit_id import ( + _DEBUG_ONE_REG_PREFIX, + _DEBUG_ZERO_REG_PREFIX, + Bit, + BitRegister, +) from .logic_exp import ( + BinaryOp, BitLogicExp, BitWiseOp, - RegLogicExp, - RegWiseOp, Constant, LogicExp, - BinaryOp, Ops, + RegLogicExp, + RegWiseOp, + create_bit_logic_exp, + create_reg_logic_exp, if_bit, if_not_bit, reg_eq, @@ -55,8 +56,6 @@ reg_leq, reg_lt, reg_neq, - create_reg_logic_exp, - create_bit_logic_exp, ) diff --git a/pytket/pytket/circuit/add_condition.py b/pytket/pytket/circuit/add_condition.py index 79abac5209..d48a825949 100644 --- a/pytket/pytket/circuit/add_condition.py +++ b/pytket/pytket/circuit/add_condition.py @@ -13,25 +13,21 @@ # limitations under the License. """Enable adding of gates with conditions on Bit or BitRegister expressions.""" -from typing import Tuple, Union -from pytket.circuit import Bit, Circuit, BitRegister -from pytket._tket.unit_id import ( - _TEMP_REG_SIZE, - _TEMP_BIT_NAME, - _TEMP_BIT_REG_BASE, -) +from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE +from pytket.circuit import Bit, BitRegister, Circuit +from pytket.circuit.clexpr import wired_clexpr_from_logic_exp from pytket.circuit.logic_exp import ( BitLogicExp, Constant, PredicateExp, RegEq, - RegNeq, RegGeq, RegGt, RegLeq, RegLogicExp, RegLt, + RegNeq, ) @@ -40,14 +36,14 @@ class NonConstError(Exception): def _add_condition( - circ: Circuit, condition: Union[PredicateExp, Bit, BitLogicExp] -) -> Tuple[Bit, bool]: + circ: Circuit, condition: PredicateExp | Bit | BitLogicExp +) -> tuple[Bit, bool]: """Add a condition expression to a circuit using classical expression boxes, rangepredicates and conditionals. Return predicate bit and value of said bit. """ if isinstance(condition, Bit): return condition, True - elif isinstance(condition, PredicateExp): + if isinstance(condition, PredicateExp): pred_exp, pred_val = condition.args # PredicateExp constructor should ensure arg order if not isinstance(pred_val, Constant): @@ -60,7 +56,7 @@ def _add_condition( pred_exp = condition else: raise ValueError( - f"Condition {condition} must be of type Bit, " "BitLogicExp or PredicateExp" + f"Condition {condition} must be of type Bit, BitLogicExp or PredicateExp" ) next_index = ( @@ -79,7 +75,8 @@ def _add_condition( circ.add_bit(condition_bit) if isinstance(pred_exp, BitLogicExp): - circ.add_classicalexpbox_bit(pred_exp, [condition_bit]) + wexpr, args = wired_clexpr_from_logic_exp(pred_exp, [condition_bit]) + circ.add_clexpr(wexpr, args) return condition_bit, bool(pred_val) assert isinstance(pred_exp, (RegLogicExp, BitRegister)) @@ -99,10 +96,11 @@ def _add_condition( int(r_name.split("_")[-1]) for r_name in existing_reg_names ) next_index = max(existing_reg_indices, default=-1) + 1 - temp_reg = BitRegister(f"{_TEMP_BIT_REG_BASE}_{next_index}", _TEMP_REG_SIZE) + temp_reg = BitRegister(f"{_TEMP_BIT_REG_BASE}_{next_index}", min_reg_size) circ.add_c_register(temp_reg) - target_bits = temp_reg.to_list()[:min_reg_size] - circ.add_classicalexpbox_register(pred_exp, target_bits) + target_bits = temp_reg.to_list() + wexpr, args = wired_clexpr_from_logic_exp(pred_exp, target_bits) + circ.add_clexpr(wexpr, args) elif isinstance(pred_exp, BitRegister): target_bits = pred_exp.to_list() diff --git a/pytket/pytket/circuit/clexpr.py b/pytket/pytket/circuit/clexpr.py index b6059c560c..62d7e4bfe1 100644 --- a/pytket/pytket/circuit/clexpr.py +++ b/pytket/pytket/circuit/clexpr.py @@ -13,16 +13,21 @@ # limitations under the License. from dataclasses import dataclass +from typing import Any + from pytket.circuit import ( Bit, BitRegister, + Circuit, ClBitVar, ClExpr, + ClExprOp, ClOp, ClRegVar, + OpType, WiredClExpr, ) -from pytket.circuit.logic_exp import Ops, BitWiseOp, RegWiseOp, LogicExp +from pytket.circuit.logic_exp import BitWiseOp, LogicExp, Ops, RegWiseOp _reg_output_clops = set( [ @@ -161,3 +166,39 @@ def wired_clexpr_from_logic_exp( ), args, ) + + +def check_register_alignments(circ: Circuit) -> bool: + """Check whether all `ClExprOp` operations in the circuit are register-aligned. + + This means that all register variables and outputs occurring in `ClExprOp` comprise + whole registers with the bits in the correct order. + + :param circ: circuit to check + :return: True iff all `ClExprOp` operations are register-aligned + """ + cregs: set[tuple[Bit, ...]] = set( + tuple(creg.to_list()) for creg in circ.c_registers + ) + for cmd in circ: + op = cmd.op + if op.type == OpType.ClExpr: + assert isinstance(op, ClExprOp) + wexpr: WiredClExpr = op.expr + args = cmd.args + if any( + tuple(args[i] for i in poslist) not in cregs + for poslist in wexpr.reg_posn.values() + ) or ( + has_reg_output(wexpr.expr.op) + and tuple(args[i] for i in wexpr.output_posn) not in cregs + ): + return False + return True + + +def _add_clexpr_to_circuit_from_logicexp( + circ: Circuit, exp: LogicExp, output_bits: list[Bit], **kwargs: Any +) -> None: + wexpr, args = wired_clexpr_from_logic_exp(exp, output_bits) + circ.add_clexpr(wexpr, args, **kwargs) diff --git a/pytket/pytket/circuit/decompose_classical.py b/pytket/pytket/circuit/decompose_classical.py index 1b525bc4f1..11a7f1a71c 100644 --- a/pytket/pytket/circuit/decompose_classical.py +++ b/pytket/pytket/circuit/decompose_classical.py @@ -15,28 +15,30 @@ """Functions for decomposing Circuits containing classical expressions in to primitive logical operations.""" import copy +from collections.abc import Callable from heapq import heappop, heappush -from typing import ( - Callable, - Dict, - List, - Optional, - Set, - Tuple, - Type, - TypeVar, - Union, - Generic, +from typing import Any, Generic, TypeVar + +from pytket._tket.circuit import ( + Circuit, + ClassicalExpBox, + ClBitVar, + ClExpr, + ClExprOp, + ClOp, + ClRegVar, + Conditional, + OpType, + WiredClExpr, ) - from pytket._tket.unit_id import ( _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE, _TEMP_REG_SIZE, - BitRegister, Bit, + BitRegister, ) -from pytket._tket.circuit import Circuit, ClassicalExpBox, Conditional, OpType +from pytket.circuit.clexpr import check_register_alignments, has_reg_output from pytket.circuit.logic_exp import ( BitLogicExp, BitWiseOp, @@ -58,8 +60,8 @@ class VarHeap(Generic[T]): """A generic heap implementation.""" def __init__(self) -> None: - self._heap: List[T] = [] - self._heap_vars: Set[T] = set() + self._heap: list[T] = [] + self._heap_vars: set[T] = set() def pop(self) -> T: """Pop from top of heap.""" @@ -137,19 +139,18 @@ def fresh_var(self, size: int = _TEMP_REG_SIZE) -> BitRegister: return new_reg -def temp_reg_in_args(args: List[Bit]) -> Optional[BitRegister]: +def temp_reg_in_args(args: list[Bit]) -> BitRegister | None: """If there are bits from a temporary register in the args, return it.""" temp_reg_bits = [b for b in args if b.reg_name.startswith(_TEMP_BIT_REG_BASE)] if temp_reg_bits: - temp_reg = BitRegister(temp_reg_bits[0].reg_name, _TEMP_REG_SIZE) - return temp_reg + return BitRegister(temp_reg_bits[0].reg_name, _TEMP_REG_SIZE) return None -VarType = TypeVar("VarType", Type[Bit], Type[BitRegister]) +VarType = TypeVar("VarType", type[Bit], type[BitRegister]) -def int_to_bools(val: Constant, width: int) -> List[bool]: +def int_to_bools(val: Constant, width: int) -> list[bool]: # map int to bools via litle endian encoding return list(map(bool, map(int, reversed(f"{val:0{width}b}"[-width:])))) @@ -164,7 +165,7 @@ def get_bit_width(x: int) -> int: def _gen_walk(var_type: VarType, newcirc: Circuit, heap: VarHeap) -> Callable[ - [Union[RegLogicExp, BitLogicExp], Optional[Variable], Optional[Dict]], + [RegLogicExp | BitLogicExp, Variable | None, dict | None], Variable, ]: """Generate a recursive walk method for decomposing an expression tree.""" @@ -178,8 +179,8 @@ def _gen_walk(var_type: VarType, newcirc: Circuit, heap: VarHeap) -> Callable[ RegWiseOp.XOR: newcirc.add_c_xor_to_registers, } if var_type is Bit: - op_type: Union[Type[BitWiseOp], Type[RegWiseOp]] = BitWiseOp - exp_type: Union[Type[BitLogicExp], Type[RegLogicExp]] = BitLogicExp + op_type: type[BitWiseOp] | type[RegWiseOp] = BitWiseOp + exp_type: type[BitLogicExp] | type[RegLogicExp] = BitLogicExp else: assert var_type is BitRegister op_type = RegWiseOp @@ -194,7 +195,7 @@ def add_method(var: Variable) -> None: newcirc.add_bit(var.__getitem__(i), reject_dups=False) # method for setting bits during walk - def set_bits(var: Variable, val: Constant, kwargs: Dict) -> None: + def set_bits(var: Variable, val: Constant, kwargs: dict) -> None: if isinstance(var, Bit): newcirc.add_c_setbits([bool(val)], [var], **kwargs) else: @@ -210,9 +211,9 @@ def set_bits(var: Variable, val: Constant, kwargs: Dict) -> None: # convert an expression to gates on the circuit # and return the variable holding the result def recursive_walk( - exp: Union[RegLogicExp, BitLogicExp], - targ_bit: Optional[Variable] = None, - kwargs: Optional[Dict] = None, + exp: RegLogicExp | BitLogicExp, + targ_bit: Variable | None = None, + kwargs: dict | None = None, ) -> Variable: assert isinstance(exp.op, op_type) kwargs = kwargs or {} @@ -253,8 +254,131 @@ def recursive_walk( return recursive_walk -def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: - """Rewrite a circuit command-wise, decomposing ClassicalExpBox.""" +class ClExprDecomposer: + def __init__( + self, + circ: Circuit, + bit_posn: dict[int, int], + reg_posn: dict[int, list[int]], + args: list[Bit], + bit_heap: BitHeap, + reg_heap: RegHeap, + kwargs: dict[str, Any], + ): + self.circ: Circuit = circ + self.bit_posn: dict[int, int] = bit_posn + self.reg_posn: dict[int, list[int]] = reg_posn + self.args: list[Bit] = args + self.bit_heap: BitHeap = bit_heap + self.reg_heap: RegHeap = reg_heap + self.kwargs: dict[str, Any] = kwargs + # Construct maps from int (i.e. ClBitVar) to Bit, and from int (i.e. ClRegVar) + # to BitRegister: + self.bit_vars = {i: args[p] for i, p in bit_posn.items()} + self.reg_vars = { + i: BitRegister(args[p[0]].reg_name, len(p)) for i, p in reg_posn.items() + } + + def add_var(self, var: Variable) -> None: + """Add a Bit or BitRegister to the circuit if not already present.""" + if isinstance(var, Bit): + self.circ.add_bit(var, reject_dups=False) + else: + assert isinstance(var, BitRegister) + for bit in var.to_list(): + self.circ.add_bit(bit, reject_dups=False) + + def set_bits(self, var: Variable, val: int) -> None: + """Set the value of a Bit or BitRegister.""" + assert val >= 0 + if isinstance(var, Bit): + assert val >> 1 == 0 + self.circ.add_c_setbits([bool(val)], [var], **self.kwargs) + else: + assert isinstance(var, BitRegister) + assert val >> var.size == 0 + self.circ.add_c_setreg(val, var, **self.kwargs) + + def decompose_expr(self, expr: ClExpr, out_var: Variable | None) -> Variable: + """Add the decomposed expression to the circuit and return the Bit or + BitRegister that contains the result. + + :param expr: the expression to decompose + :param out_var: where to put the output (if None, create a new scratch location) + """ + op: ClOp = expr.op + heap: VarHeap = self.reg_heap if has_reg_output(op) else self.bit_heap + + # Eliminate (recursively) subsidiary expressions from the arguments, and convert + # all terms to Bit or BitRegister: + terms: list[Variable] = [] + for arg in expr.args: + if isinstance(arg, int): + # Assign to a fresh variable + fresh_var = heap.fresh_var() + self.add_var(fresh_var) + self.set_bits(fresh_var, arg) + terms.append(fresh_var) + elif isinstance(arg, ClBitVar): + terms.append(self.bit_vars[arg.index]) + elif isinstance(arg, ClRegVar): + terms.append(self.reg_vars[arg.index]) + else: + assert isinstance(arg, ClExpr) + terms.append(self.decompose_expr(arg, None)) + + # Enable reuse of temporary terms: + for term in terms: + if heap.is_heap_var(term): + heap.push(term) + + if out_var is None: + out_var = heap.fresh_var() + self.add_var(out_var) + match op: + case ClOp.BitAnd: + self.circ.add_c_and(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.BitNot: + self.circ.add_c_not(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.BitOne: + assert isinstance(out_var, Bit) + self.circ.add_c_setbits([True], [out_var], **self.kwargs) + case ClOp.BitOr: + self.circ.add_c_or(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.BitXor: + self.circ.add_c_xor(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.BitZero: + assert isinstance(out_var, Bit) + self.circ.add_c_setbits([False], [out_var], **self.kwargs) + case ClOp.RegAnd: + self.circ.add_c_and_to_registers(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.RegNot: + self.circ.add_c_not_to_registers(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.RegOne: + assert isinstance(out_var, BitRegister) + self.circ.add_c_setbits( + [True] * out_var.size, out_var.to_list(), **self.kwargs + ) + case ClOp.RegOr: + self.circ.add_c_or_to_registers(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.RegXor: + self.circ.add_c_xor_to_registers(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.RegZero: + assert isinstance(out_var, BitRegister) + self.circ.add_c_setbits( + [False] * out_var.size, out_var.to_list(), **self.kwargs + ) + case _: + raise DecomposeClassicalError( + f"{op} cannot be decomposed to TKET primitives." + ) + return out_var + + +def _decompose_expressions(circ: Circuit) -> tuple[Circuit, bool]: + """Rewrite a circuit command-wise, decomposing ClassicalExpBox and ClExprOp.""" + if not check_register_alignments(circ): + raise DecomposeClassicalError("Circuit contains non-register-aligned ClExprOp.") bit_heap = BitHeap() reg_heap = RegHeap() # add already used heap variables to heaps @@ -281,7 +405,7 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: reg_recursive_walk = _gen_walk(BitRegister, newcirc, reg_heap) # targets of predicates that need to be relabelled - replace_targets: Dict[Variable, Variable] = dict() + replace_targets: dict[Variable, Variable] = dict() modified = False for command in circ: op = command.op @@ -354,9 +478,41 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: replace_targets[out_reg] = comp_reg modified = True continue + + elif optype == OpType.ClExpr: + assert isinstance(op, ClExprOp) + wexpr: WiredClExpr = op.expr + expr: ClExpr = wexpr.expr + bit_posn = wexpr.bit_posn + reg_posn = wexpr.reg_posn + output_posn = wexpr.output_posn + assert len(output_posn) > 0 + output0 = args[output_posn[0]] + assert isinstance(output0, Bit) + out_var: Variable = ( + BitRegister(output0.reg_name, len(output_posn)) + if has_reg_output(expr.op) + else output0 + ) + decomposer = ClExprDecomposer( + newcirc, bit_posn, reg_posn, args, bit_heap, reg_heap, kwargs # type: ignore + ) + comp_var = decomposer.decompose_expr(expr, out_var) + if comp_var != out_var: + replace_targets[out_var] = comp_var + modified = True + continue + if optype == OpType.Barrier: # add_gate doesn't work for metaops newcirc.add_barrier(args) else: + for arg in args: + if ( + isinstance(arg, Bit) + and arg.reg_name != "_w" # workaround: this shouldn't be type Bit + and arg not in newcirc.bits + ): + newcirc.add_bit(arg) newcirc.add_gate(op, args, **kwargs) return newcirc, modified diff --git a/pytket/pytket/circuit/display/__init__.py b/pytket/pytket/circuit/display/__init__.py index 38483197c0..cc36129605 100644 --- a/pytket/pytket/circuit/display/__init__.py +++ b/pytket/pytket/circuit/display/__init__.py @@ -21,12 +21,12 @@ import uuid import webbrowser from dataclasses import dataclass, field -from typing import Literal, cast, Any +from typing import Any, Literal, cast -from jinja2 import Environment, PrefixLoader, FileSystemLoader, nodes +from jinja2 import Environment, FileSystemLoader, PrefixLoader, nodes from jinja2.ext import Extension -from jinja2.utils import markupsafe from jinja2.parser import Parser +from jinja2.utils import markupsafe from pytket.circuit import Circuit from pytket.config import PytketExtConfig @@ -47,8 +47,7 @@ def _render(self, filename: str) -> markupsafe.Markup: return markupsafe.Markup( self.environment.loader.get_source(self.environment, filename)[0] ) - else: - return markupsafe.Markup("") + return markupsafe.Markup("") # Set up jinja to access our templates diff --git a/pytket/pytket/circuit/logic_exp.py b/pytket/pytket/circuit/logic_exp.py index 9ba71c2317..a62f617a3e 100644 --- a/pytket/pytket/circuit/logic_exp.py +++ b/pytket/pytket/circuit/logic_exp.py @@ -14,30 +14,17 @@ """Classes and functions for constructing logical expressions over Bit and BitRegister.""" -from typing import ( - Any, - Iterable, - Set, - Tuple, - Type, - List, - Union, - Dict, - ClassVar, - Iterator, - TypeVar, - cast, - Sequence, -) -from enum import Enum +from collections.abc import Iterable, Iterator, Sequence from dataclasses import dataclass +from enum import Enum +from typing import Any, ClassVar, TypeVar, Union, cast from pytket.circuit import Bit, BitRegister T = TypeVar("T") -def filter_by_type(seq: Iterable, var_type: Type[T]) -> Iterator[Tuple[int, T]]: +def filter_by_type(seq: Iterable, var_type: type[T]) -> Iterator[tuple[int, T]]: """Return enumeration of seq, with only elements of type var_type.""" return filter(lambda x: isinstance(x[1], var_type), enumerate(seq)) @@ -75,7 +62,7 @@ class RegWiseOp(Enum): LSH = "<<" RSH = ">>" NOT = "~" - NEG = "-" + NEG = "-" # noqa: PIE796 Ops = Union[BitWiseOp, RegWiseOp] # all op enum types @@ -92,12 +79,12 @@ class LogicExp: Encoded as a tree of expressions""" op: Ops # enum for operation encoded by this node - args: List[ArgType] # arguments of operation + args: list[ArgType] # arguments of operation # class level dictionary mapping enum to class - op_cls_dict: ClassVar[Dict[Ops, Type["LogicExp"]]] = dict() + op_cls_dict: ClassVar[dict[Ops, type["LogicExp"]]] = dict() @classmethod - def factory(cls, op: Ops) -> Type["LogicExp"]: + def factory(cls, op: Ops) -> type["LogicExp"]: """Return matching operation class for enum.""" # RegNeg cannot be initialised this way as "-" clashes with SUB if op == BitWiseOp.AND: @@ -162,7 +149,7 @@ def set_value(self, var: Variable, val: Constant) -> None: arg.set_value(var, val) @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: """Evaluate expression given constant values for all args.""" raise NotImplementedError @@ -173,17 +160,17 @@ def eval_vals(self) -> ArgType: self.args[i] = arg.eval_vals() if all(isinstance(a, Constant) for a in self.args): try: - rval = self._const_eval(cast(List[Constant], self.args)) + rval = self._const_eval(cast(list[Constant], self.args)) except NotImplementedError: pass return rval - def all_inputs(self) -> Set[Variable]: + def all_inputs(self) -> set[Variable]: """ :return: All variables involved in expression. :rtype: Set[Variable] """ - outset: Set[Variable] = set() + outset: set[Variable] = set() for arg in self.args: if isinstance(arg, LogicExp): @@ -192,9 +179,8 @@ def all_inputs(self) -> Set[Variable]: if isinstance(self, BitLogicExp): if isinstance(arg, Bit): outset.add(arg) - else: - if isinstance(arg, BitRegister): - outset.add(arg) + elif isinstance(arg, BitRegister): + outset.add(arg) return outset def all_inputs_ordered(self) -> list[Variable]: @@ -212,9 +198,8 @@ def all_inputs_ordered(self) -> list[Variable]: if isinstance(self, BitLogicExp): if isinstance(arg, Bit): outset[arg] = None - else: - if isinstance(arg, BitRegister): - outset[arg] = None + elif isinstance(arg, BitRegister): + outset[arg] = None return list(outset) def __eq__(self, other: object) -> bool: @@ -222,10 +207,10 @@ def __eq__(self, other: object) -> bool: return False return (self.op == other.op) and (self.args == other.args) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Output JSON serializable nested dictionary.""" - out: Dict[str, Any] = {"op": str(self.op)} - args_ser: List[Union[Dict, Constant, List[Union[str, int]]]] = [] + out: dict[str, Any] = {"op": str(self.op)} + args_ser: list[dict | Constant | list[str | int]] = [] for arg in self.args: if isinstance(arg, LogicExp): @@ -241,18 +226,18 @@ def to_dict(self) -> Dict[str, Any]: return out @classmethod - def from_dict(cls, dic: Dict[str, Any]) -> "LogicExp": + def from_dict(cls, dic: dict[str, Any]) -> "LogicExp": """Load from JSON serializable nested dictionary.""" opset_name, op_name = dic["op"].split(".", 2) opset = BitWiseOp if opset_name == "BitWiseOp" else RegWiseOp op = next(o for o in opset if o.name == op_name) - args: List[ArgType] = [] + args: list[ArgType] = [] for arg_ser in dic["args"]: if isinstance(arg_ser, Constant): args.append(arg_ser) - elif isinstance(arg_ser, List): + elif isinstance(arg_ser, list): args.append(Bit(arg_ser[0], arg_ser[1])) - elif isinstance(arg_ser, Dict): + elif isinstance(arg_ser, dict): if "op" in arg_ser: args.append(LogicExp.from_dict(arg_ser)) else: @@ -260,7 +245,7 @@ def from_dict(cls, dic: Dict[str, Any]) -> "LogicExp": return create_logic_exp(op, args) def _rename_args_recursive( - self, cmap: Dict[Bit, Bit], renamed_regs: Set[str] + self, cmap: dict[Bit, Bit], renamed_regs: set[str] ) -> bool: success = False for i, arg in enumerate(self.args): @@ -279,13 +264,13 @@ def _rename_args_recursive( success |= arg._rename_args_recursive(cmap, renamed_regs) return success - def rename_args(self, cmap: Dict[Bit, Bit]) -> bool: + def rename_args(self, cmap: dict[Bit, Bit]) -> bool: """Rename the Bits according to a Bit map. Raise ValueError if a bit is being used in a register-wise expression. """ if all(old_bit == new_bit for old_bit, new_bit in cmap.items()): return False - renamed_regs = set([key.reg_name for key in cmap.keys()]) + renamed_regs = set([key.reg_name for key in cmap]) return self._rename_args_recursive(cmap, renamed_regs) @@ -381,7 +366,7 @@ def __str__(self) -> str: class And(BinaryOp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] & args[1] def eval_vals(self) -> ArgType: @@ -393,13 +378,13 @@ def eval_vals(self) -> ArgType: class Or(BinaryOp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] | args[1] class Xor(BinaryOp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] ^ args[1] @@ -433,7 +418,7 @@ def __init__(self, arg1: BitArgType) -> None: self.args = [arg1] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return 1 - args[0] @@ -443,7 +428,7 @@ def __init__(self) -> None: self.args = [] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return 0 @@ -453,7 +438,7 @@ def __init__(self) -> None: self.args = [] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return 1 @@ -538,13 +523,13 @@ class PredicateExp(BinaryOp): class Eq(PredicateExp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] == args[1] class Neq(PredicateExp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return 1 - Eq._const_eval(args) @@ -578,7 +563,7 @@ def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: self.args = [arg1, arg2] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] < args[1] @@ -588,7 +573,7 @@ def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: self.args = [arg1, arg2] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] > args[1] @@ -598,7 +583,7 @@ def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: self.args = [arg1, arg2] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] <= args[1] @@ -608,53 +593,53 @@ def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: self.args = [arg1, arg2] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] >= args[1] -def reg_eq(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_eq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister equality predicate, i.e. for a register ``r``, ``(r == 5)`` is expressed as ``reg_eq(r, 5)``""" return RegEq(register, value) -def reg_neq(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_neq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister inequality predicate, i.e. for a register ``r``, ``(r != 5)`` is expressed as ``reg_neq(r, 5)``""" return RegNeq(register, value) -def reg_lt(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_lt(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister less than predicate, i.e. for a register ``r``, ``(r < 5)`` is expressed as ``reg_lt(r, 5)``""" return RegLt(register, value) -def reg_gt(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_gt(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister greater than predicate, i.e. for a register ``r``, ``(r > 5)`` is expressed as ``reg_gt(r, 5)``""" return RegGt(register, value) -def reg_leq(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_leq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister less than or equal to predicate, i.e. for a register ``r``, ``(r <= 5)`` is expressed as ``reg_leq(r, 5)``""" return RegLeq(register, value) -def reg_geq(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_geq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister greater than or equal to predicate, i.e. for a register ``r``, ``(r >= 5)`` is expressed as ``reg_geq(r, 5)``""" return RegGeq(register, value) -def if_bit(bit: Union[Bit, BitLogicExp]) -> PredicateExp: +def if_bit(bit: Bit | BitLogicExp) -> PredicateExp: """Equivalent of ``if bit:``.""" return BitEq(bit, 1) -def if_not_bit(bit: Union[Bit, BitLogicExp]) -> PredicateExp: +def if_not_bit(bit: Bit | BitLogicExp) -> PredicateExp: """Equivalent of ``if not bit:``.""" return BitEq(bit, 0) @@ -681,7 +666,7 @@ def create_bit_logic_exp(op: BitWiseOp, args: Sequence[BitArgType]) -> BitLogicE if op == BitWiseOp.ZERO: assert len(args) == 0 return BitZero() - if op == BitWiseOp.ONE: + if op == BitWiseOp.ONE: # noqa: RET503 assert len(args) == 0 return BitOne() @@ -753,13 +738,12 @@ def create_logic_exp(op: Ops, args: Sequence[ArgType]) -> LogicExp: assert isinstance(arg, (BitLogicExp, Bit, Constant)) bit_args.append(arg) return create_bit_logic_exp(op, bit_args) - else: - assert isinstance(op, RegWiseOp) - reg_args = [] - for arg in args: - assert isinstance(arg, (RegLogicExp, BitRegister, Constant)) - reg_args.append(arg) - return create_reg_logic_exp(op, reg_args) + assert isinstance(op, RegWiseOp) + reg_args = [] + for arg in args: + assert isinstance(arg, (RegLogicExp, BitRegister, Constant)) + reg_args.append(arg) + return create_reg_logic_exp(op, reg_args) def create_predicate_exp(op: Ops, args: Sequence[ArgType]) -> PredicateExp: diff --git a/pytket/pytket/circuit/named_types.py b/pytket/pytket/circuit/named_types.py index 1673cb78a5..bf35d3362b 100644 --- a/pytket/pytket/circuit/named_types.py +++ b/pytket/pytket/circuit/named_types.py @@ -13,12 +13,13 @@ # limitations under the License. """Named types for convenience""" -from typing import Union, Sequence +from collections.abc import Sequence +from typing import Union from sympy import Expr from pytket._tket.circuit import Op -from pytket._tket.unit_id import UnitID, Qubit, Bit +from pytket._tket.unit_id import Bit, Qubit, UnitID ParamType = Union[float, Expr] """Type used for circuit parameters that can either diff --git a/pytket/pytket/circuit_library/__init__.py b/pytket/pytket/circuit_library/__init__.py index 1cd3275f1e..37441072bd 100644 --- a/pytket/pytket/circuit_library/__init__.py +++ b/pytket/pytket/circuit_library/__init__.py @@ -13,100 +13,100 @@ # limitations under the License. from pytket._tket.circuit_library import ( - BRIDGE_using_CX_0, - BRIDGE_using_CX_1, - CX_using_TK2, - TK2_using_CX, - TK2_using_CX_and_swap, - approx_TK2_using_1xCX, - approx_TK2_using_2xCX, - TK2_using_3xCX, - CX_using_flipped_CX, - CX_using_ECR, - CX_using_ZZMax, - CX_using_ZZPhase, - CX_using_XXPhase_0, - CX_using_XXPhase_1, - CX_using_AAMS, - CX_VS_CX_reduced, - CX_V_CX_reduced, - CX_S_CX_reduced, - CX_V_S_XC_reduced, - CX_S_V_XC_reduced, - CX_XC_reduced, - SWAP_using_CX_0, - SWAP_using_CX_1, + BRIDGE, + CCX, + CX, + H_CZ_H, X1_CX, Z0_CX, - CCX_modulo_phase_shift, - CCX_normal_decomp, + BRIDGE_using_CX_0, + BRIDGE_using_CX_1, C3X_normal_decomp, C4X_normal_decomp, - ladder_down, - ladder_down_2, - ladder_up, - X, - CX, - CCX, - BRIDGE, - H_CZ_H, - CZ_using_CX, - CY_using_CX, + CCX_modulo_phase_shift, + CCX_normal_decomp, CH_using_CX, - CV_using_CX, - CVdg_using_CX, - CSX_using_CX, - CSXdg_using_CX, + CRx_using_CX, + CRx_using_TK2, + CRy_using_CX, + CRy_using_TK2, + CRz_using_CX, + CRz_using_TK2, CS_using_CX, CSdg_using_CX, CSWAP_using_CX, - ECR_using_CX, - ZZMax_using_CX, - CRz_using_TK2, - CRz_using_CX, - CRx_using_TK2, - CRx_using_CX, - CRy_using_TK2, - CRy_using_CX, - CU1_using_TK2, + CSX_using_CX, + CSXdg_using_CX, CU1_using_CX, + CU1_using_TK2, CU3_using_CX, - ISWAP_using_TK2, - ISWAP_using_CX, - XXPhase_using_TK2, - XXPhase_using_CX, - YYPhase_using_TK2, - YYPhase_using_CX, - ZZPhase_using_TK2, - ZZPhase_using_CX, - TK2_using_ZZPhase, - TK2_using_ZZPhase_and_swap, - TK2_using_TK2_or_swap, - approx_TK2_using_1xZZPhase, - approx_TK2_using_2xZZPhase, - TK2_using_ZZMax, - TK2_using_ZZMax_and_swap, - XXPhase3_using_TK2, - XXPhase3_using_CX, - ESWAP_using_TK2, + CV_using_CX, + CVdg_using_CX, + CX_S_CX_reduced, + CX_S_V_XC_reduced, + CX_using_AAMS, + CX_using_ECR, + CX_using_flipped_CX, + CX_using_TK2, + CX_using_XXPhase_0, + CX_using_XXPhase_1, + CX_using_ZZMax, + CX_using_ZZPhase, + CX_V_CX_reduced, + CX_V_S_XC_reduced, + CX_VS_CX_reduced, + CX_XC_reduced, + CY_using_CX, + CZ_using_CX, + ECR_using_CX, ESWAP_using_CX, - FSim_using_TK2, + ESWAP_using_TK2, FSim_using_CX, - PhasedISWAP_using_TK2, - PhasedISWAP_using_CX, + FSim_using_TK2, + ISWAP_using_CX, + ISWAP_using_TK2, NPhasedX_using_PhasedX, - TK2_using_normalised_TK2, + PhasedISWAP_using_CX, + PhasedISWAP_using_TK2, + Rx_using_GPI, + Ry_using_GPI, + Rz_using_GPI, + SWAP_using_CX_0, + SWAP_using_CX_1, TK1_to_PhasedXRz, - TK1_to_RzRx, TK1_to_RzH, + TK1_to_RzRx, TK1_to_RzSX, TK1_to_TK1, - Rx_using_GPI, - Ry_using_GPI, - Rz_using_GPI, + TK1_using_GPI, + TK2_using_3xCX, + TK2_using_AAMS, + TK2_using_CX, + TK2_using_CX_and_swap, + TK2_using_normalised_TK2, + TK2_using_TK2_or_swap, + TK2_using_ZZMax, + TK2_using_ZZMax_and_swap, + TK2_using_ZZPhase, + TK2_using_ZZPhase_and_swap, + X, + XXPhase3_using_CX, + XXPhase3_using_TK2, XXPhase_using_AAMS, + XXPhase_using_CX, + XXPhase_using_TK2, YYPhase_using_AAMS, + YYPhase_using_CX, + YYPhase_using_TK2, + ZZMax_using_CX, ZZPhase_using_AAMS, - TK1_using_GPI, - TK2_using_AAMS, + ZZPhase_using_CX, + ZZPhase_using_TK2, + approx_TK2_using_1xCX, + approx_TK2_using_1xZZPhase, + approx_TK2_using_2xCX, + approx_TK2_using_2xZZPhase, + ladder_down, + ladder_down_2, + ladder_up, ) diff --git a/pytket/pytket/config/__init__.py b/pytket/pytket/config/__init__.py index 419c6b5ee1..6a6f365818 100644 --- a/pytket/pytket/config/__init__.py +++ b/pytket/pytket/config/__init__.py @@ -16,9 +16,9 @@ The configuration is saved to and loaded from file.""" from .pytket_config import ( - get_config_file_path, PytketConfig, PytketExtConfig, + get_config_file_path, load_config_file, write_config_file, ) diff --git a/pytket/pytket/config/pytket_config.py b/pytket/pytket/config/pytket_config.py index d5229239bf..3df189f551 100644 --- a/pytket/pytket/config/pytket_config.py +++ b/pytket/pytket/config/pytket_config.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Any, ClassVar, Dict, Optional, TypeVar, Type -from dataclasses import asdict, dataclass import json import os +from abc import ABC, abstractmethod +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any, ClassVar, TypeVar def get_config_file_path() -> Path: @@ -29,20 +29,18 @@ def get_config_file_path() -> Path: else: config_dir = Path(xdg_conifg_dir) - pytket_config_file = config_dir / "pytket" / "config.json" - - return pytket_config_file + return config_dir / "pytket" / "config.json" class PytketConfig: """PytketConfig represents a loaded config file for pytket and extension packages.""" - extensions: Dict[str, Any] + extensions: dict[str, Any] def __init__( self, - extensions: Optional[Dict[str, Any]] = None, + extensions: dict[str, Any] | None = None, ) -> None: """Construct a PytketConfig object with inital config parameter values. @@ -98,23 +96,23 @@ class PytketExtConfig(ABC): @classmethod @abstractmethod - def from_extension_dict(cls: Type[T], ext_dict: Dict[str, Any]) -> T: + def from_extension_dict(cls: type[T], ext_dict: dict[str, Any]) -> T: """Abstract method to build PytketExtConfig from dictionary serialized form.""" ... - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Serialize to dictionary.""" return asdict(self) @classmethod - def from_pytketconfig(cls: Type[T], p_config: PytketConfig) -> T: + def from_pytketconfig(cls: type[T], p_config: PytketConfig) -> T: """Build from PytketConfig instance.""" if cls.ext_dict_key in p_config.extensions: return cls.from_extension_dict(p_config.extensions[cls.ext_dict_key]) return cls.from_extension_dict({}) @classmethod - def from_default_config_file(cls: Type[T]) -> T: + def from_default_config_file(cls: type[T]) -> T: """Load from default config file.""" return cls.from_pytketconfig(load_config_file()) diff --git a/pytket/pytket/passes/__init__.py b/pytket/pytket/passes/__init__.py index 9a0a77a02b..65d104fab1 100644 --- a/pytket/pytket/passes/__init__.py +++ b/pytket/pytket/passes/__init__.py @@ -16,7 +16,7 @@ from pytket._tket.passes import * -from .script import compilation_pass_from_script, compilation_pass_grammar from .auto_rebase import auto_rebase_pass, auto_squash_pass from .passselector import PassSelector from .resizeregpass import scratch_reg_resize_pass +from .script import compilation_pass_from_script, compilation_pass_grammar diff --git a/pytket/pytket/passes/auto_rebase.py b/pytket/pytket/passes/auto_rebase.py index 3b91fdedab..37e5d22d1c 100644 --- a/pytket/pytket/passes/auto_rebase.py +++ b/pytket/pytket/passes/auto_rebase.py @@ -13,7 +13,7 @@ # limitations under the License. import warnings -from typing import Set + from pytket.circuit import OpType from pytket.passes import AutoRebase, AutoSquash, BasePass @@ -34,7 +34,7 @@ def __init__(self, *args: object, **kwargs: dict) -> None: super().__init__(*args, **kwargs) -def auto_rebase_pass(gateset: Set[OpType], allow_swaps: bool = False) -> BasePass: +def auto_rebase_pass(gateset: set[OpType], allow_swaps: bool = False) -> BasePass: """Attempt to generate a rebase pass automatically for the given target gateset. @@ -44,8 +44,8 @@ def auto_rebase_pass(gateset: Set[OpType], allow_swaps: bool = False) -> BasePas Raises an error if no known decompositions can be found, in which case try using RebaseCustom with your own decompositions. - In addition to the gate types in ``gateset``, any ``Measure``, ``Reset`` and - ``Collapse`` operations in the original circuit are retained. Conditional + In addition to the gate types in ``gateset``, any ``Measure`` and ``Reset`` + operations in the original circuit are retained. Conditional operations are also allowed. ``Phase`` gates may also be introduced. :param gateset: Set of supported OpTypes, target gate set. @@ -70,7 +70,7 @@ def auto_rebase_pass(gateset: Set[OpType], allow_swaps: bool = False) -> BasePas ) -def auto_squash_pass(gateset: Set[OpType]) -> BasePass: +def auto_squash_pass(gateset: set[OpType]) -> BasePass: """Attempt to generate a squash pass automatically for the given target single qubit gateset. diff --git a/pytket/pytket/passes/passselector.py b/pytket/pytket/passes/passselector.py index 2e4ba5ec6f..26d3ab8b8d 100644 --- a/pytket/pytket/passes/passselector.py +++ b/pytket/pytket/passes/passselector.py @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Callable +from collections.abc import Callable from pytket.circuit import Circuit + from .._tket.passes import BasePass @@ -51,13 +52,14 @@ def apply(self, circ: Circuit) -> Circuit: """ circ_list = [circ.copy() for _ in self._passlist] - self._scores: list[Optional[int]] = [] + self._scores: list[int | None] = [] for p, c in zip(self._passlist, circ_list): try: p.apply(c) self._scores.append(self._score_func(c)) - except: # in case of any error the pass should be ignored + except: # noqa: E722 + # in case of any error the pass should be ignored self._scores.append(None) try: @@ -67,7 +69,7 @@ def apply(self, circ: Circuit) -> Circuit: except ValueError: raise RuntimeError("No passes have successfully run on this circuit") - def get_scores(self) -> list[Optional[int]]: + def get_scores(self) -> list[int | None]: """ :return: scores of the circuit after compiling for each of the compilations passes diff --git a/pytket/pytket/passes/resizeregpass.py b/pytket/pytket/passes/resizeregpass.py index 494b1c2b49..412d72f470 100644 --- a/pytket/pytket/passes/resizeregpass.py +++ b/pytket/pytket/passes/resizeregpass.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pytket.circuit import Bit, Circuit from pytket.unit_id import _TEMP_BIT_NAME -from pytket.circuit import Circuit, Bit + from .._tket.passes import BasePass, CustomPass MAX_C_REG_WIDTH = 32 diff --git a/pytket/pytket/passes/script.py b/pytket/pytket/passes/script.py index aa467135d7..eb2b6a5c30 100644 --- a/pytket/pytket/passes/script.py +++ b/pytket/pytket/passes/script.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, cast +from typing import cast + from lark import Lark, Transformer -from pytket.circuit import OpType -from pytket.passes import BasePass, RepeatPass, SequencePass + +from pytket.circuit import CXConfigType, OpType from pytket.passes import ( + BasePass, CliffordSimp, CommuteThroughMultis, ContextSimp, @@ -32,21 +34,22 @@ GuidedPauliSimp, KAKDecomposition, OptimisePhaseGadgets, - PauliSimp, PauliExponentials, + PauliSimp, PauliSquash, PeepholeOptimise2Q, RebaseTket, RemoveBarriers, RemoveDiscarded, RemoveRedundancies, + RepeatPass, + SequencePass, SimplifyInitial, SimplifyMeasured, SynthesiseTket, SynthesiseUMD, ThreeQubitSquash, ) -from pytket.circuit import CXConfigType from pytket.transform import PauliSynthStrat pass_grammar = """ @@ -181,166 +184,166 @@ def pass_list(self, t: list[BasePass]) -> BasePass: def repeat_pass(self, t: list[BasePass]) -> BasePass: return RepeatPass(t[0]) - def clifford_simp(self, t: List) -> BasePass: + def clifford_simp(self, t: list) -> BasePass: return CliffordSimp() - def clifford_simp_no_swaps(self, t: List) -> BasePass: + def clifford_simp_no_swaps(self, t: list) -> BasePass: return CliffordSimp(allow_swaps=False) - def commute_through_multis(self, t: List) -> BasePass: + def commute_through_multis(self, t: list) -> BasePass: return CommuteThroughMultis() - def context_simp(self, t: List) -> BasePass: + def context_simp(self, t: list) -> BasePass: return ContextSimp() - def context_simp_no_classical(self, t: List) -> BasePass: + def context_simp_no_classical(self, t: list) -> BasePass: return ContextSimp(allow_classical=False) - def decompose_arbitrarily_controlled_gates(self, t: List) -> BasePass: + def decompose_arbitrarily_controlled_gates(self, t: list) -> BasePass: return DecomposeArbitrarilyControlledGates() - def decompose_boxes(self, t: List) -> BasePass: + def decompose_boxes(self, t: list) -> BasePass: return DecomposeBoxes() - def decompose_classical_exp(self, t: List) -> BasePass: + def decompose_classical_exp(self, t: list) -> BasePass: return DecomposeClassicalExp() - def decompose_multi_qubits_cx(self, t: List) -> BasePass: + def decompose_multi_qubits_cx(self, t: list) -> BasePass: return DecomposeMultiQubitsCX() - def decompose_single_qubits_tk1(self, t: List) -> BasePass: + def decompose_single_qubits_tk1(self, t: list) -> BasePass: return DecomposeSingleQubitsTK1() - def delay_measures(self, t: List) -> BasePass: + def delay_measures(self, t: list) -> BasePass: return DelayMeasures(False) - def delay_measures_try(self, t: List) -> BasePass: + def delay_measures_try(self, t: list) -> BasePass: return DelayMeasures(True) def euler_angle_reduction(self, t: list[OpType]) -> BasePass: return EulerAngleReduction(t[0], t[1]) - def flatten_registers(self, t: List) -> BasePass: + def flatten_registers(self, t: list) -> BasePass: return FlattenRegisters() - def full_peephole_optimise(self, t: List) -> BasePass: + def full_peephole_optimise(self, t: list) -> BasePass: return FullPeepholeOptimise() - def full_peephole_optimise_no_swaps(self, t: List) -> BasePass: + def full_peephole_optimise_no_swaps(self, t: list) -> BasePass: return FullPeepholeOptimise(allow_swaps=False) - def guided_pauli_simp(self, t: List) -> BasePass: + def guided_pauli_simp(self, t: list) -> BasePass: assert isinstance(t[0], PauliSynthStrat) assert isinstance(t[1], CXConfigType) return GuidedPauliSimp(strat=t[0], cx_config=t[1]) - def guided_pauli_simp_default(self, t: List) -> BasePass: + def guided_pauli_simp_default(self, t: list) -> BasePass: return GuidedPauliSimp() - def kak_decomposition(self, t: List) -> BasePass: + def kak_decomposition(self, t: list) -> BasePass: return KAKDecomposition() - def optimise_phase_gadgets(self, t: List) -> BasePass: + def optimise_phase_gadgets(self, t: list) -> BasePass: assert isinstance(t[0], CXConfigType) return OptimisePhaseGadgets(cx_config=t[0]) - def optimise_phase_gadgets_default(self, t: List) -> BasePass: + def optimise_phase_gadgets_default(self, t: list) -> BasePass: return OptimisePhaseGadgets() - def pauli_exponentials(self, t: List) -> BasePass: + def pauli_exponentials(self, t: list) -> BasePass: assert isinstance(t[0], PauliSynthStrat) assert isinstance(t[1], CXConfigType) return PauliExponentials(strat=t[0], cx_config=t[1]) - def pauli_exponentials_default(self, t: List) -> BasePass: + def pauli_exponentials_default(self, t: list) -> BasePass: return PauliExponentials() - def pauli_simp(self, t: List) -> BasePass: + def pauli_simp(self, t: list) -> BasePass: assert isinstance(t[0], PauliSynthStrat) assert isinstance(t[1], CXConfigType) return PauliSimp(strat=t[0], cx_config=t[1]) - def pauli_simp_default(self, t: List) -> BasePass: + def pauli_simp_default(self, t: list) -> BasePass: return PauliSimp() - def pauli_squash(self, t: List) -> BasePass: + def pauli_squash(self, t: list) -> BasePass: assert isinstance(t[0], PauliSynthStrat) assert isinstance(t[1], CXConfigType) return PauliSquash(strat=t[0], cx_config=t[1]) - def pauli_squash_default(self, t: List) -> BasePass: + def pauli_squash_default(self, t: list) -> BasePass: return PauliSquash() - def peephole_optimise_2q(self, t: List) -> BasePass: + def peephole_optimise_2q(self, t: list) -> BasePass: return PeepholeOptimise2Q() - def rebase_tket(self, t: List) -> BasePass: + def rebase_tket(self, t: list) -> BasePass: return RebaseTket() - def remove_barriers(self, t: List) -> BasePass: + def remove_barriers(self, t: list) -> BasePass: return RemoveBarriers() - def remove_discarded(self, t: List) -> BasePass: + def remove_discarded(self, t: list) -> BasePass: return RemoveDiscarded() - def remove_redundancies(self, t: List) -> BasePass: + def remove_redundancies(self, t: list) -> BasePass: return RemoveRedundancies() - def simplify_initial(self, t: List) -> BasePass: + def simplify_initial(self, t: list) -> BasePass: return SimplifyInitial() - def simplify_initial_no_classical(self, t: List) -> BasePass: + def simplify_initial_no_classical(self, t: list) -> BasePass: return SimplifyInitial(allow_classical=False) - def simplify_measured(self, t: List) -> BasePass: + def simplify_measured(self, t: list) -> BasePass: return SimplifyMeasured() - def synthesise_tket(self, t: List) -> BasePass: + def synthesise_tket(self, t: list) -> BasePass: return SynthesiseTket() - def synthesise_umd(self, t: List) -> BasePass: + def synthesise_umd(self, t: list) -> BasePass: return SynthesiseUMD() - def three_qubit_squash(self, t: List) -> BasePass: + def three_qubit_squash(self, t: list) -> BasePass: return ThreeQubitSquash() - def cx_config_type(self, t: List[CXConfigType]) -> CXConfigType: + def cx_config_type(self, t: list[CXConfigType]) -> CXConfigType: return t[0] - def cx_config_type_snake(self, t: List) -> CXConfigType: + def cx_config_type_snake(self, t: list) -> CXConfigType: return CXConfigType.Snake - def cx_config_type_star(self, t: List) -> CXConfigType: + def cx_config_type_star(self, t: list) -> CXConfigType: return CXConfigType.Star - def cx_config_type_tree(self, t: List) -> CXConfigType: + def cx_config_type_tree(self, t: list) -> CXConfigType: return CXConfigType.Tree - def cx_config_type_multi_q_gate(self, t: List) -> CXConfigType: + def cx_config_type_multi_q_gate(self, t: list) -> CXConfigType: return CXConfigType.MultiQGate - def op_type(self, t: List[OpType]) -> OpType: + def op_type(self, t: list[OpType]) -> OpType: return t[0] - def op_type_rx(self, t: List) -> OpType: + def op_type_rx(self, t: list) -> OpType: return OpType.Rx - def op_type_ry(self, t: List) -> OpType: + def op_type_ry(self, t: list) -> OpType: return OpType.Ry - def op_type_rz(self, t: List) -> OpType: + def op_type_rz(self, t: list) -> OpType: return OpType.Rz - def pauli_synth_strat(self, t: List[PauliSynthStrat]) -> PauliSynthStrat: + def pauli_synth_strat(self, t: list[PauliSynthStrat]) -> PauliSynthStrat: return t[0] - def pauli_synth_strat_individual(self, t: List) -> PauliSynthStrat: + def pauli_synth_strat_individual(self, t: list) -> PauliSynthStrat: return PauliSynthStrat.Individual - def pauli_synth_strat_pairwise(self, t: List) -> PauliSynthStrat: + def pauli_synth_strat_pairwise(self, t: list) -> PauliSynthStrat: return PauliSynthStrat.Pairwise - def pauli_synth_strat_sets(self, t: List) -> PauliSynthStrat: + def pauli_synth_strat_sets(self, t: list) -> PauliSynthStrat: return PauliSynthStrat.Sets diff --git a/pytket/pytket/qasm/__init__.py b/pytket/pytket/qasm/__init__.py index 4a4bbd14e1..19988d435a 100644 --- a/pytket/pytket/qasm/__init__.py +++ b/pytket/pytket/qasm/__init__.py @@ -18,10 +18,10 @@ from .qasm import ( circuit_from_qasm, - circuit_to_qasm, - circuit_from_qasm_str, - circuit_to_qasm_str, circuit_from_qasm_io, - circuit_to_qasm_io, + circuit_from_qasm_str, circuit_from_qasm_wasm, + circuit_to_qasm, + circuit_to_qasm_io, + circuit_to_qasm_str, ) diff --git a/pytket/pytket/qasm/grammar.py b/pytket/pytket/qasm/grammar.py index 50082ee3d7..493d7e4954 100644 --- a/pytket/pytket/qasm/grammar.py +++ b/pytket/pytket/qasm/grammar.py @@ -69,7 +69,7 @@ _pow_exp: ipow | _atom_exp ipow: _pow_exp "**" _atom_exp -_atom_exp: "(" _exp ")" +_atom_exp: "(" _exp ")" | cce_call | _atom diff --git a/pytket/pytket/qasm/qasm.py b/pytket/pytket/qasm/qasm.py index 86cba07ed1..605689dd1c 100644 --- a/pytket/pytket/qasm/qasm.py +++ b/pytket/pytket/qasm/qasm.py @@ -12,51 +12,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass +import itertools import os import re import uuid - -import itertools from collections import OrderedDict +from collections.abc import Callable, Generator, Iterable, Iterator, Sequence +from dataclasses import dataclass +from decimal import Decimal from importlib import import_module from itertools import chain, groupby -from decimal import Decimal -from typing import ( - Any, - Callable, - Dict, - Generator, - Iterable, - Iterator, - List, - NewType, - Optional, - Sequence, - Set, - TextIO, - Tuple, - Type, - TypeVar, - Union, - cast, -) -from sympy import Symbol, pi, Expr +from typing import Any, NewType, TextIO, TypeVar, Union, cast + from lark import Discard, Lark, Token, Transformer, Tree +from sympy import Expr, Symbol, pi from pytket._tket.circuit import ( + BarrierOp, ClassicalExpBox, + ClExpr, + ClExprOp, Command, Conditional, - RangePredicateOp, - SetBitsOp, CopyBitsOp, MultiBitOp, + RangePredicateOp, + SetBitsOp, WASMOp, - BarrierOp, - ClExprOp, WiredClExpr, - ClExpr, ) from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE from pytket.circuit import ( @@ -69,31 +52,38 @@ QubitRegister, UnitID, ) -from pytket.circuit.clexpr import has_reg_output, wired_clexpr_from_logic_exp +from pytket.circuit.clexpr import ( + check_register_alignments, + has_reg_output, + wired_clexpr_from_logic_exp, +) from pytket.circuit.decompose_classical import int_to_bools from pytket.circuit.logic_exp import ( BitLogicExp, BitWiseOp, - PredicateExp, LogicExp, + PredicateExp, RegEq, RegLogicExp, RegNeg, RegWiseOp, - create_predicate_exp, create_logic_exp, + create_predicate_exp, +) +from pytket.passes import ( + AutoRebase, + DecomposeBoxes, + RemoveRedundancies, + scratch_reg_resize_pass, ) from pytket.qasm.grammar import grammar -from pytket.passes import AutoRebase, DecomposeBoxes, RemoveRedundancies from pytket.wasm import WasmFileHandler class QASMParseError(Exception): """Error while parsing QASM input.""" - def __init__( - self, msg: str, line: Optional[int] = None, fname: Optional[str] = None - ): + def __init__(self, msg: str, line: int | None = None, fname: str | None = None): self.msg = msg self.line = line self.fname = fname @@ -115,7 +105,7 @@ class QASMUnsupportedError(Exception): _BITOPS.update(("+", "-")) # both are parsed to XOR _REGOPS = set(op.value for op in RegWiseOp) -Arg = Union[List, str] +Arg = Union[list, str] NOPARAM_COMMANDS = { @@ -193,16 +183,16 @@ class QASMUnsupportedError(Exception): "fsim": OpType.FSim, } -_tk_to_qasm_noparams = dict(((item[1], item[0]) for item in NOPARAM_COMMANDS.items())) +_tk_to_qasm_noparams = dict((item[1], item[0]) for item in NOPARAM_COMMANDS.items()) _tk_to_qasm_noparams[OpType.CX] = "cx" # prefer "cx" to "CX" -_tk_to_qasm_params = dict(((item[1], item[0]) for item in PARAM_COMMANDS.items())) +_tk_to_qasm_params = dict((item[1], item[0]) for item in PARAM_COMMANDS.items()) _tk_to_qasm_params[OpType.U3] = "u3" # prefer "u3" to "U" _tk_to_qasm_params[OpType.Rz] = "rz" # prefer "rz" to "Rz" _tk_to_qasm_extra_noparams = dict( - ((item[1], item[0]) for item in NOPARAM_EXTRA_COMMANDS.items()) + (item[1], item[0]) for item in NOPARAM_EXTRA_COMMANDS.items() ) _tk_to_qasm_extra_params = dict( - ((item[1], item[0]) for item in PARAM_EXTRA_COMMANDS.items()) + (item[1], item[0]) for item in PARAM_EXTRA_COMMANDS.items() ) _classical_gatestr_map = {"AND": "&", "OR": "|", "XOR": "^"} @@ -228,7 +218,7 @@ class QASMUnsupportedError(Exception): regname_regex = re.compile(r"^[a-z][a-zA-Z0-9_]*$") -def _extract_reg(var: Token) -> Tuple[str, int]: +def _extract_reg(var: Token) -> tuple[str, int]: match = unit_regex.match(var.value) if match is None: raise QASMParseError( @@ -243,10 +233,10 @@ def _extract_reg(var: Token) -> Tuple[str, int]: def _load_include_module( header_name: str, flter: bool, decls_only: bool -) -> Dict[str, Dict]: +) -> dict[str, dict]: try: if decls_only: - include_def: Dict[str, Dict] = import_module( + include_def: dict[str, dict] = import_module( f"pytket.qasm.includes._{header_name}_decls" )._INCLUDE_DECLS else: @@ -264,33 +254,33 @@ def _load_include_module( } -def _bin_par_exp(op: "str") -> Callable[["CircuitTransformer", List[str]], str]: - def f(self: "CircuitTransformer", vals: List[str]) -> str: +def _bin_par_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]: + def f(self: "CircuitTransformer", vals: list[str]) -> str: return f"({vals[0]} {op} {vals[1]})" return f -def _un_par_exp(op: "str") -> Callable[["CircuitTransformer", List[str]], str]: - def f(self: "CircuitTransformer", vals: List[str]) -> str: +def _un_par_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]: + def f(self: "CircuitTransformer", vals: list[str]) -> str: return f"({op}{vals[0]})" return f -def _un_call_exp(op: "str") -> Callable[["CircuitTransformer", List[str]], str]: - def f(self: "CircuitTransformer", vals: List[str]) -> str: +def _un_call_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]: + def f(self: "CircuitTransformer", vals: list[str]) -> str: return f"{op}({vals[0]})" return f -def _hashable_uid(arg: List) -> Tuple[str, int]: +def _hashable_uid(arg: list) -> tuple[str, int]: return arg[0], arg[1][0] Reg = NewType("Reg", str) -CommandDict = Dict[str, Any] +CommandDict = dict[str, Any] @dataclass @@ -306,19 +296,19 @@ def __init__( self, return_gate_dict: bool = False, maxwidth: int = 32, - use_clexpr: bool = False, + use_clexpr: bool = True, ) -> None: super().__init__() - self.q_registers: Dict[str, int] = {} - self.c_registers: Dict[str, int] = {} - self.gate_dict: Dict[str, Dict] = {} - self.wasm: Optional[WasmFileHandler] = None + self.q_registers: dict[str, int] = {} + self.c_registers: dict[str, int] = {} + self.gate_dict: dict[str, dict] = {} + self.wasm: WasmFileHandler | None = None self.include = "" self.return_gate_dict = return_gate_dict self.maxwidth = maxwidth self.use_clexpr = use_clexpr - def _fresh_temp_bit(self) -> List: + def _fresh_temp_bit(self) -> list: if _TEMP_BIT_NAME in self.c_registers: idx = self.c_registers[_TEMP_BIT_NAME] else: @@ -338,17 +328,16 @@ def _reset_context(self, reset_wasm: bool = True) -> None: def _get_reg(self, name: str) -> Reg: return Reg(name) - def _get_uid(self, iarg: Token) -> List: + def _get_uid(self, iarg: Token) -> list: name, idx = _extract_reg(iarg) return [name, [idx]] def _get_arg(self, arg: Token) -> Arg: if arg.type == "IARG": return self._get_uid(arg) - else: - return self._get_reg(arg.value) + return self._get_reg(arg.value) - def unroll_all_args(self, args: Iterable[Arg]) -> Iterator[List[Any]]: + def unroll_all_args(self, args: Iterable[Arg]) -> Iterator[list[Any]]: for arg in args: if isinstance(arg, str): size = ( @@ -363,13 +352,13 @@ def unroll_all_args(self, args: Iterable[Arg]) -> Iterator[List[Any]]: def margs(self, tree: Iterable[Token]) -> Iterator[Arg]: return map(self._get_arg, tree) - def iargs(self, tree: Iterable[Token]) -> Iterator[List]: + def iargs(self, tree: Iterable[Token]) -> Iterator[list]: return map(self._get_uid, tree) - def args(self, tree: Iterable[Token]) -> Iterator[List]: + def args(self, tree: Iterable[Token]) -> Iterator[list]: return ([tok.value, [0]] for tok in tree) - def creg(self, tree: List[Token]) -> None: + def creg(self, tree: list[Token]) -> None: name, size = _extract_reg(tree[0]) if size > self.maxwidth: raise QASMUnsupportedError( @@ -379,17 +368,17 @@ def creg(self, tree: List[Token]) -> None: ) self.c_registers[Reg(name)] = size - def qreg(self, tree: List[Token]) -> None: + def qreg(self, tree: list[Token]) -> None: name, size = _extract_reg(tree[0]) self.q_registers[Reg(name)] = size - def meas(self, tree: List[Token]) -> Iterable[CommandDict]: + def meas(self, tree: list[Token]) -> Iterable[CommandDict]: for args in zip(*self.unroll_all_args(self.margs(tree))): yield {"args": list(args), "op": {"type": "Measure"}} - def barr(self, tree: List[Arg]) -> Iterable[CommandDict]: + def barr(self, tree: list[Arg]) -> Iterable[CommandDict]: args = [q for qs in self.unroll_all_args(tree[0]) for q in qs] - signature: List[str] = [] + signature: list[str] = [] for arg in args: if arg[0] in self.c_registers: signature.append("C") @@ -404,14 +393,14 @@ def barr(self, tree: List[Arg]) -> Iterable[CommandDict]: "op": {"signature": signature, "type": "Barrier"}, } - def reset(self, tree: List[Token]) -> Iterable[CommandDict]: + def reset(self, tree: list[Token]) -> Iterable[CommandDict]: for qb in next(self.unroll_all_args(self.margs(tree))): yield {"args": [qb], "op": {"type": "Reset"}} def pars(self, vals: Iterable[str]) -> ParsMap: return ParsMap(map(str, vals)) - def mixedcall(self, tree: List) -> Iterator[CommandDict]: + def mixedcall(self, tree: list) -> Iterator[CommandDict]: child_iter = iter(tree) optoken = next(child_iter) @@ -475,7 +464,7 @@ def mixedcall(self, tree: List) -> Iterator[CommandDict]: else: params = [f"({par})/pi" for par in pars] if opstr in self.gate_dict: - op: Dict[str, Any] = {} + op: dict[str, Any] = {} if opstr in treat_as_barrier: op["type"] = "Barrier" param_sorted = ",".join(params) @@ -499,7 +488,7 @@ def mixedcall(self, tree: List) -> Iterator[CommandDict]: optype = _all_string_maps[opstr] except KeyError as e: raise QASMParseError( - "Cannot parse gate of type: {}".format(opstr), optoken.line + f"Cannot parse gate of type: {opstr}", optoken.line ) from e op = {"type": optype} if params: @@ -514,7 +503,7 @@ def mixedcall(self, tree: List) -> Iterator[CommandDict]: for arg in zip(*self.unroll_all_args(args)): yield {"args": list(arg), "op": op} - def gatecall(self, tree: List) -> Iterable[CommandDict]: + def gatecall(self, tree: list) -> Iterable[CommandDict]: return self.mixedcall(tree) def exp_args(self, tree: Iterable[Token]) -> Iterable[Reg]: @@ -526,14 +515,16 @@ def exp_args(self, tree: Iterable[Token]) -> Iterable[Reg]: "Non register arguments not supported for extern call.", arg.line ) - def _logic_exp(self, tree: List, opstr: str) -> LogicExp: + def _logic_exp(self, tree: list, opstr: str) -> LogicExp: args, line = self._get_logic_args(tree) - openum: Union[Type[BitWiseOp], Type[RegWiseOp]] + openum: type[BitWiseOp] | type[RegWiseOp] if opstr in _BITOPS and opstr not in _REGOPS: openum = BitWiseOp - elif opstr in _REGOPS and opstr not in _BITOPS: - openum = RegWiseOp - elif all(isinstance(arg, int) for arg in args): + elif ( + opstr in _REGOPS + and opstr not in _BITOPS + or all(isinstance(arg, int) for arg in args) + ): openum = RegWiseOp elif all(isinstance(arg, (Bit, BitLogicExp, int)) for arg in args): if all(arg in (0, 1) for arg in args if isinstance(arg, int)): @@ -547,15 +538,15 @@ def _logic_exp(self, tree: List, opstr: str) -> LogicExp: else: openum = RegWiseOp if openum is BitWiseOp and opstr in ("+", "-"): - op: Union[BitWiseOp, RegWiseOp] = BitWiseOp.XOR + op: BitWiseOp | RegWiseOp = BitWiseOp.XOR else: op = openum(opstr) return create_logic_exp(op, args) def _get_logic_args( - self, tree: Sequence[Union[Token, LogicExp]] - ) -> Tuple[List[Union[LogicExp, Bit, BitRegister, int]], Optional[int]]: - args: List[Union[LogicExp, Bit, BitRegister, int]] = [] + self, tree: Sequence[Token | LogicExp] + ) -> tuple[list[LogicExp | Bit | BitRegister | int], int | None]: + args: list[LogicExp | Bit | BitRegister | int] = [] line = None for tok in tree: if isinstance(tok, LogicExp): @@ -600,14 +591,14 @@ def _get_logic_args( div = lambda self, tree: self._logic_exp(tree, "/") ipow = lambda self, tree: self._logic_exp(tree, "**") - def neg(self, tree: List[Union[Token, LogicExp]]) -> RegNeg: + def neg(self, tree: list[Token | LogicExp]) -> RegNeg: arg = self._get_logic_args(tree)[0][0] assert isinstance(arg, (RegLogicExp, BitRegister, int)) return RegNeg(arg) - def cond(self, tree: List[Token]) -> PredicateExp: - op: Union[BitWiseOp, RegWiseOp] - arg: Union[Bit, BitRegister] + def cond(self, tree: list[Token]) -> PredicateExp: + op: BitWiseOp | RegWiseOp + arg: Bit | BitRegister if tree[1].type == "IARG": arg = Bit(*_extract_reg(tree[1])) op = BitWiseOp(str(tree[2])) @@ -679,9 +670,9 @@ def cop(self, tree: Sequence[Iterable[CommandDict]]) -> Iterable[CommandDict]: return tree[0] def _calc_exp_io( - self, exp: LogicExp, out_args: List - ) -> Tuple[List[List], Dict[str, Any]]: - all_inps: list[Tuple[str, int]] = [] + self, exp: LogicExp, out_args: list + ) -> tuple[list[list], dict[str, Any]]: + all_inps: list[tuple[str, int]] = [] for inp in exp.all_inputs_ordered(): if isinstance(inp, Bit): all_inps.append((inp.reg_name, inp.index[0])) @@ -709,7 +700,7 @@ def _calc_exp_io( } return exp_args, numbers_dict - def _cexpbox_dict(self, exp: LogicExp, args: List[List]) -> CommandDict: + def _cexpbox_dict(self, exp: LogicExp, args: list[list]) -> CommandDict: box = { "exp": exp.to_dict(), "id": str(uuid.uuid4()), @@ -725,7 +716,7 @@ def _cexpbox_dict(self, exp: LogicExp, args: List[List]) -> CommandDict: }, } - def _clexpr_dict(self, exp: LogicExp, out_args: List[List]) -> CommandDict: + def _clexpr_dict(self, exp: LogicExp, out_args: list[list]) -> CommandDict: # Convert the LogicExp to a serialization of a command containing the # corresponding ClExprOp. wexpr, args = wired_clexpr_from_logic_exp( @@ -740,7 +731,7 @@ def _clexpr_dict(self, exp: LogicExp, out_args: List[List]) -> CommandDict: } def _logic_exp_as_cmd_dict( - self, exp: LogicExp, out_args: List[List] + self, exp: LogicExp, out_args: list[list] ) -> CommandDict: return ( self._clexpr_dict(exp, out_args) @@ -748,14 +739,14 @@ def _logic_exp_as_cmd_dict( else self._cexpbox_dict(exp, out_args) ) - def assign(self, tree: List) -> Iterable[CommandDict]: + def assign(self, tree: list) -> Iterable[CommandDict]: child_iter = iter(tree) out_args = list(next(child_iter)) args_uids = list(self.unroll_all_args(out_args)) exp_tree = next(child_iter) - exp: Union[str, List, LogicExp, int] = "" + exp: str | list | LogicExp | int = "" line = None if isinstance(exp_tree, Token): if exp_tree.type == "INT": @@ -783,7 +774,7 @@ def assign(self, tree: List) -> Iterable[CommandDict]: assert len(out_args) == 1 out_arg = out_args[0] args = args_uids[0] - if isinstance(out_arg, List): + if isinstance(out_arg, list): if isinstance(exp, LogicExp): yield self._logic_exp_as_cmd_dict(exp, args) elif isinstance(exp, (int, bool)): @@ -792,7 +783,7 @@ def assign(self, tree: List) -> Iterable[CommandDict]: "args": args, "op": {"classical": {"values": [bool(exp)]}, "type": "SetBits"}, } - elif isinstance(exp, List): + elif isinstance(exp, list): yield { "args": [exp] + args, "op": {"classical": {"n_i": 1}, "type": "CopyBits"}, @@ -826,14 +817,14 @@ def assign(self, tree: List) -> Iterable[CommandDict]: else: raise QASMParseError(f"Unexpected expression in assignment {exp}", line) - def extern(self, tree: List[Any]) -> Any: + def extern(self, tree: list[Any]) -> Any: # TODO parse extern defs return Discard - def ccall(self, tree: List) -> Iterable[CommandDict]: + def ccall(self, tree: list) -> Iterable[CommandDict]: return self.cce_call(tree) - def cce_call(self, tree: List) -> Iterable[CommandDict]: + def cce_call(self, tree: list) -> Iterable[CommandDict]: nam = tree[0].value params = list(tree[1]) if self.wasm is None: @@ -862,11 +853,11 @@ def cce_call(self, tree: List) -> Iterable[CommandDict]: }, } - def transform(self, tree: Tree) -> Dict[str, Any]: + def transform(self, tree: Tree) -> dict[str, Any]: self._reset_context() - return cast(Dict[str, Any], super().transform(tree)) + return cast(dict[str, Any], super().transform(tree)) - def gdef(self, tree: List) -> None: + def gdef(self, tree: list) -> None: child_iter = iter(tree) gate = next(child_iter).value @@ -920,7 +911,7 @@ def gdef(self, tree: List) -> None: ) if not existing_op: gate_circ.symbol_substitution(symbol_map) - gate_circ.rename_units(cast(Dict[UnitID, UnitID], rename_map)) + gate_circ.rename_units(cast(dict[UnitID, UnitID], rename_map)) self.gate_dict[gate] = { "definition": gate_circ.to_dict(), @@ -930,15 +921,15 @@ def gdef(self, tree: List) -> None: opaq = gdef - def oqasm(self, tree: List) -> Any: + def oqasm(self, tree: list) -> Any: return Discard - def incl(self, tree: List[Token]) -> None: + def incl(self, tree: list[Token]) -> None: self.include = str(tree[0].value).split(".")[0] self.gate_dict.update(_load_include_module(self.include, True, False)) - def prog(self, tree: Iterable) -> Dict[str, Any]: - outdict: Dict[str, Any] = { + def prog(self, tree: Iterable) -> dict[str, Any]: + outdict: dict[str, Any] = { "commands": list( chain.from_iterable( filter(lambda x: x is not None and x is not Discard, tree) @@ -987,7 +978,7 @@ def circuit_from_qasm( input_file: Union[str, "os.PathLike[Any]"], encoding: str = "utf-8", maxwidth: int = 32, - use_clexpr: bool = False, + use_clexpr: bool = True, ) -> Circuit: """A method to generate a tket Circuit from a qasm file. @@ -1000,7 +991,7 @@ def circuit_from_qasm( ext = os.path.splitext(input_file)[-1] if ext != ".qasm": raise TypeError("Can only convert .qasm files") - with open(input_file, "r", encoding=encoding) as f: + with open(input_file, encoding=encoding) as f: try: circ = circuit_from_qasm_io(f, maxwidth=maxwidth, use_clexpr=use_clexpr) except QASMParseError as e: @@ -1009,7 +1000,7 @@ def circuit_from_qasm( def circuit_from_qasm_str( - qasm_str: str, maxwidth: int = 32, use_clexpr: bool = False + qasm_str: str, maxwidth: int = 32, use_clexpr: bool = True ) -> Circuit: """A method to generate a tket Circuit from a qasm string. @@ -1024,11 +1015,15 @@ def circuit_from_qasm_str( cast(CircuitTransformer, g_parser.options.transformer)._reset_context( reset_wasm=False ) - return Circuit.from_dict(g_parser.parse(qasm_str)) # type: ignore[arg-type] + + circ = Circuit.from_dict(g_parser.parse(qasm_str)) # type: ignore[arg-type] + cpass = scratch_reg_resize_pass(maxwidth) + cpass.apply(circ) + return circ def circuit_from_qasm_io( - stream_in: TextIO, maxwidth: int = 32, use_clexpr: bool = False + stream_in: TextIO, maxwidth: int = 32, use_clexpr: bool = True ) -> Circuit: """A method to generate a tket Circuit from a qasm text stream""" return circuit_from_qasm_str( @@ -1041,7 +1036,7 @@ def circuit_from_qasm_wasm( wasm_file: Union[str, "os.PathLike[Any]"], encoding: str = "utf-8", maxwidth: int = 32, - use_clexpr: bool = False, + use_clexpr: bool = True, ) -> Circuit: """A method to generate a tket Circuit from a qasm string and external WASM module. @@ -1083,8 +1078,8 @@ def _filtered_qasm_str(qasm: str) -> str: # remove any c registers starting with _TEMP_BIT_NAME # that are not being used somewhere else lines = qasm.split("\n") - def_matcher = re.compile(r"creg ({}\_*\d*)\[\d+\]".format(_TEMP_BIT_NAME)) - arg_matcher = re.compile(r"({}\_*\d*)\[\d+\]".format(_TEMP_BIT_NAME)) + def_matcher = re.compile(rf"creg ({_TEMP_BIT_NAME}\_*\d*)\[\d+\]") + arg_matcher = re.compile(rf"({_TEMP_BIT_NAME}\_*\d*)\[\d+\]") unused_regs = dict() for i, line in enumerate(lines): if reg := def_matcher.match(line): @@ -1129,19 +1124,28 @@ def check_can_convert_circuit(circ: Circuit, header: str, maxwidth: int) -> None f"Circuit contains a classical register larger than {maxwidth}: try " "setting the `maxwidth` parameter to a higher value." ) - for cmd in circ: - if is_empty_customgate(cmd.op) or ( - isinstance(cmd.op, Conditional) and is_empty_customgate(cmd.op.op) - ): + set_circ_register = set([creg.name for creg in circ.c_registers]) + for b in circ.bits: + if b.reg_name not in set_circ_register: raise QASMUnsupportedError( - f"Empty CustomGates and opaque gates are not supported." + f"Circuit contains an invalid classical register {b.reg_name}." ) + # Empty CustomGates should have been removed by DecomposeBoxes(). + for cmd in circ: + assert not is_empty_customgate(cmd.op) + if isinstance(cmd.op, Conditional): + assert not is_empty_customgate(cmd.op.op) + if not check_register_alignments(circ): + raise QASMUnsupportedError( + "Circuit contains classical expressions on registers whose arguments or " + "outputs are not register-aligned." + ) def circuit_to_qasm_str( circ: Circuit, header: str = "qelib1", - include_gate_defs: Optional[Set[str]] = None, + include_gate_defs: set[str] | None = None, maxwidth: int = 32, ) -> str: """Convert a Circuit to QASM and return the string. @@ -1158,12 +1162,12 @@ def circuit_to_qasm_str( :return: qasm string """ - check_can_convert_circuit(circ, header, maxwidth) qasm_writer = QasmWriter( circ.qubits, circ.bits, header, include_gate_defs, maxwidth ) circ1 = circ.copy() DecomposeBoxes().apply(circ1) + check_can_convert_circuit(circ1, header, maxwidth) for command in circ1: assert isinstance(command, Command) qasm_writer.add_op(command.op, command.args) @@ -1174,8 +1178,8 @@ def circuit_to_qasm_str( def _retrieve_registers( - units: List[UnitID], reg_type: Type[TypeReg] -) -> Dict[str, TypeReg]: + units: list[UnitID], reg_type: type[TypeReg] +) -> dict[str, TypeReg]: if any(len(unit.index) != 1 for unit in units): raise NotImplementedError("OPENQASM registers must use a single index") maxunits = map(lambda x: max(x[1]), groupby(units, key=lambda un: un.reg_name)) @@ -1185,7 +1189,7 @@ def _retrieve_registers( } -def _parse_range(minval: int, maxval: int, maxwidth: int) -> Tuple[str, int]: +def _parse_range(minval: int, maxval: int, maxwidth: int) -> tuple[str, int]: if maxwidth > 64: raise NotImplementedError("Register width exceeds maximum of 64.") @@ -1193,38 +1197,35 @@ def _parse_range(minval: int, maxval: int, maxwidth: int) -> Tuple[str, int]: if minval > REGMAX: raise NotImplementedError("Range's lower bound exceeds register capacity.") - elif minval > maxval: + if minval > maxval: raise NotImplementedError("Range's lower bound exceeds upper bound.") - elif maxval > REGMAX: - maxval = REGMAX + maxval = min(maxval, REGMAX) if minval == maxval: return ("==", minval) - elif minval == 0: + if minval == 0: return ("<=", maxval) - elif maxval == REGMAX: + if maxval == REGMAX: return (">=", minval) - else: - raise NotImplementedError("Range can only be bounded on one side.") + raise NotImplementedError("Range can only be bounded on one side.") def _negate_comparator(comparator: str) -> str: if comparator == "==": return "!=" - elif comparator == "!=": + if comparator == "!=": return "==" - elif comparator == "<=": + if comparator == "<=": return ">" - elif comparator == ">": + if comparator == ">": return "<=" - elif comparator == ">=": + if comparator == ">=": return "<" - else: - assert comparator == "<" - return ">=" + assert comparator == "<" + return ">=" -def _get_optype_and_params(op: Op) -> Tuple[OpType, Optional[List[Union[float, Expr]]]]: +def _get_optype_and_params(op: Op) -> tuple[OpType, list[float | Expr] | None]: optype = op.type params = ( op.params @@ -1241,10 +1242,10 @@ def _get_optype_and_params(op: Op) -> Tuple[OpType, Optional[List[Union[float, E def _get_gate_circuit( - optype: OpType, qubits: List[Qubit], symbols: Optional[List[Symbol]] = None + optype: OpType, qubits: list[Qubit], symbols: list[Symbol] | None = None ) -> Circuit: # create Circuit for constructing qasm from - unitids = cast(List[UnitID], qubits) + unitids = cast(list[UnitID], qubits) gate_circ = Circuit() for q in qubits: gate_circ.add_qubit(q) @@ -1282,7 +1283,7 @@ class LabelledStringList: def __init__(self) -> None: self.strings: OrderedDict[int, str] = OrderedDict() - self.conditions: Dict[int, ConditionString] = dict() + self.conditions: dict[int, ConditionString] = dict() self.label = 0 def add_string(self, string: str) -> int: @@ -1291,7 +1292,7 @@ def add_string(self, string: str) -> int: self.label += 1 return label - def get_string(self, label: int) -> Optional[str]: + def get_string(self, label: int) -> str | None: return self.strings.get(label, None) def del_string(self, label: int) -> None: @@ -1311,7 +1312,7 @@ def get_full_string(self) -> str: return "".join(strings) -def make_params_str(params: Optional[List[Union[float, Expr]]]) -> str: +def make_params_str(params: list[float | Expr] | None) -> str: s = "" if params is not None: n_params = len(params) @@ -1319,20 +1320,19 @@ def make_params_str(params: Optional[List[Union[float, Expr]]]) -> str: for i in range(n_params): reduced = True try: - p: Union[float, Expr] = float(params[i]) + p: float | Expr = float(params[i]) except TypeError: reduced = False p = params[i] if i < n_params - 1: if reduced: - s += "{}*pi,".format(p) + s += f"{p}*pi," else: - s += "({})*pi,".format(p) + s += f"({p})*pi," + elif reduced: + s += f"{p}*pi)" else: - if reduced: - s += "{}*pi)".format(p) - else: - s += "({})*pi)".format(p) + s += f"({p})*pi)" s += " " return s @@ -1374,14 +1374,13 @@ def _var_appears(v: str, s: str) -> bool: # check if v appears in s and is not surrounded by word characters # e.g. a = a & b or a = a[1] & b[1] return bool(re.search(r"(? Bit: self.scratch_reg = BitRegister(self.scratch_reg.name, self.scratch_reg.size + 1) @@ -1468,7 +1467,7 @@ def remove_last_scratch_bit(self) -> None: assert self.scratch_reg.size > 0 self.scratch_reg = BitRegister(self.scratch_reg.name, self.scratch_reg.size - 1) - def write_params(self, params: Optional[List[Union[float, Expr]]]) -> None: + def write_params(self, params: list[float | Expr] | None) -> None: params_str = make_params_str(params) self.strings.add_string(params_str) @@ -1481,10 +1480,10 @@ def make_gate_definition( n_qubits: int, opstr: str, optype: OpType, - n_params: Optional[int] = None, + n_params: int | None = None, ) -> str: s = "gate " + opstr + " " - symbols: Optional[List[Symbol]] = None + symbols: list[Symbol] | None = None if n_params is not None: # need to add parameters to gate definition s += "(" @@ -1518,31 +1517,33 @@ def mark_as_written(self, label: int, written_variable: str) -> None: else: self.variable_writes[label] = [written_variable] - def add_range_predicate(self, op: RangePredicateOp, args: List[Bit]) -> None: - comparator, value = _parse_range(op.lower, op.upper, self.maxwidth) - if (not hqs_header(self.header)) and comparator != "==": + def check_range_predicate(self, op: RangePredicateOp, args: list[Bit]) -> None: + if (not hqs_header(self.header)) and op.lower != op.upper: raise QASMUnsupportedError( "OpenQASM conditions must be on a register's fixed value." ) - bits = args[:-1] + variable = args[0].reg_name + assert isinstance(variable, str) + if op.n_inputs != self.cregs[variable].size: + raise QASMUnsupportedError( + "RangePredicate conditions must be an entire classical register" + ) + if args[:-1] != self.cregs[variable].to_list(): + raise QASMUnsupportedError( + "RangePredicate conditions must be a single classical register" + ) + + def add_range_predicate(self, op: RangePredicateOp, args: list[Bit]) -> None: + self.check_range_predicate(op, args) + comparator, value = _parse_range(op.lower, op.upper, self.maxwidth) variable = args[0].reg_name dest_bit = str(args[-1]) - if not hqs_header(self.header): - assert isinstance(variable, str) - if op.n_inputs != self.cregs[variable].size: - raise QASMUnsupportedError( - "OpenQASM conditions must be an entire classical register" - ) - if bits != self.cregs[variable].to_list(): - raise QASMUnsupportedError( - "OpenQASM conditions must be a single classical register" - ) label = self.strings.add_string( "".join( [ f"if({variable}{comparator}{value}) " + f"{dest_bit} = 1;\n", f"if({variable}{_negate_comparator(comparator)}{value}) " - + f"{dest_bit} = 0;\n", + f"{dest_bit} = 0;\n", ] ) ) @@ -1576,9 +1577,9 @@ def replace_condition(self, pred_label: int) -> bool: line_labels.append(label) if "\n" not in string: continue - written_variables: List[str] = [] + written_variables: list[str] = [] # (label, condition) - conditions: List[Tuple[int, ConditionString]] = [] + conditions: list[tuple[int, ConditionString]] = [] for l in line_labels: written_variables.extend(self.variable_writes.get(l, [])) cond = self.strings.conditions.get(l) @@ -1653,9 +1654,42 @@ def add_conditional(self, op: Conditional, args: Sequence[UnitID]) -> None: # Conditional phase is ignored. return if op.op.type == OpType.RangePredicate: - raise QASMUnsupportedError( - "Conditional RangePredicate is currently unsupported." + # Special handling for nested ifs + # if condition + # if pred dest = 1 + # if not pred dest = 0 + # can be written as + # if condition s0 = 1 + # if pred s1 = 1 + # s2 = s0 & s1 + # s3 = s0 & ~s1 + # if s2 dest = 1 + # if s3 dest = 0 + # where s0, s1, s2, and s3 are scratch bits + s0 = self.fresh_scratch_bit() + l = self.strings.add_string(f"{s0} = 1;\n") + # we store the condition in self.strings.conditions + # as it can be later replaced by `replace_condition` + # if possible + self.strings.conditions[l] = ConditionString(variable, "==", op.value) + # output the RangePredicate to s1 + s1 = self.fresh_scratch_bit() + assert isinstance(op.op, RangePredicateOp) + self.check_range_predicate(op.op, cast(list[Bit], args[op.width :])) + pred_comparator, pred_value = _parse_range( + op.op.lower, op.op.upper, self.maxwidth ) + pred_variable = args[op.width :][0].reg_name + self.strings.add_string( + f"if({pred_variable}{pred_comparator}{pred_value}) {s1} = 1;\n" + ) + s2 = self.fresh_scratch_bit() + self.strings.add_string(f"{s2} = {s0} & {s1};\n") + s3 = self.fresh_scratch_bit() + self.strings.add_string(f"{s3} = {s0} & (~ {s1});\n") + self.strings.add_string(f"if({s2}==1) {args[-1]} = 1;\n") + self.strings.add_string(f"if({s3}==1) {args[-1]} = 0;\n") + return # we assign the condition to a scratch bit, which we will later remove # if the condition variable is unchanged. scratch_bit = self.fresh_scratch_bit() @@ -1684,7 +1718,7 @@ def add_conditional(self, op: Conditional, args: Sequence[UnitID]) -> None: # remove the unused scratch bit self.remove_last_scratch_bit() - def add_set_bits(self, op: SetBitsOp, args: List[Bit]) -> None: + def add_set_bits(self, op: SetBitsOp, args: list[Bit]) -> None: creg_name = args[0].reg_name bits, vals = zip(*sorted(zip(args, op.values))) # check if whole register can be set at once @@ -1697,7 +1731,7 @@ def add_set_bits(self, op: SetBitsOp, args: List[Bit]) -> None: label = self.strings.add_string(f"{bit} = {int(value)};\n") self.mark_as_written(label, f"{bit}") - def add_copy_bits(self, op: CopyBitsOp, args: List[Bit]) -> None: + def add_copy_bits(self, op: CopyBitsOp, args: list[Bit]) -> None: l_args = args[op.n_inputs :] r_args = args[: op.n_inputs] l_name = l_args[0].reg_name @@ -1714,7 +1748,7 @@ def add_copy_bits(self, op: CopyBitsOp, args: List[Bit]) -> None: label = self.strings.add_string(f"{bit_l} = {bit_r};\n") self.mark_as_written(label, f"{bit_l}") - def add_multi_bit(self, op: MultiBitOp, args: List[Bit]) -> None: + def add_multi_bit(self, op: MultiBitOp, args: list[Bit]) -> None: basic_op = op.basic_op basic_n = basic_op.n_inputs + basic_op.n_outputs + basic_op.n_input_outputs n_args = len(args) @@ -1734,7 +1768,7 @@ def add_multi_bit(self, op: MultiBitOp, args: List[Bit]) -> None: basic_args = args[basic_n * i : basic_n * (i + 1)] self.add_op(basic_op, basic_args) - def add_explicit_op(self, op: Op, args: List[Bit]) -> None: + def add_explicit_op(self, op: Op, args: list[Bit]) -> None: # &, ^ and | gates opstr = str(op) if opstr not in _classical_gatestr_map: @@ -1744,10 +1778,10 @@ def add_explicit_op(self, op: Op, args: List[Bit]) -> None: ) self.mark_as_written(label, f"{args[-1]}") - def add_classical_exp_box(self, op: ClassicalExpBox, args: List[Bit]) -> None: + def add_classical_exp_box(self, op: ClassicalExpBox, args: list[Bit]) -> None: out_args = args[op.get_n_i() :] if len(out_args) == 1: - label = self.strings.add_string(f"{out_args[0]} = {str(op.get_exp())};\n") + label = self.strings.add_string(f"{out_args[0]} = {op.get_exp()!s};\n") self.mark_as_written(label, f"{out_args[0]}") elif ( out_args @@ -1756,16 +1790,16 @@ def add_classical_exp_box(self, op: ClassicalExpBox, args: List[Bit]) -> None: ] ): label = self.strings.add_string( - f"{out_args[0].reg_name} = {str(op.get_exp())};\n" + f"{out_args[0].reg_name} = {op.get_exp()!s};\n" ) self.mark_as_written(label, f"{out_args[0].reg_name}") else: raise QASMUnsupportedError( - f"ClassicalExpBox only supported" + "ClassicalExpBox only supported" " for writing to a single bit or whole registers." ) - def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: + def add_wired_clexpr(self, op: ClExprOp, args: list[Bit]) -> None: wexpr: WiredClExpr = op.expr # 1. Determine the mappings from bit variables to bits and from register # variables to registers. @@ -1783,18 +1817,18 @@ def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: input_regs[i] = creg break else: - raise QASMUnsupportedError( - f"ClExprOp ({wexpr}) contains a register variable (r{i}) " - "that is not wired to any BitRegister in the circuit." + assert ( + not f"ClExprOp ({wexpr}) contains a register variable (r{i}) that " + "is not wired to any BitRegister in the circuit." ) # 2. Write the left-hand side of the assignment. - output_repr: Optional[str] = None + output_repr: str | None = None output_args: list[Bit] = [args[j] for j in output_posn] n_output_args = len(output_args) expect_reg_output = has_reg_output(expr.op) if n_output_args == 0: raise QASMUnsupportedError("Expression has no output.") - elif n_output_args == 1: + if n_output_args == 1: output_arg = output_args[0] output_repr = output_arg.reg_name if expect_reg_output else str(output_arg) else: @@ -1803,6 +1837,8 @@ def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: for creg in all_cregs: if creg.to_list() == output_args: output_repr = creg.name + break + assert output_repr is not None self.strings.add_string(f"{output_repr} = ") # 3. Write the right-hand side of the assignment. self.strings.add_string( @@ -1810,9 +1846,9 @@ def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: ) self.strings.add_string(";\n") - def add_wasm(self, op: WASMOp, args: List[Bit]) -> None: - inputs: List[str] = [] - outputs: List[str] = [] + def add_wasm(self, op: WASMOp, args: list[Bit]) -> None: + inputs: list[str] = [] + outputs: list[str] = [] for reglist, sizes in [(inputs, op.input_widths), (outputs, op.output_widths)]: for in_width in sizes: bits = args[:in_width] @@ -1831,7 +1867,7 @@ def add_measure(self, args: Sequence[UnitID]) -> None: label = self.strings.add_string(f"measure {args[0]} -> {args[1]};\n") self.mark_as_written(label, f"{args[1]}") - def add_zzphase(self, param: Union[float, Expr], args: Sequence[UnitID]) -> None: + def add_zzphase(self, param: float | Expr, args: Sequence[UnitID]) -> None: # as op.params returns reduced parameters, we can assume # that 0 <= param < 4 if param > 1: @@ -1865,7 +1901,7 @@ def add_gate_params(self, op: Op, args: Sequence[UnitID]) -> None: self.write_params(params) self.write_args(args) - def add_extra_noparams(self, op: Op, args: Sequence[UnitID]) -> Tuple[str, str]: + def add_extra_noparams(self, op: Op, args: Sequence[UnitID]) -> tuple[str, str]: optype = op.type opstr = _tk_to_qasm_extra_noparams[optype] gatedefstr = "" @@ -1875,7 +1911,7 @@ def add_extra_noparams(self, op: Op, args: Sequence[UnitID]) -> Tuple[str, str]: mainstr = opstr + " " + make_args_str(args) return gatedefstr, mainstr - def add_extra_params(self, op: Op, args: Sequence[UnitID]) -> Tuple[str, str]: + def add_extra_params(self, op: Op, args: Sequence[UnitID]) -> tuple[str, str]: optype, params = _get_optype_and_params(op) assert params is not None opstr = _tk_to_qasm_extra_params[optype] @@ -1892,7 +1928,7 @@ def add_op(self, op: Op, args: Sequence[UnitID]) -> None: optype, _params = _get_optype_and_params(op) if optype == OpType.RangePredicate: assert isinstance(op, RangePredicateOp) - self.add_range_predicate(op, cast(List[Bit], args)) + self.add_range_predicate(op, cast(list[Bit], args)) elif optype == OpType.Conditional: assert isinstance(op, Conditional) self.add_conditional(op, args) @@ -1901,24 +1937,24 @@ def add_op(self, op: Op, args: Sequence[UnitID]) -> None: pass elif optype == OpType.SetBits: assert isinstance(op, SetBitsOp) - self.add_set_bits(op, cast(List[Bit], args)) + self.add_set_bits(op, cast(list[Bit], args)) elif optype == OpType.CopyBits: assert isinstance(op, CopyBitsOp) - self.add_copy_bits(op, cast(List[Bit], args)) + self.add_copy_bits(op, cast(list[Bit], args)) elif optype == OpType.MultiBit: assert isinstance(op, MultiBitOp) - self.add_multi_bit(op, cast(List[Bit], args)) + self.add_multi_bit(op, cast(list[Bit], args)) elif optype in (OpType.ExplicitPredicate, OpType.ExplicitModifier): - self.add_explicit_op(op, cast(List[Bit], args)) + self.add_explicit_op(op, cast(list[Bit], args)) elif optype == OpType.ClassicalExpBox: assert isinstance(op, ClassicalExpBox) - self.add_classical_exp_box(op, cast(List[Bit], args)) + self.add_classical_exp_box(op, cast(list[Bit], args)) elif optype == OpType.ClExpr: assert isinstance(op, ClExprOp) - self.add_wired_clexpr(op, cast(List[Bit], args)) + self.add_wired_clexpr(op, cast(list[Bit], args)) elif optype == OpType.WASM: assert isinstance(op, WASMOp) - self.add_wasm(op, cast(List[Bit], args)) + self.add_wasm(op, cast(list[Bit], args)) elif optype == OpType.Measure: self.add_measure(args) elif hqs_header(self.header) and optype == OpType.ZZPhase: @@ -1947,9 +1983,7 @@ def add_op(self, op: Op, args: Sequence[UnitID]) -> None: self.gatedefs += gatedefstr self.strings.add_string(mainstr) else: - raise QASMUnsupportedError( - "Cannot print command of type: {}".format(op.get_name()) - ) + raise QASMUnsupportedError(f"Cannot print command of type: {op.get_name()}") def finalize(self) -> str: # try removing unused predicates @@ -1981,7 +2015,7 @@ def circuit_to_qasm_io( circ: Circuit, stream_out: TextIO, header: str = "qelib1", - include_gate_defs: Optional[Set[str]] = None, + include_gate_defs: set[str] | None = None, maxwidth: int = 32, ) -> None: """Convert a Circuit to QASM and write to a text stream. diff --git a/pytket/pytket/quipper/quipper.py b/pytket/pytket/quipper/quipper.py index 5ec0e20736..57ad639bd0 100644 --- a/pytket/pytket/quipper/quipper.py +++ b/pytket/pytket/quipper/quipper.py @@ -12,24 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. -from enum import unique, Enum -from typing import List, NamedTuple, Tuple +from enum import Enum, unique from math import pi +from typing import NamedTuple from lark import Lark, Transformer, Tree -from pytket.circuit import Circuit, OpType, CircBox + +from pytket.circuit import CircBox, Circuit, OpType # The Lark grammar, transformer and type definitions below are adapted from the # code in Eddie Schoute's `quippy` project # (https://github.com/eddieschoute/quippy). The main enhancements are support # for multi-qubit gates and correct handling of negative controls on qubit 0. + # Types -Wire = NamedTuple("Wire", [("i", int)]) -ControlWire = NamedTuple("ControlWire", [("wire", Wire), ("negative", bool)]) -Control = NamedTuple( - "Control", [("controlled", List[ControlWire]), ("no_control", bool)] -) +class Wire(NamedTuple): + i: int + + +class ControlWire(NamedTuple): + wire: Wire + negative: bool + + +class Control(NamedTuple): + controlled: list[ControlWire] + no_control: bool @unique @@ -38,9 +47,9 @@ class TypeAssignment_Type(Enum): Cbit = 2 -TypeAssignment = NamedTuple( - "TypeAssignment", [("wire", Wire), ("type", TypeAssignment_Type)] -) +class TypeAssignment(NamedTuple): + wire: Wire + type: TypeAssignment_Type class Gate: @@ -71,7 +80,7 @@ class QGate( [ ("op", QGate_Op), ("inverted", bool), - ("wires", List[Wire]), + ("wires", list[Wire]), ("control", Control), ], ), @@ -132,8 +141,8 @@ class SubroutineCall( ("name", str), ("shape", str), ("inverted", bool), - ("inputs", List[Wire]), - ("outputs", List[Wire]), + ("inputs", list[Wire]), + ("outputs", list[Wire]), ("control", Control), ], ), @@ -148,21 +157,17 @@ class Comment( [ ("comment", str), ("inverted", bool), - ("wire_comments", List[Tuple[Wire, str]]), + ("wire_comments", list[tuple[Wire, str]]), ], ), ): pass -Program = NamedTuple( - "Program", - [ - ("inputs", List[TypeAssignment]), - ("gates", List[Gate]), - ("outputs", List[TypeAssignment]), - ], -) +class Program(NamedTuple): + inputs: list[TypeAssignment] + gates: list[Gate] + outputs: list[TypeAssignment] @unique @@ -172,55 +177,55 @@ class Subroutine_Control(Enum): classically = 3 -Subroutine = NamedTuple( - "Subroutine", - [ - ("name", str), - ("shape", str), - ("controllable", Subroutine_Control), - ("circuit", Program), - ], -) -Start = NamedTuple("Start", [("circuit", Program), ("subroutines", List[Subroutine])]) +class Subroutine(NamedTuple): + name: str + shape: str + controllable: Subroutine_Control + circuit: Program + + +class Start(NamedTuple): + circuit: Program + subroutines: list[Subroutine] # Transformer class QuipperTransformer(Transformer): - def int(self, t: List) -> int: + def int(self, t: list) -> int: return int(t[0]) - def float(self, t: List) -> float: + def float(self, t: list) -> float: return float(t[0]) - def string(self, t: List) -> str: + def string(self, t: list) -> str: return str(t[0][1:-1]) - def wire(self, t: List) -> Wire: + def wire(self, t: list) -> Wire: return Wire(t[0]) wire_list = list - def wire_string_list(self, t: List) -> List[Tuple[Wire, str]]: + def wire_string_list(self, t: list) -> list[tuple[Wire, str]]: wires = (el for i, el in enumerate(t) if i % 2 == 0) labels = (el for i, el in enumerate(t) if i % 2 == 1) return list(zip(wires, labels)) - def pos_control_wire(self, t: List) -> ControlWire: + def pos_control_wire(self, t: list) -> ControlWire: return ControlWire(t[0], False) control_wire_list = list - def neg_control_wire(self, t: List) -> ControlWire: + def neg_control_wire(self, t: list) -> ControlWire: return ControlWire(t[0], True) - def type_assignment(self, t: List) -> TypeAssignment: + def type_assignment(self, t: list) -> TypeAssignment: ty = TypeAssignment_Type.Qbit if t[1] == "Qbit" else TypeAssignment_Type.Cbit return TypeAssignment(t[0], ty) - def arity(self, t: List) -> List[Tree]: + def arity(self, t: list) -> list[Tree]: return list(t) - def qgate(self, t: List) -> QGate: + def qgate(self, t: list) -> QGate: ops = QGate_Op n = t[0] if n == "not" or n == "x" or n == "X": @@ -250,41 +255,41 @@ def qgate(self, t: List) -> QGate: elif n == "W": op = ops.W else: - raise RuntimeError("Unknown QGate operation: {}".format(n)) + raise RuntimeError(f"Unknown QGate operation: {n}") return QGate(op=op, inverted=len(t[1].children) > 0, wires=t[2], control=t[3]) - def qrot1(self, t: List) -> QRot: + def qrot1(self, t: list) -> QRot: return QRot( op=QRot_Op.ExpZt, timestep=t[0], inverted=len(t[1].children) > 0, wire=t[2] ) - def qrot2(self, t: List) -> QRot: + def qrot2(self, t: list) -> QRot: return QRot( op=QRot_Op.R, timestep=t[0], inverted=len(t[1].children) > 0, wire=t[2] ) - def qinit(self, t: List) -> QInit: + def qinit(self, t: list) -> QInit: return QInit(value=(t[0] == "QInit1"), wire=t[1]) - def cinit(self, t: List) -> CInit: + def cinit(self, t: list) -> CInit: return CInit(value=(t[0] == "CInit1"), wire=t[1]) - def qterm(self, t: List) -> QTerm: + def qterm(self, t: list) -> QTerm: return QTerm(value=(t[0] == "QTerm1"), wire=t[1]) - def cterm(self, t: List) -> CTerm: + def cterm(self, t: list) -> CTerm: return CTerm(value=(t[0] == "CTerm1"), wire=t[1]) - def qmeas(self, t: List) -> QMeas: + def qmeas(self, t: list) -> QMeas: return QMeas(wire=t[0]) - def qdiscard(self, t: List) -> QDiscard: + def qdiscard(self, t: list) -> QDiscard: return QDiscard(wire=t[0]) - def cdiscard(self, t: List) -> CDiscard: + def cdiscard(self, t: list) -> CDiscard: return CDiscard(wire=t[0]) - def subroutine_call(self, t: List) -> SubroutineCall: + def subroutine_call(self, t: list) -> SubroutineCall: repetitions = 1 if t[0] is not None: assert isinstance(t[0], int) @@ -299,13 +304,13 @@ def subroutine_call(self, t: List) -> SubroutineCall: control=t[6], ) - def comment(self, t: List) -> Comment: + def comment(self, t: list) -> Comment: wire_comments = t[2] if len(t) > 2 else [] return Comment( comment=t[0], inverted=len(t[1].children) > 0, wire_comments=wire_comments ) - def control_app(self, t: List) -> Control: + def control_app(self, t: list) -> Control: if not t: return Control(controlled=list(), no_control=False) if len(t) == 2: @@ -314,10 +319,10 @@ def control_app(self, t: List) -> Control: return Control(controlled=list(), no_control=True) return Control(controlled=t[0], no_control=False) - def circuit(self, t: List) -> Program: + def circuit(self, t: list) -> Program: return Program(inputs=t[0], gates=t[1:-1], outputs=t[-1]) - def subroutine(self, t: List) -> Subroutine: + def subroutine(self, t: list) -> Subroutine: if t[2] == "yes": controllable = Subroutine_Control.yes elif t[2] == "no": @@ -328,7 +333,7 @@ def subroutine(self, t: List) -> Subroutine: name=t[0], shape=t[1], controllable=controllable, circuit=t[3] ) - def start(self, t: List) -> Start: + def start(self, t: list) -> Start: circuit = t.pop(0) return Start(circuit, list(t)) @@ -337,16 +342,15 @@ def start(self, t: List) -> Start: def allowed(op: str, arity: int) -> bool: if op in ["Not", "IX", "H", "Y", "Z", "S", "T", "E", "Omega", "V"]: return arity == 1 - elif op in ["Swap", "W"]: + if op in ["Swap", "W"]: return arity == 2 - else: - # MultiNot - return True + # MultiNot + return True # Class for constructing a pytket Circuit from a parsed Quipper program class CircuitMaker: - def __init__(self, subr: List[Subroutine]) -> None: + def __init__(self, subr: list[Subroutine]) -> None: self.subrd = dict((s.name, s) for s in subr) if len(self.subrd) != len(subr): raise TypeError("Repeated subroutine names") @@ -624,7 +628,7 @@ def circuit_from_quipper(input_file: str) -> Circuit: """ # Read Quipper program from file. - with open(input_file, "r") as f: + with open(input_file) as f: quip = f.read() # Parse the circuit using the QuipperTransformer. diff --git a/pytket/pytket/transform/__init__.py b/pytket/pytket/transform/__init__.py index cce39aebbd..4929ae900a 100644 --- a/pytket/pytket/transform/__init__.py +++ b/pytket/pytket/transform/__init__.py @@ -17,5 +17,5 @@ Exports class Transform """ -from pytket._tket.transform import * from pytket._tket.circuit import CXConfigType +from pytket._tket.transform import * diff --git a/pytket/pytket/unit_id/__init__.py b/pytket/pytket/unit_id/__init__.py index 5b3961a31d..b1016c6851 100644 --- a/pytket/pytket/unit_id/__init__.py +++ b/pytket/pytket/unit_id/__init__.py @@ -13,9 +13,14 @@ # limitations under the License. from pytket._tket.unit_id import * -from pytket._tket.unit_id import Bit, BitRegister, Qubit, QubitRegister -from pytket._tket.unit_id import _TEMP_BIT_NAME -from pytket._tket.unit_id import _TEMP_BIT_REG_BASE +from pytket._tket.unit_id import ( + _TEMP_BIT_NAME, + _TEMP_BIT_REG_BASE, + Bit, + BitRegister, + Qubit, + QubitRegister, +) def _bitregister_next(self: BitRegister) -> Bit: @@ -23,8 +28,7 @@ def _bitregister_next(self: BitRegister) -> Bit: result = self[self._current] self._current += 1 return result - else: - raise StopIteration + raise StopIteration def _qubitregister_next(self: QubitRegister) -> Qubit: @@ -32,8 +36,7 @@ def _qubitregister_next(self: QubitRegister) -> Qubit: result = self[self._current] self._current += 1 return result - else: - raise StopIteration + raise StopIteration setattr(BitRegister, "__next__", _bitregister_next) diff --git a/pytket/pytket/utils/__init__.py b/pytket/pytket/utils/__init__.py index 9d07a45e64..7261395951 100644 --- a/pytket/pytket/utils/__init__.py +++ b/pytket/pytket/utils/__init__.py @@ -14,32 +14,32 @@ """Utility functions for performing high-level procedures in pytket""" +from .distribution import ( + EmpiricalDistribution, + ProbabilityDistribution, + convex_combination, +) from .expectations import ( - expectation_from_shots, expectation_from_counts, - get_pauli_expectation_value, + expectation_from_shots, get_operator_expectation_value, + get_pauli_expectation_value, ) +from .graph import Graph from .measurements import append_pauli_measurement +from .operators import QubitPauliOperator +from .outcomearray import OutcomeArray, readout_counts from .prepare import prepare_circuit from .results import ( + compare_statevectors, + compare_unitaries, counts_from_shot_table, - probs_from_counts, - probs_from_state, - permute_qubits_in_statevector, permute_basis_indexing, + permute_qubits_in_statevector, permute_rows_cols_in_unitary, - compare_statevectors, - compare_unitaries, -) -from .term_sequence import gen_term_sequence_circuit -from .operators import QubitPauliOperator -from .outcomearray import OutcomeArray, readout_counts -from .graph import Graph -from .symbolic import circuit_to_symbolic_unitary, circuit_apply_symbolic_statevector -from .distribution import ( - ProbabilityDistribution, - EmpiricalDistribution, - convex_combination, + probs_from_counts, + probs_from_state, ) from .stats import gate_counts +from .symbolic import circuit_apply_symbolic_statevector, circuit_to_symbolic_unitary +from .term_sequence import gen_term_sequence_circuit diff --git a/pytket/pytket/utils/distribution.py b/pytket/pytket/utils/distribution.py index 9985f6c80d..1b3e1dec6d 100644 --- a/pytket/pytket/utils/distribution.py +++ b/pytket/pytket/utils/distribution.py @@ -12,21 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import defaultdict -from typing import ( - Any, - Callable, - DefaultDict, - Dict, - List, - Set, - Tuple, - Union, - Generic, - TypeVar, - Counter, -) import warnings +from collections import Counter, defaultdict +from collections.abc import Callable +from typing import Any, Generic, TypeVar, Union + import numpy as np from scipy.stats import rv_discrete @@ -67,7 +57,7 @@ def total(self) -> int: return self._C.total() @property - def support(self) -> Set[T0]: + def support(self) -> set[T0]: """Return the support of the distribution (set of all observations).""" return set(self._C.keys()) @@ -78,7 +68,7 @@ def __eq__(self, other: object) -> bool: return self._C == other._C def __repr__(self) -> str: - return f"{self.__class__.__name__}({repr(self._C)})" + return f"{self.__class__.__name__}({self._C!r})" def __getitem__(self, x: T0) -> int: """Get the count associated with an observation.""" @@ -150,7 +140,7 @@ class ProbabilityDistribution(Generic[T0]): derived from an :py:class:`EmpriricalDistribution`. """ - def __init__(self, P: Dict[T0, float], min_p: float = 0.0): + def __init__(self, P: dict[T0, float], min_p: float = 0.0): """Initialize with a dictionary of probabilities. :param P: Dictionary of probabilities. @@ -174,11 +164,11 @@ def __init__(self, P: Dict[T0, float], min_p: float = 0.0): s = sum(newP.values()) self._P = {x: p / s for x, p in newP.items()} - def as_dict(self) -> Dict[T0, float]: + def as_dict(self) -> dict[T0, float]: """Return the distribution as a :py:class:`dict` object.""" return self._P - def as_rv_discrete(self) -> Tuple[rv_discrete, List[T0]]: + def as_rv_discrete(self) -> tuple[rv_discrete, list[T0]]: """Return the distribution as a :py:class:`scipy.stats.rv_discrete` object. This method returns an RV over integers {0, 1, ..., k-1} where k is the size of @@ -189,7 +179,7 @@ def as_rv_discrete(self) -> Tuple[rv_discrete, List[T0]]: return (rv_discrete(values=(range(len(X)), [self._P[x] for x in X])), X) @property - def support(self) -> Set[T0]: + def support(self) -> set[T0]: """Return the support of the distribution (set of all possible outcomes).""" return set(self._P.keys()) @@ -204,7 +194,7 @@ def __eq__(self, other: object) -> bool: return all(np.isclose(self._P[x], other._P[x]) for x in keys0) def __repr__(self) -> str: - return f"{self.__class__.__name__}({repr(self._P)})" + return f"{self.__class__.__name__}({self._P!r})" def __getitem__(self, x: T0) -> float: """Get the probability associated with a possible outcome.""" @@ -245,7 +235,7 @@ def map(self, mapping: Callable[[T0], T1]) -> "ProbabilityDistribution[T1]": :param mapping: A function defined on all possible outcomes, mapping them to another domain. """ - P: DefaultDict[Any, float] = defaultdict(float) + P: defaultdict[Any, float] = defaultdict(float) for x, p in self._P.items(): P[mapping(x)] += p return ProbabilityDistribution(P) @@ -271,7 +261,7 @@ def variance(self, f: Callable[[T0], Number]) -> Number: def convex_combination( - dists: List[Tuple[ProbabilityDistribution[T0], float]] + dists: list[tuple[ProbabilityDistribution[T0], float]] ) -> ProbabilityDistribution[T0]: """Return a convex combination of probability distributions. @@ -286,7 +276,7 @@ def convex_combination( >>> dist3.expectation(lambda x : x**2) 0.75 """ - P: DefaultDict[T0, float] = defaultdict(float) + P: defaultdict[T0, float] = defaultdict(float) S = 0.0 for pd, a in dists: if a < 0: diff --git a/pytket/pytket/utils/expectations.py b/pytket/pytket/utils/expectations.py index 95cc093bb9..76615ad0d4 100644 --- a/pytket/pytket/utils/expectations.py +++ b/pytket/pytket/utils/expectations.py @@ -12,20 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Optional, Tuple, List +from typing import TYPE_CHECKING import numpy as np + from pytket.circuit import Circuit, Qubit -from pytket.pauli import QubitPauliString from pytket.partition import ( - measurement_reduction, - PauliPartitionStrat, GraphColourMethod, + PauliPartitionStrat, + measurement_reduction, ) +from pytket.pauli import QubitPauliString from .measurements import _all_pauli_measurements, append_pauli_measurement -from .results import KwargTypes from .operators import QubitPauliOperator +from .results import KwargTypes if TYPE_CHECKING: from pytket.backends.backend import Backend @@ -47,7 +48,7 @@ def expectation_from_shots(shot_table: np.ndarray) -> float: return -2 * aritysum / len(shot_table) + 1 -def expectation_from_counts(counts: Dict[Tuple[int, ...], int]) -> float: +def expectation_from_counts(counts: dict[tuple[int, ...], int]) -> float: """Estimates the expectation value of a circuit from shot counts. Computes the parity of '1's across all bits to determine a +1 or -1 contribution from each readout, and returns the weighted average. @@ -75,7 +76,7 @@ def get_pauli_expectation_value( state_circuit: Circuit, pauli: QubitPauliString, backend: "Backend", - n_shots: Optional[int] = None, + n_shots: int | None = None, ) -> complex: """Estimates the expectation value of the given circuit with respect to the Pauli term by preparing measurements in the appropriate basis, running on the backend and @@ -108,19 +109,18 @@ def get_pauli_expectation_value( if backend.supports_counts: counts = backend.run_circuit(measured_circ, n_shots=n_shots).get_counts() return expectation_from_counts(counts) - elif backend.supports_shots: + if backend.supports_shots: shot_table = backend.run_circuit(measured_circ, n_shots=n_shots).get_shots() return expectation_from_shots(shot_table) - else: - raise ValueError("Backend does not support counts or shots") + raise ValueError("Backend does not support counts or shots") def get_operator_expectation_value( state_circuit: Circuit, operator: QubitPauliOperator, backend: "Backend", - n_shots: Optional[int] = None, - partition_strat: Optional[PauliPartitionStrat] = None, + n_shots: int | None = None, + partition_strat: PauliPartitionStrat | None = None, colour_method: GraphColourMethod = GraphColourMethod.LargestFirst, **kwargs: KwargTypes, ) -> complex: @@ -150,7 +150,7 @@ def get_operator_expectation_value( if not backend.valid_circuit(state_circuit): state_circuit = backend.get_compiled_circuit(state_circuit) try: - coeffs: List[complex] = [complex(v) for v in operator._dict.values()] + coeffs: list[complex] = [complex(v) for v in operator._dict.values()] except TypeError: raise ValueError("QubitPauliOperator contains unevaluated symbols.") if backend.supports_expectation and ( @@ -189,64 +189,62 @@ def get_operator_expectation_value( for handle in handles: backend.pop_result(handle) return energy - elif backend.supports_shots: + if backend.supports_shots: for result, coeff in zip(results, coeffs): shots = result.get_shots() energy += coeff * expectation_from_shots(shots) for handle in handles: backend.pop_result(handle) return energy - else: - raise ValueError("Backend does not support counts or shots") - else: - qubit_pauli_string_list = [p for p in operator._dict.keys() if (p != id_string)] - measurement_expectation = measurement_reduction( - qubit_pauli_string_list, partition_strat, colour_method - ) - # note: this implementation requires storing all the results - # in memory simultaneously to filter through them. - measure_circs = [] - for pauli_circ in measurement_expectation.measurement_circs: - circ = state_circuit.copy() - circ.append(pauli_circ) - measure_circs.append(circ) - handles = backend.process_circuits( - backend.get_compiled_circuits(measure_circs), - n_shots=n_shots, - valid_check=True, - **kwargs, - ) - results = backend.get_results(handles) - for pauli_string in measurement_expectation.results: - bitmaps = measurement_expectation.results[pauli_string] - string_coeff = operator[pauli_string] - for bm in bitmaps: - index = bm.circ_index - aritysum = 0.0 - if backend.supports_counts: - counts = results[index].get_counts() - total_shots = 0 - for row, count in counts.items(): - aritysum += count * (sum(row[i] for i in bm.bits) % 2) - total_shots += count - e = ( - ((-1) ** bm.invert) - * string_coeff - * (-2 * aritysum / total_shots + 1) - ) - energy += complex(e) - elif backend.supports_shots: - shots = results[index].get_shots() - for row in shots: - aritysum += sum(row[i] for i in bm.bits) % 2 - e = ( - ((-1) ** bm.invert) - * string_coeff - * (-2 * aritysum / len(shots) + 1) - ) - energy += complex(e) - else: - raise ValueError("Backend does not support counts or shots") - for handle in handles: - backend.pop_result(handle) - return energy + raise ValueError("Backend does not support counts or shots") + qubit_pauli_string_list = [p for p in operator._dict.keys() if (p != id_string)] + measurement_expectation = measurement_reduction( + qubit_pauli_string_list, partition_strat, colour_method + ) + # note: this implementation requires storing all the results + # in memory simultaneously to filter through them. + measure_circs = [] + for pauli_circ in measurement_expectation.measurement_circs: + circ = state_circuit.copy() + circ.append(pauli_circ) + measure_circs.append(circ) + handles = backend.process_circuits( + backend.get_compiled_circuits(measure_circs), + n_shots=n_shots, + valid_check=True, + **kwargs, + ) + results = backend.get_results(handles) + for pauli_string in measurement_expectation.results: + bitmaps = measurement_expectation.results[pauli_string] + string_coeff = operator[pauli_string] + for bm in bitmaps: + index = bm.circ_index + aritysum = 0.0 + if backend.supports_counts: + counts = results[index].get_counts() + total_shots = 0 + for row, count in counts.items(): + aritysum += count * (sum(row[i] for i in bm.bits) % 2) + total_shots += count + e = ( + ((-1) ** bm.invert) + * string_coeff + * (-2 * aritysum / total_shots + 1) + ) + energy += complex(e) + elif backend.supports_shots: + shots = results[index].get_shots() + for row in shots: + aritysum += sum(row[i] for i in bm.bits) % 2 + e = ( + ((-1) ** bm.invert) + * string_coeff + * (-2 * aritysum / len(shots) + 1) + ) + energy += complex(e) + else: + raise ValueError("Backend does not support counts or shots") + for handle in handles: + backend.pop_result(handle) + return energy diff --git a/pytket/pytket/utils/graph.py b/pytket/pytket/utils/graph.py index 16467bd371..f8642763f0 100644 --- a/pytket/pytket/utils/graph.py +++ b/pytket/pytket/utils/graph.py @@ -15,10 +15,9 @@ from collections import defaultdict from itertools import combinations from tempfile import NamedTemporaryFile -from typing import Optional -import networkx as nx # type: ignore import graphviz as gv # type: ignore +import networkx as nx # type: ignore from pytket.circuit import Circuit @@ -57,9 +56,9 @@ def __init__(self, c: Circuit): self.input_names = input_names self.output_names = output_names self.node_data = node_data - self.Gnx: Optional[nx.MultiDiGraph] = None - self.G: Optional[gv.Digraph] = None - self.Gqc: Optional[gv.Graph] = None + self.Gnx: nx.MultiDiGraph | None = None + self.G: gv.Digraph | None = None + self.Gqc: gv.Graph | None = None self.edge_data: dict[tuple[int, int], list[tuple[int, int, str]]] = defaultdict( list ) diff --git a/pytket/pytket/utils/measurements.py b/pytket/pytket/utils/measurements.py index ce5db31b41..be78eeba34 100644 --- a/pytket/pytket/utils/measurements.py +++ b/pytket/pytket/utils/measurements.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Iterable -from pytket.circuit import Circuit, Bit +from collections.abc import Iterable + +from pytket.circuit import Bit, Circuit from pytket.pauli import Pauli, QubitPauliString + from .operators import QubitPauliOperator diff --git a/pytket/pytket/utils/operators.py b/pytket/pytket/utils/operators.py index 8547ab524e..ce37719e8e 100644 --- a/pytket/pytket/utils/operators.py +++ b/pytket/pytket/utils/operators.py @@ -13,23 +13,23 @@ # limitations under the License. import copy -from typing import Dict, TYPE_CHECKING, Union, List, Optional, Set, Any +from typing import TYPE_CHECKING, Any, Union import numpy import numpy as np -from sympy import Symbol, sympify, Expr, re, im -from pytket.pauli import QubitPauliString, pauli_string_mult +from sympy import Expr, Symbol, im, re, sympify + from pytket.circuit import Qubit +from pytket.pauli import QubitPauliString, pauli_string_mult from pytket.utils.serialization import complex_to_list, list_to_complex - CoeffTypeAccepted = Union[int, float, complex, Expr] if TYPE_CHECKING: from scipy.sparse import csc_matrix -def _coeff_convert(coeff: Union[CoeffTypeAccepted, str]) -> Expr: +def _coeff_convert(coeff: CoeffTypeAccepted | str) -> Expr: sympy_val = sympify(coeff) if not isinstance(sympy_val, Expr): raise ValueError("Unsupported value for QubitPauliString coefficient") @@ -62,9 +62,9 @@ class QubitPauliOperator: def __init__( self, - dictionary: Optional[Dict[QubitPauliString, CoeffTypeAccepted]] = None, + dictionary: dict[QubitPauliString, CoeffTypeAccepted] | None = None, ) -> None: - self._dict: Dict[QubitPauliString, Expr] = dict() + self._dict: dict[QubitPauliString, Expr] = dict() if dictionary: for key, value in dictionary.items(): self._dict[key] = _coeff_convert(value) @@ -91,10 +91,10 @@ def __setitem__(self, key: QubitPauliString, value: CoeffTypeAccepted) -> None: self._dict[key] = _coeff_convert(value) self._all_qubits.update(key.map.keys()) - def __getstate__(self) -> Dict[QubitPauliString, Expr]: + def __getstate__(self) -> dict[QubitPauliString, Expr]: return self._dict - def __setstate__(self, _dict: Dict[QubitPauliString, Expr]) -> None: + def __setstate__(self, _dict: dict[QubitPauliString, Expr]) -> None: # values assumed to be already sympified self._dict = _dict self._collect_qubits() @@ -117,7 +117,7 @@ def __iadd__(self, addend: "QubitPauliOperator") -> "QubitPauliOperator": self[key] = self.get(key, 0.0) + value self._all_qubits.update(addend._all_qubits) else: - raise TypeError("Cannot add {} to QubitPauliOperator.".format(type(addend))) + raise TypeError(f"Cannot add {type(addend)} to QubitPauliOperator.") return self @@ -147,7 +147,7 @@ def __imul__( # Handle operator of the same type if isinstance(multiplier, QubitPauliOperator): - result_terms: Dict = dict() + result_terms: dict = dict() for left_key, left_value in self._dict.items(): for right_key, right_value in multiplier._dict.items(): new_term, bonus_coeff = pauli_string_mult(left_key, right_key) @@ -163,16 +163,13 @@ def __imul__( return self # Handle scalars. - elif isinstance(multiplier, (float, Expr)): + if isinstance(multiplier, (float, Expr)): for key in self._dict: self[key] *= multiplier return self # Invalid multiplier type - else: - raise TypeError( - "Cannot multiply QubitPauliOperator with {}".format(type(multiplier)) - ) + raise TypeError(f"Cannot multiply QubitPauliOperator with {type(multiplier)}") def __mul__( self, multiplier: Union[float, Expr, "QubitPauliOperator"] @@ -202,7 +199,7 @@ def __rmul__(self, multiplier: CoeffTypeAccepted) -> "QubitPauliOperator": return self.__mul__(_coeff_convert(multiplier)) @property - def all_qubits(self) -> Set[Qubit]: + def all_qubits(self) -> set[Qubit]: """ :return: The set of all qubits the operator ranges over (including qubits that were provided explicitly as identities) @@ -210,7 +207,7 @@ def all_qubits(self) -> Set[Qubit]: """ return self._all_qubits - def subs(self, symbol_dict: Dict[Symbol, complex]) -> None: + def subs(self, symbol_dict: dict[Symbol, complex]) -> None: """Substitutes any matching symbols in the QubitPauliOperator. :param symbol_dict: A dictionary of symbols to fixed values. @@ -219,19 +216,19 @@ def subs(self, symbol_dict: Dict[Symbol, complex]) -> None: for key, value in self._dict.items(): self._dict[key] = value.subs(symbol_dict) - def to_list(self) -> List[Dict[str, Any]]: + def to_list(self) -> list[dict[str, Any]]: """Generate a list serialized representation of QubitPauliOperator, suitable for writing to JSON. :return: JSON serializable list of dictionaries. :rtype: List[Dict[str, Any]] """ - ret: List[Dict[str, Any]] = [] + ret: list[dict[str, Any]] = [] for k, v in self._dict.items(): try: coeff = complex_to_list(complex(v)) except TypeError: - assert type(Expr(v)) == Expr + assert isinstance(Expr(v), Expr) coeff = str(v) ret.append( { @@ -242,7 +239,7 @@ def to_list(self) -> List[Dict[str, Any]]: return ret @classmethod - def from_list(cls, pauli_list: List[Dict[str, Any]]) -> "QubitPauliOperator": + def from_list(cls, pauli_list: list[dict[str, Any]]) -> "QubitPauliOperator": """Construct a QubitPauliOperator from a serializable JSON list format, as returned by QubitPauliOperator.to_list() @@ -250,21 +247,18 @@ def from_list(cls, pauli_list: List[Dict[str, Any]]) -> "QubitPauliOperator": :rtype: QubitPauliOperator """ - def get_qps(obj: Dict[str, Any]) -> QubitPauliString: + def get_qps(obj: dict[str, Any]) -> QubitPauliString: return QubitPauliString.from_list(obj["string"]) - def get_coeff(obj: Dict[str, Any]) -> Expr: + def get_coeff(obj: dict[str, Any]) -> Expr: coeff = obj["coefficient"] if type(coeff) is str: return _coeff_convert(coeff) - else: - return _coeff_convert(list_to_complex(coeff)) + return _coeff_convert(list_to_complex(coeff)) return QubitPauliOperator({get_qps(obj): get_coeff(obj) for obj in pauli_list}) - def to_sparse_matrix( - self, qubits: Union[List[Qubit], int, None] = None - ) -> "csc_matrix": + def to_sparse_matrix(self, qubits: list[Qubit] | int | None = None) -> "csc_matrix": """Represents the sparse operator as a dense operator under the ordering scheme specified by ``qubits``, and generates the corresponding matrix. @@ -297,7 +291,7 @@ def to_sparse_matrix( ) def dot_state( - self, state: np.ndarray, qubits: Optional[List[Qubit]] = None + self, state: np.ndarray, qubits: list[Qubit] | None = None ) -> np.ndarray: """Applies the operator to the given state, mapping qubits to indexes according to ``qubits``. @@ -328,7 +322,7 @@ def dot_state( return product_sum if isinstance(product_sum, numpy.ndarray) else state def state_expectation( - self, state: np.ndarray, qubits: Optional[List[Qubit]] = None + self, state: np.ndarray, qubits: list[Qubit] | None = None ) -> complex: """Calculates the expectation value of the given statevector with respect to the operator, mapping qubits to indexes according to ``qubits``. diff --git a/pytket/pytket/utils/outcomearray.py b/pytket/pytket/utils/outcomearray.py index 63cdafd51a..0ecba91297 100644 --- a/pytket/pytket/utils/outcomearray.py +++ b/pytket/pytket/utils/outcomearray.py @@ -14,8 +14,10 @@ """`OutcomeArray` class and associated methods.""" import operator +from collections import Counter +from collections.abc import Sequence from functools import reduce -from typing import Counter, List, Sequence, Dict, Tuple, Any, Optional, cast +from typing import Any, cast import numpy as np import numpy.typing as npt @@ -59,7 +61,7 @@ def __array_finalize__(self, obj: Any, *args: Any, **kwargs: Any) -> None: # see InfoArray.__array_finalize__ for comments if obj is None: return - self._width: Optional[int] = getattr(obj, "_width", None) + self._width: int | None = getattr(obj, "_width", None) @property def width(self) -> int: @@ -103,7 +105,7 @@ def to_readout(self) -> np.ndarray: raise ValueError(f"Not a singleton: {self.n_outcomes} readouts") return cast(np.ndarray, self.to_readouts()[0]) - def to_intlist(self, big_endian: bool = True) -> List[int]: + def to_intlist(self, big_endian: bool = True) -> list[int]: """Express each outcome as an integer corresponding to the bit values. :param big_endian: whether to use big endian encoding (or little endian @@ -162,7 +164,7 @@ def counts(self) -> Counter["OutcomeArray"]: oalist = [OutcomeArray(x[None, :], width) for x in ars] return Counter(dict(zip(oalist, count_vals))) - def choose_indices(self, indices: List[int]) -> "OutcomeArray": + def choose_indices(self, indices: list[int]) -> "OutcomeArray": """Permute ordering of bits in outcomes or choose subset of bits. e.g. [1, 0, 2] acting on a bitstring of length 4 swaps bit locations 0 & 1, leaves 2 in the same place and deletes location 3. @@ -174,7 +176,7 @@ def choose_indices(self, indices: List[int]) -> "OutcomeArray": """ return OutcomeArray.from_readouts(self.to_readouts()[..., indices]) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Return a JSON serializable dictionary representation of the OutcomeArray. :return: JSON serializable dictionary @@ -183,7 +185,7 @@ def to_dict(self) -> Dict[str, Any]: return {"width": self.width, "array": self.tolist()} @classmethod - def from_dict(cls, ar_dict: Dict[str, Any]) -> "OutcomeArray": + def from_dict(cls, ar_dict: dict[str, Any]) -> "OutcomeArray": """Create an OutcomeArray from JSON serializable dictionary (as created by `to_dict`). @@ -199,6 +201,6 @@ def from_dict(cls, ar_dict: Dict[str, Any]) -> "OutcomeArray": def readout_counts( ctr: Counter[OutcomeArray], -) -> Counter[Tuple[int, ...]]: +) -> Counter[tuple[int, ...]]: """Convert counts from :py:class:`OutcomeArray` types to tuples of ints.""" return Counter({tuple(oa.to_readout()): n for oa, n in ctr.items()}) diff --git a/pytket/pytket/utils/prepare.py b/pytket/pytket/utils/prepare.py index 32a5889b57..4cae2f4247 100644 --- a/pytket/pytket/utils/prepare.py +++ b/pytket/pytket/utils/prepare.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Tuple from pytket.circuit import Circuit from pytket.passes import ContextSimp from pytket.transform import separate_classical def prepare_circuit( - circ: Circuit, allow_classical: bool = True, xcirc: Optional[Circuit] = None -) -> Tuple[Circuit, Circuit]: + circ: Circuit, allow_classical: bool = True, xcirc: Circuit | None = None +) -> tuple[Circuit, Circuit]: """ Prepare a circuit for processing by a backend device. diff --git a/pytket/pytket/utils/results.py b/pytket/pytket/utils/results.py index 87fe3d65ec..0219b05dcb 100644 --- a/pytket/pytket/utils/results.py +++ b/pytket/pytket/utils/results.py @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, List, Tuple, Union +from typing import Any import numpy as np + from pytket.circuit import BasisOrder -StateTuple = Tuple[int, ...] -CountsDict = Dict[StateTuple, Union[int, float]] +StateTuple = tuple[int, ...] +CountsDict = dict[StateTuple, int | float] KwargTypes = Any @@ -29,7 +30,7 @@ class BitPermuter: """ - def __init__(self, permutation: Tuple[int, ...]): + def __init__(self, permutation: tuple[int, ...]): """Constructor :param permutation: Map from current bit index (big-endian) to its new position, @@ -42,7 +43,7 @@ def __init__(self, permutation: Tuple[int, ...]): raise ValueError("Permutation is not a valid complete permutation.") self.perm = tuple(permutation) self.n_bits = len(self.perm) - self.int_maps: Tuple[Dict[int, int], Dict[int, int]] = ({}, {}) + self.int_maps: tuple[dict[int, int], dict[int, int]] = ({}, {}) def permute(self, val: int, inverse: bool = False) -> int: """Return input with bit values permuted. @@ -71,7 +72,7 @@ def permute(self, val: int, inverse: bool = False) -> int: other_map[res] = val return res - def permute_all(self) -> List[int]: + def permute_all(self) -> list[int]: """Permute all integers within bit-width specified by permutation. :return: List of permuted outputs. @@ -80,7 +81,7 @@ def permute_all(self) -> List[int]: return list(map(self.permute, range(1 << self.n_bits))) -def counts_from_shot_table(shot_table: np.ndarray) -> Dict[Tuple[int, ...], int]: +def counts_from_shot_table(shot_table: np.ndarray) -> dict[tuple[int, ...], int]: """Summarises a shot table into a dictionary of counts for each observed outcome. :param shot_table: Table of shots from a pytket backend. @@ -93,8 +94,8 @@ def counts_from_shot_table(shot_table: np.ndarray) -> Dict[Tuple[int, ...], int] def probs_from_counts( - counts: Dict[Tuple[int, ...], int] -) -> Dict[Tuple[int, ...], float]: + counts: dict[tuple[int, ...], int] +) -> dict[tuple[int, ...], float]: """Converts raw counts of observed outcomes into the observed probability distribution. @@ -109,7 +110,7 @@ def probs_from_counts( def _index_to_readout( index: int, width: int, basis: BasisOrder = BasisOrder.ilo -) -> Tuple[int, ...]: +) -> tuple[int, ...]: return tuple( (index >> i) & 1 for i in range(width)[:: (-1) ** (basis == BasisOrder.ilo)] ) @@ -155,7 +156,7 @@ def _compute_probs_from_state(state: np.ndarray, min_p: float = 1e-10) -> np.nda def probs_from_state( state: np.ndarray, min_p: float = 1e-10 -) -> Dict[Tuple[int, ...], float]: +) -> dict[tuple[int, ...], float]: """ Converts statevector to the probability distribution over readouts in the computational basis. Ignores probabilities lower than `min_p`. @@ -172,7 +173,7 @@ def probs_from_state( return {_index_to_readout(i, width): p for i, p in enumerate(probs) if p != 0} -def int_dist_from_state(state: np.ndarray, min_p: float = 1e-10) -> Dict[int, float]: +def int_dist_from_state(state: np.ndarray, min_p: float = 1e-10) -> dict[int, float]: """ Converts statevector to the probability distribution over its indices. Ignores probabilities lower than `min_p`. @@ -204,7 +205,7 @@ def get_n_qb_from_statevector(state: np.ndarray) -> int: def _assert_compatible_state_permutation( - state: np.ndarray, permutation: Tuple[int, ...] + state: np.ndarray, permutation: tuple[int, ...] ) -> None: """Asserts that a statevector and a permutation list both refer to the same number of qubits @@ -221,7 +222,7 @@ def _assert_compatible_state_permutation( def permute_qubits_in_statevector( - state: np.ndarray, permutation: Tuple[int, ...] + state: np.ndarray, permutation: tuple[int, ...] ) -> np.ndarray: """Rearranges a statevector according to a permutation of the qubit indices. @@ -247,7 +248,7 @@ def permute_qubits_in_statevector( def permute_basis_indexing( - matrix: np.ndarray, permutation: Tuple[int, ...] + matrix: np.ndarray, permutation: tuple[int, ...] ) -> np.ndarray: """Rearranges the first dimensions of an array (statevector or unitary) according to a permutation of the bit indices in the binary representation @@ -269,7 +270,7 @@ def permute_basis_indexing( def permute_rows_cols_in_unitary( - matrix: np.ndarray, permutation: Tuple[int, ...] + matrix: np.ndarray, permutation: tuple[int, ...] ) -> np.ndarray: """Rearranges the rows of a unitary matrix according to a permutation of the qubit indices. @@ -286,8 +287,7 @@ def permute_rows_cols_in_unitary( permuter = BitPermuter(permutation) all_perms = permuter.permute_all() permat: np.ndarray = matrix[:, all_perms] - permat = permat[all_perms, :] - return permat + return permat[all_perms, :] def compare_statevectors(first: np.ndarray, second: np.ndarray) -> bool: diff --git a/pytket/pytket/utils/spam.py b/pytket/pytket/utils/spam.py index ac0b5c3208..351c391877 100644 --- a/pytket/pytket/utils/spam.py +++ b/pytket/pytket/utils/spam.py @@ -13,25 +13,26 @@ # limitations under the License. import itertools +from collections import Counter, OrderedDict +from collections.abc import Callable, Iterable from functools import lru_cache - from math import ceil, log2 -from collections import OrderedDict -from typing import Dict, Iterable, List, Tuple, Counter, cast, Optional, Callable, Union +from typing import cast + import numpy as np -from pytket.circuit import Circuit, Qubit, Bit, Node, CircBox, OpType + from pytket.backends import Backend -from pytket.passes import DecomposeBoxes, FlattenRegisters from pytket.backends.backendresult import BackendResult +from pytket.circuit import Bit, CircBox, Circuit, Node, OpType, Qubit +from pytket.passes import DecomposeBoxes, FlattenRegisters from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import CountsDict, StateTuple - -ParallelMeasures = List[Dict[Qubit, Bit]] +ParallelMeasures = list[dict[Qubit, Bit]] def compress_counts( - counts: Dict[StateTuple, float], tol: float = 1e-6, round_to_int: bool = False + counts: dict[StateTuple, float], tol: float = 1e-6, round_to_int: bool = False ) -> CountsDict: """Filter counts to remove states that have a count value (which can be a floating-point number) below a tolerance, and optionally round to an @@ -47,7 +48,7 @@ def compress_counts( :return: Filtered counts :rtype: CountsDict """ - valprocess: Callable[[float], Union[int, float]] = lambda x: ( + valprocess: Callable[[float], int | float] = lambda x: ( int(round(x)) if round_to_int else x ) processed_pairs = ( @@ -57,7 +58,7 @@ def compress_counts( @lru_cache(maxsize=128) -def binary_to_int(bintuple: Tuple[int]) -> int: +def binary_to_int(bintuple: tuple[int]) -> int: """Convert a binary tuple to corresponding integer, with most significant bit as the first element of tuple. @@ -75,7 +76,7 @@ def binary_to_int(bintuple: Tuple[int]) -> int: @lru_cache(maxsize=128) -def int_to_binary(val: int, dim: int) -> Tuple[int, ...]: +def int_to_binary(val: int, dim: int) -> tuple[int, ...]: """Convert an integer to corresponding binary tuple, with most significant bit as the first element of tuple. @@ -87,7 +88,7 @@ def int_to_binary(val: int, dim: int) -> Tuple[int, ...]: :return: Binary tuple of width dim :rtype: Tuple[int, ...] """ - return tuple(map(int, format(val, "0{}b".format(dim)))) + return tuple(map(int, format(val, f"0{dim}b"))) ######################################### @@ -98,7 +99,7 @@ def int_to_binary(val: int, dim: int) -> Tuple[int, ...]: ### and especially ### https://gist.github.com/ahwillia/f65bc70cb30206d4eadec857b98c4065 ### on which this code is based. -def _unfold(tens: np.ndarray, mode: int, dims: List[int]) -> np.ndarray: +def _unfold(tens: np.ndarray, mode: int, dims: list[int]) -> np.ndarray: """Unfolds tensor into matrix. :param tens: Tensor with shape equivalent to dimensions @@ -113,11 +114,10 @@ def _unfold(tens: np.ndarray, mode: int, dims: List[int]) -> np.ndarray: """ if mode == 0: return tens.reshape(dims[0], -1) - else: - return np.moveaxis(tens, mode, 0).reshape(dims[mode], -1) + return np.moveaxis(tens, mode, 0).reshape(dims[mode], -1) -def _refold(vec: np.ndarray, mode: int, dims: List[int]) -> np.ndarray: +def _refold(vec: np.ndarray, mode: int, dims: list[int]) -> np.ndarray: """Refolds vector into tensor. :param vec: Tensor with length equivalent to the product of dimensions given in @@ -133,11 +133,10 @@ def _refold(vec: np.ndarray, mode: int, dims: List[int]) -> np.ndarray: """ if mode == 0: return vec.reshape(dims) - else: - # Reshape and then move dims[mode] back to its - # appropriate spot (undoing the `unfold` operation). - tens = vec.reshape([dims[mode]] + [d for m, d in enumerate(dims) if m != mode]) - return np.moveaxis(tens, 0, mode) + # Reshape and then move dims[mode] back to its + # appropriate spot (undoing the `unfold` operation). + tens = vec.reshape([dims[mode]] + [d for m, d in enumerate(dims) if m != mode]) + return np.moveaxis(tens, 0, mode) def _compute_dot(submatrices: Iterable[np.ndarray], vector: np.ndarray) -> np.ndarray: @@ -204,7 +203,7 @@ def _bayesian_iterative_correct( submatrices: Iterable[np.ndarray], measurements: np.ndarray, tol: float = 1e-5, - max_it: Optional[int] = None, + max_it: int | None = None, ) -> np.ndarray: """Finds new states to represent application of inversion of submatrices on measurements. Converges when update states within tol range of previously @@ -246,7 +245,7 @@ def _bayesian_iterative_correct( return true_states -def reduce_matrix(indices_to_remove: List[int], matrix: np.ndarray) -> np.ndarray: +def reduce_matrix(indices_to_remove: list[int], matrix: np.ndarray) -> np.ndarray: """Removes indices from indices_to_remove from binary associated to indexing of matrix, producing a new transition matrix. To do so, it assigns all transition probabilities as the given state in the remaining indices binary, with the removed @@ -286,8 +285,8 @@ def reduce_matrix(indices_to_remove: List[int], matrix: np.ndarray) -> np.ndarra def reduce_matrices( - entries_to_remove: List[Tuple[int, int]], matrices: List[np.ndarray] -) -> List[np.ndarray]: + entries_to_remove: list[tuple[int, int]], matrices: list[np.ndarray] +) -> list[np.ndarray]: """Removes some dimensions from some matrices. :param entries_to_remove: Via indexing, details dimensions to be removed. @@ -298,16 +297,15 @@ def reduce_matrices( :return: Matrices with some dimensions removed. :rtype: List[np.ndarray] """ - organise: Dict[int, List] = dict({k: [] for k in range(len(matrices))}) + organise: dict[int, list] = dict({k: [] for k in range(len(matrices))}) for unused in entries_to_remove: # unused[0] is index in matrices # unused[1] is qubit index in matrix organise[unused[0]].append(unused[1]) output_matrices = [reduce_matrix(organise[m], matrices[m]) for m in organise] - normalised_mats = [ + return [ mat / np.sum(mat, axis=0) for mat in [x for x in output_matrices if len(x) != 0] ] - return normalised_mats class SpamCorrecter: @@ -318,9 +316,7 @@ class SpamCorrecter: dictionary. """ - def __init__( - self, qubit_subsets: List[List[Node]], backend: Optional[Backend] = None - ): + def __init__(self, qubit_subsets: list[list[Node]], backend: Backend | None = None): """Construct a new `SpamCorrecter`. :param qubit_subsets: A list of lists of correlated Nodes of an `Architecture`. @@ -365,7 +361,7 @@ def to_tuple(inp: list[Node]) -> tuple: self.xbox = CircBox(xcirc) - def calibration_circuits(self) -> List[Circuit]: + def calibration_circuits(self) -> list[Circuit]: """Generate calibration circuits according to the specified correlations. :return: A list of calibration circuits to be run on the machine. The circuits @@ -415,7 +411,7 @@ def calibration_circuits(self) -> List[Circuit]: self.state_infos.append((new_state_dicts, state_circuit.qubit_to_bit_map)) return self.prepared_circuits - def calculate_matrices(self, results_list: List[BackendResult]) -> None: + def calculate_matrices(self, results_list: list[BackendResult]) -> None: """Calculate the calibration matrices from the results of running calibration circuits. @@ -431,7 +427,7 @@ def calculate_matrices(self, results_list: List[BackendResult]) -> None: ) counter = 0 - self.node_index_dict: Dict[Node, Tuple[int, int]] = dict() + self.node_index_dict: dict[Node, tuple[int, int]] = dict() for qbs, dim in zip(self.subsets_matrix_map, self.subset_dimensions): # for a subset with n qubits, create a 2^n by 2^n matrix @@ -492,7 +488,7 @@ def correct_counts( result: BackendResult, parallel_measures: ParallelMeasures, method: str = "bayesian", - options: Optional[Dict] = None, + options: dict | None = None, ) -> BackendResult: """Modifies count distribution for result, such that the inversion of the pure noise map represented by characterisation matrices is applied to it. @@ -528,8 +524,7 @@ def correct_counts( # no q duplicates as mapping is dict from qubit to bit if q not in unused_qbs: raise ValueError( - "Measured qubit {} is not characterised by " - "SpamCorrecter".format(q) + f"Measured qubit {q} is not characterised by SpamCorrecter" ) unused_qbs.remove(q) # type:ignore[arg-type] char_bits_order.append(mapping[q]) @@ -601,7 +596,7 @@ def correct_counts( # produce and return BackendResult object return BackendResult(counts=counter, c_bits=char_bits_order) - def to_dict(self) -> Dict: + def to_dict(self) -> dict: """Get calibration information as a dictionary. :return: Dictionary output @@ -616,15 +611,14 @@ def to_dict(self) -> Dict: for uid in self.node_index_dict ] char_matrices = [m.tolist() for m in self.characterisation_matrices] - self_dict = { + return { "correlations": correlations, "node_index_dict": node_index_hashable, "characterisation_matrices": char_matrices, } - return self_dict @classmethod - def from_dict(class_obj, d: Dict) -> "SpamCorrecter": + def from_dict(class_obj, d: dict) -> "SpamCorrecter": """Build a `SpamCorrecter` instance from a dictionary in the format returned by `to_dict`. diff --git a/pytket/pytket/utils/stats.py b/pytket/pytket/utils/stats.py index 83ceeea426..5d1b1a1d84 100644 --- a/pytket/pytket/utils/stats.py +++ b/pytket/pytket/utils/stats.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Counter +from collections import Counter + from pytket.circuit import Circuit, OpType diff --git a/pytket/pytket/utils/symbolic.py b/pytket/pytket/utils/symbolic.py index 0107964dc1..609401094e 100644 --- a/pytket/pytket/utils/symbolic.py +++ b/pytket/pytket/utils/symbolic.py @@ -16,7 +16,8 @@ for symbolic circuits. This uses the sympy.physics.quantum module and produces sympy objects. The implementations are slow and scale poorly, so this is only suitable for very small (up to 5 qubit) circuits.""" -from typing import Callable, Dict, List, Optional, Type, Union, cast +from collections.abc import Callable +from typing import cast import numpy as np import sympy @@ -24,25 +25,25 @@ BlockDiagMatrix, BlockMatrix, Expr, + I, Identity, ImmutableMatrix, Matrix, Mul, - I, diag, eye, zeros, ) from sympy.physics.quantum import gate as symgate from sympy.physics.quantum import represent -from sympy.physics.quantum.tensorproduct import matrix_tensor_product from sympy.physics.quantum.qapply import qapply from sympy.physics.quantum.qubit import Qubit, matrix_to_qubit +from sympy.physics.quantum.tensorproduct import matrix_tensor_product from pytket.circuit import Circuit, Op, OpType # gates that have an existing definition in sympy -_FIXED_GATE_MAP: Dict[OpType, Type[symgate.Gate]] = { +_FIXED_GATE_MAP: dict[OpType, type[symgate.Gate]] = { OpType.H: symgate.HadamardGate, OpType.S: symgate.PhaseGate, OpType.CX: symgate.CNotGate, @@ -53,10 +54,10 @@ OpType.Z: symgate.ZGate, } -ParamsType = List[Union[Expr, float]] +ParamsType = list[Expr | float] # Make sure the return matrix is Immutable https://github.com/sympy/sympy/issues/18733 SymGateFunc = Callable[[ParamsType], ImmutableMatrix] -SymGateMap = Dict[OpType, SymGateFunc] +SymGateMap = dict[OpType, SymGateFunc] # Begin matrix definitions for symbolic OpTypes # matches internal TKET definitions @@ -210,8 +211,7 @@ def symb_xxphase3(params: ParamsType) -> ImmutableMatrix: ) ) res3 = matrix_tensor_product(eye(2), xxphase2) - res = ImmutableMatrix(res1 * res2 * res3) - return res + return ImmutableMatrix(res1 * res2 * res3) def symb_phasedx(params: ParamsType) -> ImmutableMatrix: @@ -354,7 +354,7 @@ def is_registered(cls, typ: OpType) -> bool: return typ in cls._g_map -def _op_to_sympy_gate(op: Op, targets: List[int]) -> symgate.Gate: +def _op_to_sympy_gate(op: Op, targets: list[int]) -> symgate.Gate: # convert Op to sympy gate if op.type in _FIXED_GATE_MAP: return _FIXED_GATE_MAP[op.type](*targets) @@ -367,7 +367,7 @@ def _op_to_sympy_gate(op: Op, targets: List[int]) -> symgate.Gate: ) # pytket matrix basis indexing is in opposite order to sympy - targets = targets[::-1] + targets.reverse() if (not float_params) and SymGateRegister.is_registered(op.type): u_mat = SymGateRegister.get_func(op.type)(op.params) else: @@ -382,8 +382,7 @@ def _op_to_sympy_gate(op: Op, targets: List[int]) -> symgate.Gate: " Try registering your own symbolic matrix representation" " with SymGateRegister.func." ) from e - gate = symgate.UGate(targets, u_mat) - return gate + return symgate.UGate(targets, u_mat) def circuit_to_symbolic_gates(circ: Circuit) -> Mul: @@ -416,7 +415,7 @@ def circuit_to_symbolic_gates(circ: Circuit) -> Mul: for i in range(len(qubit_map)): outmat = symgate.IdentityGate(i) * outmat - return outmat * sympy.exp((circ.phase * sympy.pi * I)) + return outmat * sympy.exp(circ.phase * sympy.pi * I) def circuit_to_symbolic_unitary(circ: Circuit) -> ImmutableMatrix: @@ -462,7 +461,7 @@ def circuit_apply_symbolic_qubit(circ: Circuit, input_qb: Expr) -> Qubit: def circuit_apply_symbolic_statevector( - circ: Circuit, input_state: Optional[Union[np.ndarray, ImmutableMatrix]] = None + circ: Circuit, input_state: np.ndarray | ImmutableMatrix | None = None ) -> ImmutableMatrix: """Apply circuit to an optional input statevector to calculate output symbolic statevector. diff --git a/pytket/pytket/utils/term_sequence.py b/pytket/pytket/utils/term_sequence.py index fafd56d521..1b71d802aa 100644 --- a/pytket/pytket/utils/term_sequence.py +++ b/pytket/pytket/utils/term_sequence.py @@ -14,14 +14,15 @@ from typing import cast from pytket import Circuit -from pytket.circuit import PauliExpBox, CircBox +from pytket.circuit import CircBox, PauliExpBox from pytket.partition import ( - term_sequence, - PauliPartitionStrat, GraphColourMethod, + PauliPartitionStrat, + term_sequence, ) -from .operators import QubitPauliOperator + from .._tket.unit_id import UnitID +from .operators import QubitPauliOperator def gen_term_sequence_circuit( diff --git a/pytket/pytket/wasm/wasm.py b/pytket/pytket/wasm/wasm.py index bf9de1a538..e860712ef3 100644 --- a/pytket/pytket/wasm/wasm.py +++ b/pytket/pytket/wasm/wasm.py @@ -12,23 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -from os.path import exists import base64 import hashlib from functools import cached_property -from typing_extensions import deprecated +from os.path import exists from qwasm import ( # type: ignore - decode_module, - SEC_TYPE, - SEC_FUNCTION, - SEC_EXPORT, - LANG_TYPE_I32, - LANG_TYPE_I64, + LANG_TYPE_EMPTY, LANG_TYPE_F32, LANG_TYPE_F64, - LANG_TYPE_EMPTY, + LANG_TYPE_I32, + LANG_TYPE_I64, + SEC_EXPORT, + SEC_FUNCTION, + SEC_TYPE, + decode_module, ) +from typing_extensions import deprecated class WasmModuleHandler: @@ -125,9 +125,9 @@ def check(self) -> None: ] * entry.return_count else: raise ValueError( - f"Only parameter and return values of " - + f"i{self._int_size} types are" - + f" allowed, found type: {entry.return_type}" + "Only parameter and return values of " + f"i{self._int_size} types are" + f" allowed, found type: {entry.return_type}" ) elif entry.return_count == 1: function_signatures[idx]["return_types"] = [ diff --git a/pytket/pytket/zx/tensor_eval.py b/pytket/pytket/zx/tensor_eval.py index 39a9877399..dcf3f200bd 100644 --- a/pytket/pytket/zx/tensor_eval.py +++ b/pytket/pytket/zx/tensor_eval.py @@ -14,27 +14,29 @@ """Collection of methods to evaluate a ZXDiagram to a tensor. This uses the numpy tensor features, in particular the einsum evaluation and optimisations.""" -from typing import Dict, List, Any -from math import floor, pi, sqrt, cos, sin import warnings -import sympy +from math import cos, floor, pi, sin, sqrt +from typing import Any + import numpy as np +import sympy + from pytket.zx import ( - ZXDiagram, - ZXType, - ZXVert, - ZXGen, - PhasedGen, CliffordGen, DirectedGen, - ZXBox, + PhasedGen, QuantumType, Rewrite, + ZXBox, + ZXDiagram, + ZXGen, + ZXType, + ZXVert, ) try: import quimb.tensor as qtn # type: ignore -except ModuleNotFoundError as err: +except ModuleNotFoundError: warnings.warn( 'Missing package for tensor evaluation of ZX diagrams. Run "pip ' "install 'pytket[ZX]'\" to install the optional dependencies." @@ -44,14 +46,13 @@ def _gen_to_tensor(gen: ZXGen, rank: int) -> np.ndarray: if isinstance(gen, PhasedGen): return _spider_to_tensor(gen, rank) - elif isinstance(gen, CliffordGen): + if isinstance(gen, CliffordGen): return _clifford_to_tensor(gen, rank) - elif isinstance(gen, DirectedGen): + if isinstance(gen, DirectedGen): return _dir_gen_to_tensor(gen) - elif isinstance(gen, ZXBox): + if isinstance(gen, ZXBox): return _tensor_from_basic_diagram(gen.diagram) - else: - raise ValueError(f"Cannot convert generator of type {gen.type} to a tensor") + raise ValueError(f"Cannot convert generator of type {gen.type} to a tensor") def _spider_to_tensor(gen: PhasedGen, rank: int) -> np.ndarray: @@ -80,7 +81,7 @@ def _spider_to_tensor(gen: PhasedGen, rank: int) -> np.ndarray: t = np.full(size, 1.0, dtype=complex) constant = pow(sqrt(0.5), rank) for i in range(size): - parity = bin(i).count("1") + parity = (i).bit_count() t[i] += phase if parity % 2 == 0 else -phase t[i] *= constant elif gen.type == ZXType.Hbox: @@ -109,8 +110,7 @@ def _spider_to_tensor(gen: PhasedGen, rank: int) -> np.ndarray: raise ValueError( f"Cannot convert phased generator of type {gen.type} to a tensor" ) - t = t.reshape(tuple([2] * rank)) - return t + return t.reshape(tuple([2] * rank)) def _clifford_to_tensor(gen: CliffordGen, rank: int) -> np.ndarray: @@ -131,8 +131,7 @@ def _clifford_to_tensor(gen: CliffordGen, rank: int) -> np.ndarray: raise ValueError( f"Cannot convert Clifford generator of type {gen.type} to a tensor" ) - t = t.reshape(tuple([2] * rank)) - return t + return t.reshape(tuple([2] * rank)) def _dir_gen_to_tensor(gen: DirectedGen) -> np.ndarray: @@ -140,10 +139,9 @@ def _dir_gen_to_tensor(gen: DirectedGen) -> np.ndarray: t = np.ones((2, 2), dtype=complex) t[1, 0] = 0.0 return t - else: - raise ValueError( - f"Cannot convert directed generator of type {gen.type} to a tensor" - ) + raise ValueError( + f"Cannot convert directed generator of type {gen.type} to a tensor" + ) _id_tensor = np.asarray([[1, 0], [0, 1]], dtype=complex) @@ -162,7 +160,7 @@ def _tensor_from_basic_diagram(diag: ZXDiagram) -> np.ndarray: all_wires = diag.wires indices = dict(zip(all_wires, range(len(all_wires)))) next_index = len(all_wires) - tensor_list: List[Any] + tensor_list: list[Any] tensor_list = [] id_wires = set() res_indices = [] @@ -200,7 +198,7 @@ def _tensor_from_basic_diagram(diag: ZXDiagram) -> np.ndarray: net.full_simplify_(seq="ADCR") res_ten = net.contract(output_inds=res_indices, optimize="greedy") result: np.ndarray - if type(res_ten) == qtn.Tensor: + if isinstance(res_ten, qtn.Tensor): result = res_ten.data else: # Scalar @@ -284,7 +282,7 @@ def density_matrix_from_cptp_diagram(diag: ZXDiagram) -> np.ndarray: def fix_boundaries_to_binary_states( - diag: ZXDiagram, vals: Dict[ZXVert, int] + diag: ZXDiagram, vals: dict[ZXVert, int] ) -> ZXDiagram: new_diag = ZXDiagram(diag) b_lookup = dict(zip(diag.get_boundary(), new_diag.get_boundary())) @@ -308,7 +306,7 @@ def fix_boundaries_to_binary_states( return new_diag -def fix_inputs_to_binary_state(diag: ZXDiagram, vals: List[int]) -> ZXDiagram: +def fix_inputs_to_binary_state(diag: ZXDiagram, vals: list[int]) -> ZXDiagram: inputs = diag.get_boundary(type=ZXType.Input) if len(inputs) != len(vals): raise ValueError( @@ -318,7 +316,7 @@ def fix_inputs_to_binary_state(diag: ZXDiagram, vals: List[int]) -> ZXDiagram: return fix_boundaries_to_binary_states(diag, val_dict) -def fix_outputs_to_binary_state(diag: ZXDiagram, vals: List[int]) -> ZXDiagram: +def fix_outputs_to_binary_state(diag: ZXDiagram, vals: list[int]) -> ZXDiagram: outputs = diag.get_boundary(type=ZXType.Output) if len(outputs) != len(vals): raise ValueError( diff --git a/pytket/ruff.toml b/pytket/ruff.toml new file mode 100644 index 0000000000..de31804bfd --- /dev/null +++ b/pytket/ruff.toml @@ -0,0 +1,81 @@ +exclude = [ + "docs/pytket-docs-theming", + "pytket/_tket", + "pytket/_version.py", + "pytket/qasm/includes" +] + +target-version = "py310" + +lint.select = [ + "A", + "AIR", + # "ANN", + # "ARG", # TODO + "ASYNC", + # "B", + "BLE", + # "C", + # "C4", # TODO + # "C90", + # "COM", + # "CPY", + # "D", + "DJ", + # "DOC", + "DTZ", + "E", + # "EM", + # "ERA", # TODO + "EXE", + "F", + "FA", + # "FAST", + # "FBT", + # "FIX", + "FLY", + "FURB", + "G", + "I", + # "ICN", + # "INP", + "INT", + "ISC", + "LOG", + # "N", + # "NPY", # TODO + # "PD", + # "PERF", # TODO + # "PGH", + "PIE", + # "PL", + # "PT", + # "PTH", # TODO + # "PYI", # TODO + "Q", + "R", + "RET", + "RSE", + # "RUF", + # "S", + # "SIM", # TODO + # "SLF", + "SLOT", + "T10", + # "T20", + # "TCH", # TODO + # "TD", + # "TID", + # "TRY", + # "UP", # TODO + "W", + "YTT", +] + +lint.ignore = [ + "E501", # Allow long lines in strings + "E731", # OK to assign to lambdas + "E741", # Allow variable names like "l" + "F401", # Allow importing unused names in init files + "F403", # Allow wildcard imports in init files +] diff --git a/pytket/setup.py b/pytket/setup.py old mode 100755 new mode 100644 index 14f2000f02..0ed998f47e --- a/pytket/setup.py +++ b/pytket/setup.py @@ -12,15 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import multiprocessing import os -import subprocess -import json import shutil +import subprocess +from sysconfig import get_config_var + import setuptools # type: ignore -from setuptools import setup, Extension +from setuptools import Extension, setup from setuptools.command.build_ext import build_ext # type: ignore -from sysconfig import get_config_var from wheel.bdist_wheel import bdist_wheel as _bdist_wheel @@ -62,7 +63,9 @@ def run(self): os.mkdir(build_dir) install_dir = os.getenv("INSTALL_DIR") subprocess.run( - ["cmake", f"-DCMAKE_INSTALL_PREFIX={install_dir}", os.pardir], cwd=build_dir + ["cmake", f"-DCMAKE_INSTALL_PREFIX={install_dir}", os.pardir], + cwd=build_dir, + check=True, ) subprocess.run( [ @@ -72,8 +75,9 @@ def run(self): f"-j{os.getenv('PYTKET_CMAKE_N_THREADS', multiprocessing.cpu_count())}", ], cwd=build_dir, + check=True, ) - subprocess.run(["cmake", "--install", os.curdir], cwd=build_dir) + subprocess.run(["cmake", "--install", os.curdir], cwd=build_dir, check=True) lib_folder = os.path.join(install_dir, "lib") lib_names = ["libtklog.so", "libtket.so"] ext_suffix = get_config_var("EXT_SUFFIX") @@ -138,7 +142,6 @@ def run(self): shutil.rmtree(extdir) os.makedirs(extdir) - nix_ldflags = os.environ["NIX_LDFLAGS"].split() build_inputs = os.environ["propagatedBuildInputs"].split() binders = [f"{l}/lib" for l in build_inputs if "-binders" in l] @@ -149,7 +152,7 @@ def run(self): shutil.copy(libpath, extdir) for interface_file in os.listdir("pytket/_tket"): - if interface_file.endswith(".pyi") or interface_file.endswith(".py"): + if interface_file.endswith((".pyi", ".py")): shutil.copy(os.path.join("pytket/_tket", interface_file), extdir) @@ -159,10 +162,9 @@ def run(self): def get_build_ext(): if os.getenv("USE_NIX"): return NixBuild - elif os.getenv("NO_CONAN"): + if os.getenv("NO_CONAN"): return CMakeBuild - else: - return ConanBuild + return ConanBuild class bdist_wheel(_bdist_wheel): @@ -185,7 +187,7 @@ def finalize_options(self): "Tracker": "https://github.com/CQCL/tket/issues", }, description="Quantum computing toolkit and interface to the TKET compiler", - long_description=open("package.md", "r").read(), + long_description=open("package.md").read(), long_description_content_type="text/markdown", license="Apache 2", packages=setuptools.find_packages() + ["pytket.qasm.includes"], @@ -207,9 +209,7 @@ def finalize_options(self): "autoray >= 0.6.12", ], }, - ext_modules=[ - CMakeExtension("pytket._tket.{}".format(binder)) for binder in binders - ], + ext_modules=[CMakeExtension(f"pytket._tket.{binder}") for binder in binders], cmdclass={ "build_ext": get_build_ext(), "bdist_wheel": bdist_wheel, diff --git a/pytket/tests/add_circuit_test.py b/pytket/tests/add_circuit_test.py index 4eaafec4a3..e9e9623447 100644 --- a/pytket/tests/add_circuit_test.py +++ b/pytket/tests/add_circuit_test.py @@ -13,7 +13,6 @@ # limitations under the License. from pytket import Circuit, OpType -import pytest def gen_bell_state(reset_start: bool = False) -> Circuit: diff --git a/pytket/tests/ansatz_sequence_test.py b/pytket/tests/ansatz_sequence_test.py index d8dcdd46de..a703c098c0 100644 --- a/pytket/tests/ansatz_sequence_test.py +++ b/pytket/tests/ansatz_sequence_test.py @@ -12,15 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, Tuple, List +from typing import Dict, List, Tuple + import numpy as np -import pytest + from pytket import Circuit +from pytket.circuit import OpType, Qubit, fresh_symbol +from pytket.partition import GraphColourMethod, PauliPartitionStrat from pytket.pauli import Pauli, QubitPauliString -from pytket.circuit import fresh_symbol, OpType, Qubit -from pytket.utils import gen_term_sequence_circuit, QubitPauliOperator from pytket.transform import Transform -from pytket.partition import PauliPartitionStrat, GraphColourMethod +from pytket.utils import QubitPauliOperator, gen_term_sequence_circuit def test_basic_sequence() -> None: @@ -95,15 +96,15 @@ def test_nontrivial_sequence() -> None: } # We'll build the results up, and check at the end that they match exactly - calculated_results: Dict[ - PauliPartitionStrat, Dict[GraphColourMethod, Tuple[int, ...]] + calculated_results: dict[ + PauliPartitionStrat, dict[GraphColourMethod, tuple[int, ...]] ] = {} for strategy, colour_method_dict in expected_results.items(): calculated_results[strategy] = {} for colour_method in colour_method_dict: circ = gen_term_sequence_circuit(op, c, strategy, colour_method) - counts_list: List[int] = [circ.n_gates_of_type(OpType.CircBox)] + counts_list: list[int] = [circ.n_gates_of_type(OpType.CircBox)] circ_other = circ.copy() Transform.DecomposeBoxes().apply(circ_other) diff --git a/pytket/tests/architecture_aware_synthesis_test.py b/pytket/tests/architecture_aware_synthesis_test.py index 09f72df902..fd203272cd 100644 --- a/pytket/tests/architecture_aware_synthesis_test.py +++ b/pytket/tests/architecture_aware_synthesis_test.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pytket.circuit import Circuit, OpType from pytket.architecture import Architecture +from pytket.circuit import Circuit, OpType from pytket.passes import AASRouting, CNotSynthType, ComposePhasePolyBoxes from pytket.predicates import CompilationUnit diff --git a/pytket/tests/architecture_test.py b/pytket/tests/architecture_test.py index 3bb4b58816..37221c7f40 100644 --- a/pytket/tests/architecture_test.py +++ b/pytket/tests/architecture_test.py @@ -13,20 +13,22 @@ # limitations under the License. import json +from pathlib import Path + +from jsonschema import Draft7Validator # type: ignore from referencing import Registry from referencing.jsonschema import DRAFT7 -from jsonschema import Draft7Validator # type: ignore -from pathlib import Path + +from pytket.architecture import Architecture, FullyConnected, RingArch, SquareGrid from pytket.circuit import Node -from pytket.architecture import Architecture, SquareGrid, FullyConnected, RingArch curr_file_path = Path(__file__).resolve().parent schema_dir = curr_file_path.parent.parent / "schemas" -with open(schema_dir / "circuit_v1.json", "r") as f: +with open(schema_dir / "circuit_v1.json") as f: circ_schema = json.load(f) -with open(schema_dir / "architecture_v1.json", "r") as f: +with open(schema_dir / "architecture_v1.json") as f: arch_schema = json.load(f) -with open(schema_dir / "fullyconnected_v1.json", "r") as f: +with open(schema_dir / "fullyconnected_v1.json") as f: fc_schema = json.load(f) schema_store = [ @@ -92,9 +94,9 @@ def test_architecture_eq() -> None: assert arc != Architecture([(Node("s", i), Node("s", j)) for (i, j) in coupling]) # only Node IDs and coupling matters - g00, g01, g10, g11 = [ + g00, g01, g10, g11 = ( Node("gridNode", [i, j, 0]) for i in range(2) for j in range(2) - ] + ) sq_arc = Architecture([(g00, g01), (g01, g11), (g00, g10), (g10, g11)]) assert sq_arc == SquareGrid(2, 2) assert sq_arc != Architecture([(g00, g01), (g01, g11), (g00, g10)]) diff --git a/pytket/tests/assertion_test.py b/pytket/tests/assertion_test.py index 73a11a15b2..47bd678fe4 100644 --- a/pytket/tests/assertion_test.py +++ b/pytket/tests/assertion_test.py @@ -14,20 +14,18 @@ import numpy as np import pytest +from simulator import TketSimShotBackend # type: ignore from pytket.circuit import ( - ProjectorAssertionBox, - StabiliserAssertionBox, Circuit, + ProjectorAssertionBox, Qubit, + StabiliserAssertionBox, ) - from pytket.passes import ( DecomposeBoxes, ) - -from pytket.pauli import PauliStabiliser, Pauli -from simulator import TketSimShotBackend # type: ignore +from pytket.pauli import Pauli, PauliStabiliser def test_assertion_init() -> None: diff --git a/pytket/tests/backend_test.py b/pytket/tests/backend_test.py index 0bd16136d6..d4f663a31d 100644 --- a/pytket/tests/backend_test.py +++ b/pytket/tests/backend_test.py @@ -12,30 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import Counter - -from hypothesis import given, settings, strategies import json -import pytest +from collections import Counter from typing import Any, List import numpy as np +import pytest +from hypothesis import given, settings, strategies +from simulator import TketSimBackend, TketSimShotBackend # type: ignore +from strategies import backendresults, outcomearrays # type: ignore -from pytket.circuit import Circuit, OpType, BasisOrder, Qubit, Bit, Node -from pytket.predicates import CompilationUnit -from pytket.passes import PauliSimp, CliffordSimp -from pytket.mapping import MappingManager, LexiRouteRoutingMethod, LexiLabellingMethod from pytket.architecture import Architecture -from pytket.utils.outcomearray import OutcomeArray, readout_counts -from pytket.utils.prepare import prepare_circuit from pytket.backends.backend import Backend, ResultHandleTypeError -from pytket.backends.resulthandle import ResultHandle +from pytket.backends.backend_exceptions import CircuitNotRunError, InvalidResultType from pytket.backends.backendresult import BackendResult -from pytket.backends.backend_exceptions import InvalidResultType, CircuitNotRunError +from pytket.backends.resulthandle import ResultHandle from pytket.backends.status import CircuitStatus, StatusEnum - -from strategies import outcomearrays, backendresults # type: ignore -from simulator import TketSimShotBackend, TketSimBackend # type: ignore +from pytket.circuit import BasisOrder, Bit, Circuit, Node, OpType, Qubit +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager +from pytket.passes import CliffordSimp, PauliSimp +from pytket.predicates import CompilationUnit +from pytket.utils.outcomearray import OutcomeArray, readout_counts +from pytket.utils.prepare import prepare_circuit def test_resulthandle() -> None: @@ -135,7 +133,7 @@ def test_swaps() -> None: def assert_single_entry_approx_value( - numbers: List[Any], + numbers: list[Any], index: int, value: float = 1.0, value_abs_eps: float = 1e-14, diff --git a/pytket/tests/backendinfo_test.py b/pytket/tests/backendinfo_test.py index 4f55289910..6308c30c77 100644 --- a/pytket/tests/backendinfo_test.py +++ b/pytket/tests/backendinfo_test.py @@ -19,14 +19,13 @@ from json import dumps, loads -from hypothesis import given, settings import pytest +import strategies as st # type: ignore +from hypothesis import given, settings +from pytket.architecture import FullyConnected, RingArch, SquareGrid from pytket.backends.backendinfo import BackendInfo, fully_connected_backendinfo -from pytket.architecture import SquareGrid, RingArch, FullyConnected -from pytket.circuit import OpType, Node - -import strategies as st # type: ignore +from pytket.circuit import Node, OpType def test_nodes() -> None: @@ -64,12 +63,12 @@ def test_gate_errors_options() -> None: bi = BackendInfo( "name", "device_name", "version", SquareGrid(3, 4), {OpType.CX, OpType.Rx} ) - assert bi.all_node_gate_errors == None - assert bi.all_edge_gate_errors == None - assert bi.all_readout_errors == None - assert bi.averaged_node_gate_errors == None - assert bi.averaged_edge_gate_errors == None - assert bi.averaged_readout_errors == None + assert bi.all_node_gate_errors is None + assert bi.all_edge_gate_errors is None + assert bi.all_readout_errors is None + assert bi.averaged_node_gate_errors is None + assert bi.averaged_edge_gate_errors is None + assert bi.averaged_readout_errors is None example_node_error = {Node(0): {OpType.H: 0.3, OpType.X: 0.4}} example_averaged_readout_errors = {Node(1): 0.4, Node(0): 0.3} @@ -177,7 +176,7 @@ def test_fullyconnected() -> None: "name", "device_name", "version", 10, {OpType.CX, OpType.Rx} ) assert bi.n_nodes == 10 - assert type(bi.architecture) == FullyConnected + assert isinstance(bi.architecture, FullyConnected) # https://github.com/CQCL/tket/issues/390 d = bi.to_dict() diff --git a/pytket/tests/boxes/phase_poly_box_test.py b/pytket/tests/boxes/phase_poly_box_test.py index 4a54cb2d3d..d371ae2996 100644 --- a/pytket/tests/boxes/phase_poly_box_test.py +++ b/pytket/tests/boxes/phase_poly_box_test.py @@ -2,27 +2,26 @@ import numpy as np -from pytket.circuit import Circuit, Qubit, PhasePolyBox +from pytket.circuit import Circuit, PhasePolyBox, Qubit +from pytket.circuit.named_types import PhasePolynomialDict, PhasePolynomialSequence from pytket.passes import ComposePhasePolyBoxes, DecomposeBoxes from pytket.utils import compare_unitaries -from pytket.circuit.named_types import PhasePolynomialDict, PhasePolynomialSequence def phase_polynomials_are_equal( - phase_poly_0: Union[PhasePolynomialDict, PhasePolynomialSequence], - phase_poly_1: Union[PhasePolynomialDict, PhasePolynomialSequence], + phase_poly_0: PhasePolynomialDict | PhasePolynomialSequence, + phase_poly_1: PhasePolynomialDict | PhasePolynomialSequence, ) -> bool: if isinstance(phase_poly_0, dict) and isinstance(phase_poly_1, dict): return phase_poly_0 == phase_poly_1 # If lists are given the dicts created from them should compare equal. # Lists of pairs can contain duplicate "keys", this ensures the last defined value # for a key is what is compared, replicating a dict definition with duplicate keys. - elif isinstance(phase_poly_0, list) and isinstance(phase_poly_1, list): + if isinstance(phase_poly_0, list) and isinstance(phase_poly_1, list): to_compare_0 = {tuple(pair[0]): pair[1] for pair in phase_poly_0} to_compare_1 = {tuple(pair[0]): pair[1] for pair in phase_poly_1} return to_compare_0 == to_compare_1 - else: - raise NotImplementedError("comparison not implemented") + raise NotImplementedError("comparison not implemented") def test_phase_polybox() -> None: @@ -69,8 +68,8 @@ def test_phase_polybox_II() -> None: c = Circuit(1, 1) n_qb = 1 qubit_indices = {Qubit(0): 0} - phase_polynomial: PhasePolynomialDict = {(True,): 0.1, (True,): 0.3} - phase_polynomial_alt: PhasePolynomialSequence = [([True], 0.1), ([True], 0.3)] + phase_polynomial: PhasePolynomialDict = {(True,): 0.3} + phase_polynomial_alt: PhasePolynomialSequence = [([True], 0.3)] linear_transformation = np.array([[1]]) p_box = PhasePolyBox(n_qb, qubit_indices, phase_polynomial, linear_transformation) p_box_alt = PhasePolyBox( @@ -90,9 +89,6 @@ def test_phase_polybox_II() -> None: assert p_box_ii.n_qubits == n_qb assert p_box.qubit_indices == qubit_indices assert p_box_ii.qubit_indices == qubit_indices - print(phase_polynomial) - print(p_box.phase_polynomial_as_list) - print(p_box.phase_polynomial) assert phase_polynomials_are_equal(p_box.phase_polynomial, phase_polynomial) assert phase_polynomials_are_equal( p_box.phase_polynomial_as_list, phase_polynomial_alt diff --git a/pytket/tests/characterisation_test.py b/pytket/tests/characterisation_test.py index a1c7267f85..08ae9b74b7 100644 --- a/pytket/tests/characterisation_test.py +++ b/pytket/tests/characterisation_test.py @@ -13,6 +13,7 @@ # limitations under the License. from pytket.circuit import Circuit, OpType, Qubit +from pytket.pauli import Pauli, QubitPauliString, QubitPauliTensor from pytket.tailoring import ( FrameRandomisation, PauliFrameRandomisation, @@ -20,9 +21,6 @@ apply_clifford_basis_change, apply_clifford_basis_change_tensor, ) -from pytket.pauli import Pauli, QubitPauliString, QubitPauliTensor - -import pytest def test_single_cycle_single_frame_randomisation() -> None: diff --git a/pytket/tests/circuit_test.py b/pytket/tests/circuit_test.py index bd63087cbf..861e043640 100644 --- a/pytket/tests/circuit_test.py +++ b/pytket/tests/circuit_test.py @@ -13,82 +13,77 @@ # limitations under the License. import json +import math +import pickle +from math import sqrt +from pathlib import Path +import numpy as np +import pytest +import strategies as st # type: ignore +from hypothesis import given, settings from jsonschema import validate # type: ignore -from pathlib import Path -import pickle +from scipy.linalg import block_diag +from sympy import Expr, Symbol, exp, pi, sympify from pytket.circuit import ( + Bit, + BitRegister, + CircBox, Circuit, - Op, - OpType, + ClassicalExpBox, Command, - fresh_symbol, - CircBox, - Unitary1qBox, - Unitary2qBox, - Unitary3qBox, - MultiplexorBox, - MultiplexedRotationBox, - MultiplexedU2Box, - MultiplexedTensoredU2Box, - StatePreparationBox, - DiagonalBox, ConjugationBox, + CustomGate, + CustomGateDef, + CXConfigType, + DiagonalBox, + DummyBox, ExpBox, + MultiplexedRotationBox, + MultiplexedTensoredU2Box, + MultiplexedU2Box, + MultiplexorBox, + Op, + OpType, PauliExpBox, - PauliExpPairBox, PauliExpCommutingSetBox, + PauliExpPairBox, QControlBox, - TermSequenceBox, - ToffoliBox, - ToffoliBoxSynthStrat, - CustomGateDef, - CustomGate, Qubit, - Bit, - BitRegister, QubitRegister, - CXConfigType, ResourceBounds, ResourceData, - DummyBox, - ClassicalExpBox, + StatePreparationBox, + TermSequenceBox, + ToffoliBox, + ToffoliBoxSynthStrat, + Unitary1qBox, + Unitary2qBox, + Unitary3qBox, + fresh_symbol, ) from pytket.circuit.display import get_circuit_renderer, render_circuit_as_html from pytket.circuit.named_types import ( BitstringToOpList, - BitstringToTensoredOpMap, - BitstringToTensoredOpList, BitstringToOpMap, + BitstringToTensoredOpList, + BitstringToTensoredOpMap, ParamType, PermutationMap, ) - -from pytket.pauli import Pauli from pytket.passes import ( CliffordSimp, - SynthesiseTket, DecomposeBoxes, RemoveRedundancies, + SynthesiseTket, ) -from pytket.transform import Transform, PauliSynthStrat - -import numpy as np -from scipy.linalg import block_diag -import sympy -from sympy import Symbol, pi, sympify, functions, Expr, exp -import math -from math import sqrt - -import pytest - -from hypothesis import given, settings -import strategies as st # type: ignore +from pytket.pauli import Pauli +from pytket.transform import PauliSynthStrat, Transform curr_file_path = Path(__file__).resolve().parent -with open(curr_file_path.parent.parent / "schemas/circuit_v1.json", "r") as f: +with open(curr_file_path.parent.parent / "schemas/circuit_v1.json") as f: schema = json.load(f) _0 = False @@ -788,11 +783,23 @@ def test_custom_gates() -> None: op0 = cmd0.op assert gate.type == op0.type assert gate.params == op0.params + c_d = c.dagger() + c_t = c.transpose() Transform.DecomposeBoxes().apply(c) coms = c.get_commands() assert str(coms[0]) == "CX q[0], q[3];" assert str(coms[1]) == "Rz(0.7) q[1];" assert str(coms[2]) == "CRz(1.3) q[0], q[1];" + Transform.DecomposeBoxes().apply(c_d) + coms_d = c_d.get_commands() + assert str(coms_d[0]) == "CRz(2.7) q[0], q[1];" + assert str(coms_d[1]) == "CX q[0], q[3];" + assert str(coms_d[2]) == "Rz(3.3) q[1];" + Transform.DecomposeBoxes().apply(c_t) + coms_t = c_t.get_commands() + assert str(coms_t[0]) == "CRz(1.3) q[0], q[1];" + assert str(coms_t[1]) == "CX q[0], q[3];" + assert str(coms_t[2]) == "Rz(0.7) q[1];" def test_errors() -> None: @@ -1002,7 +1009,7 @@ def test_commands_of_type() -> None: def test_empty_circuit() -> None: circ = Circuit(0) circt_dict = circ.to_dict() - assert type(circt_dict) == type({}) + assert isinstance(circt_dict, dict) assert len(circt_dict) > 0 assert Circuit(0) == Circuit(0) @@ -1133,13 +1140,13 @@ def test_clifford_checking() -> None: cx = c.get_commands()[1].op assert cx.is_clifford_type() t = c.get_commands()[2].op - assert t.is_clifford_type() == False + assert not t.is_clifford_type() rz1 = c.get_commands()[3].op - assert rz1.is_clifford_type() == False + assert not rz1.is_clifford_type() rz2 = c.get_commands()[4].op - assert rz2.is_clifford_type() == False + assert not rz2.is_clifford_type() m = c.get_commands()[5].op - assert m.is_clifford_type() == False + assert not m.is_clifford_type() def test_clifford_evaluation() -> None: @@ -1150,7 +1157,7 @@ def test_clifford_evaluation() -> None: iswap = c.get_commands()[1].op assert iswap.is_clifford() rz = c.get_commands()[2].op - assert rz.is_clifford() == False + assert not rz.is_clifford() def test_getting_registers() -> None: @@ -1265,6 +1272,22 @@ def test_counting_n_qubit_gates() -> None: assert c.n_nqb_gates(5) == 1 +def test_counting_conditional_gates() -> None: + c = Circuit(5, 2).X(0).H(1).Y(2).Z(3).S(4).CX(0, 1).CX(1, 2).CX(2, 3).CX(3, 4) + c.add_gate(OpType.H, [Qubit(0)], condition=Bit(0)) + c.add_gate(OpType.H, [Qubit(1)], condition=(Bit(0) & Bit(1))) + c.add_gate(OpType.CX, [Qubit(0), Qubit(1)], condition=Bit(1)) + assert c.n_gates_of_type(OpType.H, include_conditional=True) == 3 + assert c.n_gates_of_type(OpType.H, include_conditional=False) == 1 + assert c.n_gates_of_type(OpType.H) == 1 + assert c.n_gates_of_type(OpType.X, include_conditional=True) == 1 + assert c.n_gates_of_type(OpType.X, include_conditional=False) == 1 + assert c.n_gates_of_type(OpType.X) == 1 + assert c.n_gates_of_type(OpType.CX, include_conditional=True) == 5 + assert c.n_gates_of_type(OpType.CX, include_conditional=False) == 4 + assert c.n_gates_of_type(OpType.CX) == 4 + + def test_qcontrol_box_constructors() -> None: # only one argument qcbox1 = QControlBox(Op.create(OpType.S)) @@ -1423,8 +1446,8 @@ def test_add_circbox_with_registers() -> None: c0.CCX(breg[0], breg[1], breg[2]) cbox = CircBox(c0) c = Circuit() - xreg = c.add_q_register("x", 3) - yreg = c.add_q_register("y", 2) + c.add_q_register("x", 3) + c.add_q_register("y", 2) zreg = c.add_q_register("z", 3) wreg = c.add_q_register("w", 2) for qb in c.qubits: @@ -1442,16 +1465,16 @@ def test_add_circbox_with_registers() -> None: def test_add_circbox_with_mixed_registers() -> None: c0 = Circuit() - c0_qreg1 = c0.add_q_register("q1", 2) - c0_qreg2 = c0.add_q_register("q2", 3) - c0_creg1 = c0.add_c_register("c1", 4) - c0_creg2 = c0.add_c_register("c2", 5) + c0.add_q_register("q1", 2) + c0.add_q_register("q2", 3) + c0.add_c_register("c1", 4) + c0.add_c_register("c2", 5) cbox = CircBox(c0) c = Circuit() - c_qreg1 = c.add_q_register("q1", 2) - c_qreg2 = c.add_q_register("q2", 3) - c_creg1 = c.add_c_register("c1", 4) - c_creg2 = c.add_c_register("c2", 5) + c.add_q_register("q1", 2) + c.add_q_register("q2", 3) + c.add_c_register("c1", 4) + c.add_c_register("c2", 5) c.add_circbox_with_regmap( cbox, qregmap={"q1": "q1", "q2": "q2"}, cregmap={"c1": "c1", "c2": "c2"} @@ -1537,7 +1560,7 @@ def test_bad_circbox() -> None: b = circ.add_c_register("b", 5) c = circ.add_c_register("c", 5) circ.add_classicalexpbox_register(a | b, c.to_list()) - with pytest.raises(RuntimeError) as e: + with pytest.raises(RuntimeError): _ = CircBox(circ) diff --git a/pytket/tests/classical_test.py b/pytket/tests/classical_test.py index e365074270..8364e11115 100644 --- a/pytket/tests/classical_test.py +++ b/pytket/tests/classical_test.py @@ -12,40 +12,42 @@ # See the License for the specific language governing permissions and # limitations under the License. -import operator -from typing import Callable, Dict, List, Tuple, Union, TypeVar import json +import operator +from collections.abc import Callable from pathlib import Path +from typing import Dict, List, Tuple, TypeVar, Union -from jsonschema import validate # type: ignore +import pytest from hypothesis import given, settings, strategies from hypothesis.strategies import SearchStrategy +from jsonschema import validate # type: ignore +from strategies import binary_digits, reg_name_regex, uint32, uint64 # type: ignore +from sympy import Symbol from pytket import wasm - -import pytest from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE from pytket.circuit import ( - BitRegister, - QubitRegister, Bit, - UnitID, + BitRegister, Circuit, - OpType, - Qubit, + ClassicalExpBox, Conditional, - Op, - SetBitsOp, MultiBitOp, + Op, + OpType, + Qubit, + QubitRegister, RangePredicateOp, - ClassicalExpBox, + SetBitsOp, ) from pytket.circuit.logic_exp import ( BinaryOp, BitLogicExp, BitWiseOp, - PredicateExp, LogicExp, + NullaryOp, + PredicateExp, RegEq, RegGeq, RegGt, @@ -56,29 +58,23 @@ RegPow, RegWiseOp, UnaryOp, - NullaryOp, + create_bit_logic_exp, + create_reg_logic_exp, + if_bit, + if_not_bit, reg_eq, reg_geq, reg_gt, reg_leq, reg_lt, reg_neq, - if_bit, - if_not_bit, - create_bit_logic_exp, - create_reg_logic_exp, ) from pytket.circuit.named_types import RenameUnitsMap - from pytket.passes import DecomposeClassicalExp, FlattenRegisters -from sympy import Symbol - -from strategies import reg_name_regex, binary_digits, uint32, uint64 # type: ignore - curr_file_path = Path(__file__).resolve().parent -with open(curr_file_path.parent.parent / "schemas/circuit_v1.json", "r") as f: +with open(curr_file_path.parent.parent / "schemas/circuit_v1.json") as f: schema = json.load(f) @@ -358,10 +354,10 @@ def test_wasm_12() -> None: def test_wasm_handler() -> None: - w = wasm.WasmFileHandler("testfile.wasm") + wasm.WasmFileHandler("testfile.wasm") with pytest.raises(ValueError): - w2 = wasm.WasmFileHandler("notexistingfile.wasm") + wasm.WasmFileHandler("notexistingfile.wasm") def test_wasm_function_check() -> None: @@ -452,7 +448,7 @@ def test_wasm_function_check_8() -> None: c = Circuit(20, 20) c0 = c.add_c_register("c0", 32) c1 = c.add_c_register("c1", 4) - c2 = c.add_c_register("c2", 5) + c.add_c_register("c2", 5) c.add_wasm_to_reg("add_something", w, [c0], [c1]) assert c.depth() == 1 @@ -463,7 +459,7 @@ def test_wasm_function_check_9() -> None: c = Circuit(20, 20) c0 = c.add_c_register("c0", 53) c1 = c.add_c_register("c1", 4) - c2 = c.add_c_register("c2", 5) + c.add_c_register("c2", 5) with pytest.raises(ValueError): c.add_wasm_to_reg("add_something", w, [c0], [c1]) @@ -487,7 +483,7 @@ def test_add_wasm_to_reg() -> None: def test_wasmfilehandler_without_init() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler("testfile-without-init.wasm") + _ = wasm.WasmFileHandler("testfile-without-init.wasm") def test_wasmfilehandler_without_init_no_check() -> None: @@ -507,34 +503,34 @@ def test_wasmfilehandler_without_init_no_check() -> None: def test_wasmfilehandler_invalid_file_1_c_32() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-1-emcc.wasm", int_size=32 ) def test_wasmfilehandler_invalid_file_1_c_64() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-1-emcc.wasm", int_size=64 ) def test_wasmfilehandler_invalid_file_1_e_32() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=32 ) def test_wasmfilehandler_invalid_file_1_e_64() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=64 ) def test_wasmfilehandler_invalid_file_1_c_32_no_check() -> None: - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-1-emcc.wasm", int_size=32, check_file=False, @@ -542,7 +538,7 @@ def test_wasmfilehandler_invalid_file_1_c_32_no_check() -> None: def test_wasmfilehandler_invalid_file_1_c_64_no_check() -> None: - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-1-emcc.wasm", int_size=64, check_file=False, @@ -550,7 +546,7 @@ def test_wasmfilehandler_invalid_file_1_c_64_no_check() -> None: def test_wasmfilehandler_invalid_file_1_e_32_no_check() -> None: - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=32, check_file=False, @@ -558,7 +554,7 @@ def test_wasmfilehandler_invalid_file_1_e_32_no_check() -> None: def test_wasmfilehandler_invalid_file_1_e_64_no_check() -> None: - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=64, check_file=False, @@ -592,7 +588,7 @@ def test_wasmfilehandler_repr() -> None: function 'no_parameters' with 0 i32 parameter(s) and 1 i32 return value(s) function 'new_function' with 0 i32 parameter(s) and 1 i32 return value(s) unsupported function with invalid parameter or result type: 'add_something' -""" +""" # noqa: W291 ) @@ -610,7 +606,7 @@ def test_wasmfilehandler_repr_64() -> None: unsupported function with invalid parameter or result type: 'no_return' unsupported function with invalid parameter or result type: 'no_parameters' unsupported function with invalid parameter or result type: 'new_function' -""" +""" # noqa: W291 ) @@ -633,7 +629,7 @@ def test_wasmfilehandler_repr_2() -> None: function 'mixed_up_3' with 3 i32 parameter(s) and 1 i32 return value(s) function 'unse_internal' with 1 i32 parameter(s) and 1 i32 return value(s) unsupported function with invalid parameter or result type: 'add_something' -""" +""" # noqa: W291 ) @@ -656,7 +652,7 @@ def test_wasmfilehandler_repr_64_2() -> None: unsupported function with invalid parameter or result type: 'mixed_up_2' unsupported function with invalid parameter or result type: 'mixed_up_3' unsupported function with invalid parameter or result type: 'unse_internal' -""" +""" # noqa: W291 ) @@ -684,7 +680,7 @@ def test_wasmfilehandler_multivalue_clang() -> None: function '__wasm_call_ctors' with 0 i32 parameter(s) and 0 i32 return value(s) function 'init' with 0 i32 parameter(s) and 0 i32 return value(s) unsupported function with invalid parameter or result type: 'divmod' -""" +""" # noqa: W291 ) @@ -767,7 +763,7 @@ def qubit_register( reg=strategies.one_of(bit_register(), qubit_register()), index=strategies.integers(min_value=0, max_value=32), ) -def test_registers(reg: Union[BitRegister, QubitRegister], index: int) -> None: +def test_registers(reg: BitRegister | QubitRegister, index: int) -> None: unit_type = Qubit if type(reg) is QubitRegister else Bit if index < reg.size: assert reg[index] == unit_type(reg.name, index) @@ -819,11 +815,11 @@ def wrapper(*args: int) -> int: bit_exp=primitive_bit_logic_exps(), constants=strategies.tuples(binary_digits, binary_digits), ) -def test_bit_exp(bit_exp: BitLogicExp, constants: Tuple[int, int]) -> None: +def test_bit_exp(bit_exp: BitLogicExp, constants: tuple[int, int]) -> None: iter_c = iter(constants) for inp in bit_exp.all_inputs(): bit_exp.set_value(inp, next(iter_c)) - op_map: Dict[BitWiseOp, Callable] = { + op_map: dict[BitWiseOp, Callable] = { BitWiseOp.AND: operator.and_, BitWiseOp.OR: operator.or_, BitWiseOp.XOR: operator.xor, @@ -856,7 +852,7 @@ def primitive_reg_logic_exps( op = draw(ops) exp_type = LogicExp.factory(op) - args: List[BitRegister] = [draw(bit_regs)] + args: list[BitRegister] = [draw(bit_regs)] if issubclass(exp_type, BinaryOp): if issubclass( exp_type, @@ -887,7 +883,7 @@ def primitive_reg_logic_exps( uint64, ), ) -def test_reg_exp(reg_exp: RegLogicExp, constants: Tuple[int, int]) -> None: +def test_reg_exp(reg_exp: RegLogicExp, constants: tuple[int, int]) -> None: if isinstance(reg_exp, RegPow): # to stop massive numbers constants = (min(1000, constants[0]), min(constants[1], 3)) @@ -895,7 +891,7 @@ def test_reg_exp(reg_exp: RegLogicExp, constants: Tuple[int, int]) -> None: for inp in reg_exp.all_inputs(): reg_exp.set_value(inp, next(iter_c)) - op_map: Dict[RegWiseOp, Callable] = { + op_map: dict[RegWiseOp, Callable] = { RegWiseOp.AND: operator.and_, RegWiseOp.OR: operator.or_, RegWiseOp.XOR: operator.xor, @@ -993,7 +989,7 @@ def bit_const_predicates( draw: DrawType, exp: SearchStrategy[BitLogicExp] = composite_bit_logic_exps(), operators: SearchStrategy[ - Callable[[Union[Bit, BitLogicExp]], PredicateExp] + Callable[[Bit | BitLogicExp], PredicateExp] ] = strategies.sampled_from([if_bit, if_not_bit]), ) -> PredicateExp: func = draw(operators) @@ -1006,7 +1002,7 @@ def reg_const_predicates( draw: DrawType, exp: SearchStrategy[RegLogicExp] = composite_reg_logic_exps(), operators: SearchStrategy[ - Callable[[Union[RegLogicExp, BitRegister], int], PredicateExp] + Callable[[RegLogicExp | BitRegister, int], PredicateExp] ] = strategies.sampled_from([reg_eq, reg_neq, reg_lt, reg_gt, reg_leq, reg_geq]), constants: SearchStrategy[int] = uint64, ) -> PredicateExp: @@ -1037,7 +1033,7 @@ def test_regpredicate(condition: PredicateExp) -> None: circ.add_bit(inp, reject_dups=False) circ.X(qb, condition=condition) - assert circ.n_gates_of_type(OpType.ClassicalExpBox) == 1 + assert circ.n_gates_of_type(OpType.ClassicalExpBox) == 0 newcirc = circ.copy() DecomposeClassicalExp().apply(newcirc) @@ -1115,178 +1111,6 @@ def check_serialization_roundtrip(circ: Circuit) -> None: assert circ_from_dict.to_dict() == circ_dict -def test_decomposition_known() -> None: - bits = [Bit(i) for i in range(10)] - registers = [BitRegister(c, 3) for c in "abdefghijk"] - - qreg = QubitRegister("q_", 10) - circ = Circuit() - conditioned_circ = Circuit() - decomposed_circ = Circuit() - - for c in (circ, conditioned_circ, decomposed_circ): - for b in bits: - c.add_bit(b) - for br in registers: - for b in br.to_list(): - c.add_bit(b, reject_dups=False) - c.add_q_register(qreg.name, qreg.size) - - circ.H(qreg[0], condition=bits[0]) - circ.X(qreg[0], condition=if_bit(bits[1])) - circ.S(qreg[0]) - circ.T(qreg[1], condition=if_not_bit(bits[2])) - circ.Z(qreg[0], condition=(bits[2] & bits[3])) - circ.Z(qreg[1], condition=if_not_bit(bits[3] & bits[4])) - big_exp = bits[4] | bits[5] ^ bits[6] | bits[7] & bits[8] - # ^ no need for parantheses as python operator precedence - # will enforce correct precedence in LogicExp - circ.CX(qreg[0], qreg[1]) - circ.CX(qreg[1], qreg[2], condition=big_exp) - - circ.add_barrier(qreg.to_list()) - - circ.H(qreg[2], condition=reg_eq(registers[0], 3)) - circ.X(qreg[3], condition=reg_lt(registers[1], 6)) - circ.Y(qreg[4], condition=reg_neq(registers[2], 5)) - circ.Z(qreg[5], condition=reg_gt(registers[3], 3)) - circ.S(qreg[6], condition=reg_leq(registers[4], 6)) - circ.T(qreg[7], condition=reg_geq(registers[5], 3)) - big_reg_exp = registers[4] & registers[3] | registers[6] ^ registers[7] - circ.CX(qreg[3], qreg[4], condition=reg_eq(big_reg_exp, 3)) - - circ.add_classicalexpbox_bit( - bits[4] | bits[5] & bits[3], [bits[0]], condition=bits[1] - ) - check_serialization_roundtrip(circ) - - temp_bits = BitRegister(_TEMP_BIT_NAME, 64) - - def temp_reg(i: int) -> BitRegister: - return BitRegister(f"{_TEMP_BIT_REG_BASE}_{i}", 64) - - for b in (temp_bits[i] for i in range(0, 10)): - conditioned_circ.add_bit(b) - - for t_r in (temp_reg(i) for i in range(0, 1)): - conditioned_circ.add_c_register(t_r.name, t_r.size) - - # relies on existing interface for adding conditionals - # may need a more low level interface for that if we decide to get rid of it - conditioned_circ.H(qreg[0], condition_bits=[bits[0]], condition_value=1) - conditioned_circ.X(qreg[0], condition_bits=[bits[1]], condition_value=1) - conditioned_circ.S(qreg[0]) - conditioned_circ.T(qreg[1], condition_bits=[bits[2]], condition_value=0) - - conditioned_circ.add_classicalexpbox_bit((bits[2] & bits[3]), [temp_bits[0]]) - conditioned_circ.Z(qreg[0], condition_bits=[temp_bits[0]], condition_value=1) - conditioned_circ.add_classicalexpbox_bit((bits[3] & bits[4]), [temp_bits[1]]) - conditioned_circ.Z(qreg[1], condition_bits=[temp_bits[1]], condition_value=0) - conditioned_circ.CX(qreg[0], qreg[1]) - conditioned_circ.add_classicalexpbox_bit(big_exp, [temp_bits[2]]) - conditioned_circ.CX( - qreg[1], qreg[2], condition_bits=[temp_bits[2]], condition_value=1 - ) - - conditioned_circ.add_barrier(qreg.to_list()) - - registers_lists = [reg.to_list() for reg in registers] - - conditioned_circ.add_c_range_predicate(3, 3, registers_lists[0], temp_bits[3]) - conditioned_circ.H(qreg[2], condition_bits=[temp_bits[3]], condition_value=1) - conditioned_circ.add_c_range_predicate(0, 5, registers_lists[1], temp_bits[4]) - conditioned_circ.X(qreg[3], condition_bits=[temp_bits[4]], condition_value=1) - conditioned_circ.add_c_range_predicate(5, 5, registers_lists[2], temp_bits[5]) - conditioned_circ.Y(qreg[4], condition_bits=[temp_bits[5]], condition_value=0) - conditioned_circ.add_c_range_predicate( - 4, 18446744073709551615, registers_lists[3], temp_bits[6] - ) - conditioned_circ.Z(qreg[5], condition_bits=[temp_bits[6]], condition_value=1) - conditioned_circ.add_c_range_predicate(0, 6, registers_lists[4], temp_bits[7]) - conditioned_circ.S(qreg[6], condition_bits=[temp_bits[7]], condition_value=1) - conditioned_circ.add_c_range_predicate( - 3, 18446744073709551615, registers_lists[5], temp_bits[8] - ) - conditioned_circ.T(qreg[7], condition_bits=[temp_bits[8]], condition_value=1) - - temp_reg_bits = [temp_reg(0)[i] for i in range(3)] - conditioned_circ.add_classicalexpbox_register(big_reg_exp, temp_reg_bits) - conditioned_circ.add_c_range_predicate(3, 3, temp_reg_bits, temp_bits[9]) - conditioned_circ.CX( - qreg[3], qreg[4], condition_bits=[temp_bits[9]], condition_value=1 - ) - conditioned_circ.add_classicalexpbox_bit( - bits[4] | bits[5] & bits[3], [bits[0]], condition=bits[1] - ) - - assert compare_commands_box(circ, conditioned_circ) - - for b in (temp_bits[i] for i in range(0, 12)): - decomposed_circ.add_bit(b) - - decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_0", 3)) - decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_1", 64)) - decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_2", 64)) - - decomposed_circ.H(qreg[0], condition_bits=[bits[0]], condition_value=1) - decomposed_circ.X(qreg[0], condition_bits=[bits[1]], condition_value=1) - decomposed_circ.S(qreg[0]) - decomposed_circ.T(qreg[1], condition_bits=[bits[2]], condition_value=0) - decomposed_circ.add_c_and(bits[2], bits[3], temp_bits[0]) - decomposed_circ.Z(qreg[0], condition_bits=[temp_bits[0]], condition_value=1) - decomposed_circ.add_c_and(bits[3], bits[4], temp_bits[1]) - decomposed_circ.Z(qreg[1], condition_bits=[temp_bits[1]], condition_value=0) - decomposed_circ.CX(qreg[0], qreg[1]) - decomposed_circ.add_c_range_predicate(3, 3, registers_lists[0], temp_bits[3]) - decomposed_circ.add_c_range_predicate(0, 5, registers_lists[1], temp_bits[4]) - decomposed_circ.add_c_range_predicate(5, 5, registers_lists[2], temp_bits[5]) - decomposed_circ.add_c_range_predicate( - 4, 18446744073709551615, registers_lists[3], temp_bits[6] - ) - decomposed_circ.add_c_range_predicate(0, 6, registers_lists[4], temp_bits[7]) - decomposed_circ.add_c_range_predicate( - 3, 18446744073709551615, registers_lists[5], temp_bits[8] - ) - - decomposed_circ.add_c_xor(bits[5], bits[6], temp_bits[10]) - decomposed_circ.add_c_and(bits[7], bits[8], temp_bits[11]) - decomposed_circ.add_c_or(bits[4], temp_bits[10], temp_bits[10]) - decomposed_circ.add_c_or(temp_bits[10], temp_bits[11], temp_bits[2]) - decomposed_circ.CX( - qreg[1], qreg[2], condition_bits=[temp_bits[2]], condition_value=1 - ) - - decomposed_circ.add_barrier(qreg.to_list()) - - decomposed_circ.H(qreg[2], condition_bits=[temp_bits[3]], condition_value=1) - decomposed_circ.X(qreg[3], condition_bits=[temp_bits[4]], condition_value=1) - decomposed_circ.Y(qreg[4], condition_bits=[temp_bits[5]], condition_value=0) - decomposed_circ.Z(qreg[5], condition_bits=[temp_bits[6]], condition_value=1) - decomposed_circ.S(qreg[6], condition_bits=[temp_bits[7]], condition_value=1) - decomposed_circ.T(qreg[7], condition_bits=[temp_bits[8]], condition_value=1) - - decomposed_circ.add_c_and_to_registers(registers[4], registers[3], temp_reg(1)) - decomposed_circ.add_c_xor_to_registers(registers[6], registers[7], temp_reg(2)) - decomposed_circ.add_c_or_to_registers( - temp_reg(1), BitRegister(temp_reg(2).name, 3), temp_reg(0) - ) - decomposed_circ.add_c_range_predicate(3, 3, temp_reg(0).to_list()[:3], temp_bits[9]) - decomposed_circ.CX( - qreg[3], qreg[4], condition_bits=[temp_bits[9]], condition_value=1 - ) - decomposed_circ.add_c_and( - bits[5], bits[3], temp_bits[10], condition_bits=[bits[1]], condition_value=1 - ) - decomposed_circ.add_c_or( - bits[4], temp_bits[10], bits[0], condition_bits=[bits[1]], condition_value=1 - ) - check_serialization_roundtrip(decomposed_circ) - circ_copy = circ.copy() - - DecomposeClassicalExp().apply(circ_copy) - assert circ_copy == decomposed_circ - - def test_conditional() -> None: c = Circuit(1, 2) c.H(0, condition_bits=[0, 1], condition_value=3) @@ -1448,7 +1272,7 @@ def test_renaming() -> None: c = circ.add_c_register("c", 3) circ.add_classicalexpbox_bit(a[0] & b[0] | c[0], [a[0]]) circ.add_classicalexpbox_bit(a[0] & c[2], [c[0]]) - d = [Bit("d", index) for index in range(0, 3)] + d = [Bit("d", index) for index in range(3)] bmap: RenameUnitsMap = {a[0]: d[0], b[0]: d[1], c[0]: d[2]} original_commands = circ.get_commands() assert circ.rename_units(bmap) diff --git a/pytket/tests/clexpr_test.py b/pytket/tests/clexpr_test.py index e078224aa0..a9e81bca21 100644 --- a/pytket/tests/clexpr_test.py +++ b/pytket/tests/clexpr_test.py @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json +from pathlib import Path + +from jsonschema import validate # type: ignore + from pytket.circuit import ( Bit, CircBox, @@ -21,10 +26,14 @@ ClExprOp, ClOp, ClRegVar, - OpType, WiredClExpr, ) -from pytket.qasm import circuit_to_qasm_str, circuit_from_qasm_str +from pytket.qasm import circuit_from_qasm_str, circuit_to_qasm_str + +with open( + Path(__file__).resolve().parent.parent.parent / "schemas/circuit_v1.json" +) as f: + SCHEMA = json.load(f) def test_op() -> None: @@ -114,16 +123,25 @@ def test_wexpr() -> None: def test_adding_to_circuit() -> None: - expr = ClExpr(op=ClOp.BitXor, args=[ClBitVar(0), ClBitVar(1)]) - wexpr = WiredClExpr(expr=expr, bit_posn={0: 0, 1: 1}, output_posn=[2]) + expr0 = ClExpr(op=ClOp.BitXor, args=[ClBitVar(0), ClBitVar(1)]) + wexpr0 = WiredClExpr(expr=expr0, bit_posn={0: 0, 1: 1}, output_posn=[2]) + expr1 = ClExpr( + op=ClOp.RegDiv, + args=[ClRegVar(0), ClExpr(op=ClOp.RegAdd, args=[2, ClBitVar(0)])], + ) + wexpr1 = WiredClExpr( + expr=expr1, bit_posn={0: 1}, reg_posn={0: [2, 0]}, output_posn=[2, 0] + ) c = Circuit(0, 3) - c.add_clexpr(wexpr, c.bits) + c.add_clexpr(wexpr0, c.bits) + c.add_clexpr(wexpr1, c.bits) cmds = c.get_commands() - assert len(cmds) == 1 + assert len(cmds) == 2 op = cmds[0].op assert isinstance(op, ClExprOp) - assert op.expr == wexpr + assert op.expr == wexpr0 d = c.to_dict() + validate(instance=d, schema=SCHEMA) c1 = Circuit.from_dict(d) assert c == c1 d1 = c1.to_dict() @@ -217,3 +235,23 @@ def test_circbox() -> None: c2 = c1.copy() c2.flatten_registers() assert c1 == c2 + + +def test_add_logicexp_as_clexpr() -> None: + c = Circuit() + a_reg = c.add_c_register("a", 3) + b_reg = c.add_c_register("b", 3) + c_reg = c.add_c_register("c", 3) + c.add_clexpr_from_logicexp(a_reg | b_reg, c_reg.to_list()) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg a[3]; +creg b[3]; +creg c[3]; +c = (a | b); +""" + ) diff --git a/pytket/tests/compilation_test.py b/pytket/tests/compilation_test.py index fd87e95f2b..c5a22beeb3 100644 --- a/pytket/tests/compilation_test.py +++ b/pytket/tests/compilation_test.py @@ -12,17 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + +from pytket.architecture import Architecture from pytket.circuit import Circuit, OpType, reg_eq from pytket.passes import ( + CXMappingPass, FullPeepholeOptimise, PassSelector, - CXMappingPass, scratch_reg_resize_pass, ) -from pytket.architecture import Architecture from pytket.placement import Placement from pytket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE -import pytest def test_compilation() -> None: @@ -182,9 +183,6 @@ def circ_depth(circ: Circuit) -> int: def test_PassSelector_wrong_pass() -> None: - fp = FullPeepholeOptimise() - fp2 = FullPeepholeOptimise(allow_swaps=False) - arc = Architecture([(1, 2)]) pl = Placement(arc) @@ -200,7 +198,7 @@ def circ_depth(circ: Circuit) -> int: circ = Circuit(3).H(1).H(0).H(1).H(0).X(1).CX(1, 0).CX(0, 1).CX(1, 2) with pytest.raises(Exception): - result = sp.apply(circ) + _ = sp.apply(circ) def test_PassSelector_empty_pass() -> None: @@ -208,7 +206,7 @@ def circ_depth(circ: Circuit) -> int: return circ.depth() with pytest.raises(Exception): - sp = PassSelector([], circ_depth) + _ = PassSelector([], circ_depth) def test_PassSelector_ii() -> None: @@ -309,7 +307,7 @@ def test_resize_scratch_registers() -> None: reg_a = circ.add_c_register("a", 1) reg_b = circ.add_c_register("b", 1) circ.X(0, condition=reg_eq(reg_a ^ reg_b, 1)) - assert circ.get_c_register(f"{_TEMP_BIT_REG_BASE}_0").size == 64 + assert circ.get_c_register(f"{_TEMP_BIT_REG_BASE}_0").size == 1 c_compiled = circ.copy() scratch_reg_resize_pass(10).apply(c_compiled) assert circ == c_compiled diff --git a/pytket/tests/distribution_test.py b/pytket/tests/distribution_test.py index 22a6240ae8..ec370b7503 100644 --- a/pytket/tests/distribution_test.py +++ b/pytket/tests/distribution_test.py @@ -13,13 +13,15 @@ # limitations under the License. from collections import Counter + +import pytest from numpy import isclose + from pytket.utils import ( - ProbabilityDistribution, EmpiricalDistribution, + ProbabilityDistribution, convex_combination, ) -import pytest def test_probability_distribution() -> None: @@ -34,7 +36,7 @@ def test_probability_distribution() -> None: assert pd1["c"] == 0.0 assert pd == pd1 with pytest.warns(UserWarning): - pd2 = ProbabilityDistribution({"a": 2 / 3, "b": 4 / 3}) + _ = ProbabilityDistribution({"a": 2 / 3, "b": 4 / 3}) pd3 = ProbabilityDistribution({"a": 2 / 9, "b": 4 / 9, "c": 1 / 3}) pd4 = convex_combination([(pd, 1 / 3), (pd3, 2 / 3)]) assert pd4 == ProbabilityDistribution({"a": 7 / 27, "b": 14 / 27, "c": 2 / 9}) diff --git a/pytket/tests/logic_exp_test.py b/pytket/tests/logic_exp_test.py index fe7648ceda..2e3c16b97f 100644 --- a/pytket/tests/logic_exp_test.py +++ b/pytket/tests/logic_exp_test.py @@ -1,18 +1,18 @@ from pytket._tket.unit_id import BitRegister from pytket.circuit.logic_exp import ( - RegOr, - RegAnd, - BitOr, BitAnd, + BitOr, BitXor, - RegXor, RegAdd, - RegSub, - RegMul, + RegAnd, RegDiv, - RegPow, RegLsh, + RegMul, + RegOr, + RegPow, RegRsh, + RegSub, + RegXor, ) diff --git a/pytket/tests/mapping_test.py b/pytket/tests/mapping_test.py index 0093e46733..d8419cf660 100644 --- a/pytket/tests/mapping_test.py +++ b/pytket/tests/mapping_test.py @@ -11,31 +11,32 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from types import MappingProxyType +from typing import Dict, Tuple + +import numpy as np + +from pytket import Circuit, OpType +from pytket.architecture import Architecture +from pytket.circuit import CircBox, Node, PhasePolyBox, Qubit, UnitID from pytket.circuit.named_types import UnitIdMap from pytket.mapping import ( - MappingManager, - RoutingMethodCircuit, - LexiRouteRoutingMethod, + AASLabellingMethod, AASRouteRoutingMethod, + BoxDecompositionRoutingMethod, LexiLabellingMethod, - AASLabellingMethod, + LexiRouteRoutingMethod, + MappingManager, MultiGateReorderRoutingMethod, - BoxDecompositionRoutingMethod, + RoutingMethodCircuit, ) -from pytket.architecture import Architecture -from pytket import Circuit, OpType -from pytket.circuit import UnitID, Node, PhasePolyBox, Qubit, CircBox from pytket.placement import Placement -from typing import Tuple, Dict -import numpy as np # simple deterministic heuristic used for testing purposes def route_subcircuit_func( circuit: Circuit, architecture: Architecture -) -> Tuple[bool, Circuit, UnitIdMap, UnitIdMap]: +) -> tuple[bool, Circuit, UnitIdMap, UnitIdMap]: # make a replacement circuit with identical unitds replacement_circuit = Circuit() for qb in circuit.qubits: @@ -112,7 +113,7 @@ def route_subcircuit_func( def route_subcircuit_func_false( circuit: Circuit, architecture: Architecture -) -> Tuple[bool, Circuit, Dict[UnitID, UnitID], Dict[UnitID, UnitID]]: +) -> tuple[bool, Circuit, dict[UnitID, UnitID], dict[UnitID, UnitID]]: return (False, Circuit(), {}, {}) diff --git a/pytket/tests/measurement_setup_test.py b/pytket/tests/measurement_setup_test.py index 0beb01c7b7..fa25c1f480 100644 --- a/pytket/tests/measurement_setup_test.py +++ b/pytket/tests/measurement_setup_test.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any + from pytket import logging from pytket.circuit import Circuit, Qubit -from pytket.pauli import Pauli, QubitPauliString from pytket.partition import ( - PauliPartitionStrat, MeasurementBitMap, MeasurementSetup, + PauliPartitionStrat, measurement_reduction, ) - -import pytest -import platform -from typing import Any +from pytket.pauli import Pauli, QubitPauliString def test_empty_setup() -> None: diff --git a/pytket/tests/mitigation_test.py b/pytket/tests/mitigation_test.py index d6599a4335..aa339e0b7f 100644 --- a/pytket/tests/mitigation_test.py +++ b/pytket/tests/mitigation_test.py @@ -12,26 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -import tempfile import json +import tempfile +from collections import Counter +from math import ceil +from typing import Dict, List, Tuple, cast + +import pytest -from pytket.utils.spam import SpamCorrecter, compress_counts -from pytket.circuit import Node, Circuit, Qubit -from pytket.mapping import MappingManager, LexiLabellingMethod, LexiRouteRoutingMethod from pytket.architecture import Architecture -from pytket.placement import place_with_map +from pytket.backends.backendresult import BackendResult +from pytket.circuit import Circuit, Node, Qubit +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager from pytket.passes import DelayMeasures -from typing import List, Dict, Counter, Tuple, cast +from pytket.placement import place_with_map from pytket.utils.outcomearray import OutcomeArray -from math import ceil -from pytket.backends.backendresult import BackendResult -import pytest +from pytket.utils.spam import SpamCorrecter, compress_counts def test_spam_integration() -> None: - SEED = 120 # test data, to avoid using backend - calib_results: List[Dict[Tuple[int, ...], int]] = [ + calib_results: list[dict[tuple[int, ...], int]] = [ { (0, 0, 0): 742, (0, 0, 1): 84, @@ -73,7 +74,7 @@ def test_spam_integration() -> None: (1, 1, 1): 424, }, ] - bellres_counts: Dict[Tuple[int, ...], int] = { + bellres_counts: dict[tuple[int, ...], int] = { (0, 0, 0): 406, (0, 0, 1): 111, (0, 1, 0): 38, @@ -116,7 +117,7 @@ def test_spam_integration() -> None: mm.route_circuit(rbell, [LexiLabellingMethod(), LexiRouteRoutingMethod()]) def check_correction( - counts0: Dict[Tuple[int, ...], int], counts1: Dict[Tuple[int, ...], int] + counts0: dict[tuple[int, ...], int], counts1: dict[tuple[int, ...], int] ) -> bool: if ( counts0[(0, 0, 0)] > counts1[(0, 0, 0)] @@ -233,7 +234,7 @@ def test_spam_routing() -> None: (1, 1, 1, 1): 167, } - calib_results: List[Dict[Tuple[int, ...], int]] = [ + calib_results: list[dict[tuple[int, ...], int]] = [ { (0, 0, 0, 0): 659, (0, 0, 0, 1): 65, diff --git a/pytket/tests/passes_script_test.py b/pytket/tests/passes_script_test.py index 5deda534e2..14c803e305 100644 --- a/pytket/tests/passes_script_test.py +++ b/pytket/tests/passes_script_test.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from lark import Lark import pytest +from lark import Lark + from pytket.circuit import OpType -from pytket.passes import compilation_pass_from_script, compilation_pass_grammar from pytket.passes import ( + BasePass, CliffordSimp, ContextSimp, DecomposeBoxes, @@ -26,9 +27,12 @@ OptimisePhaseGadgets, PauliSimp, PauliSquash, + RepeatPass, + SequencePass, SimplifyInitial, + compilation_pass_from_script, + compilation_pass_grammar, ) -from pytket.passes import BasePass, RepeatPass, SequencePass from pytket.transform import CXConfigType, PauliSynthStrat diff --git a/pytket/tests/passes_serialisation_test.py b/pytket/tests/passes_serialisation_test.py index 193ece5786..59db4ade06 100644 --- a/pytket/tests/passes_serialisation_test.py +++ b/pytket/tests/passes_serialisation_test.py @@ -13,42 +13,40 @@ # limitations under the License. import json -import pytest -from referencing import Registry -from referencing.jsonschema import DRAFT7 -from jsonschema import Draft7Validator, ValidationError # type: ignore from pathlib import Path from typing import Any, Dict, List -from sympy import Expr +import pytest +from jsonschema import Draft7Validator, ValidationError # type: ignore +from referencing import Registry +from referencing.jsonschema import DRAFT7 -from pytket.circuit import Node, Circuit, Qubit, OpType -from pytket.predicates import Predicate -from pytket.architecture import Architecture -from pytket.placement import Placement, GraphPlacement import pytket.circuit_library as _library - +from pytket.architecture import Architecture +from pytket.circuit import Circuit, Node, OpType, Qubit +from pytket.circuit.named_types import ParamType +from pytket.mapping import ( + BoxDecompositionRoutingMethod, + LexiLabellingMethod, + LexiRouteRoutingMethod, + MultiGateReorderRoutingMethod, +) from pytket.passes import ( + AASRouting, BasePass, - SequencePass, + CommuteThroughMultis, + CustomPass, + DefaultMappingPass, + FullMappingPass, + RebaseCustom, RemoveRedundancies, RepeatUntilSatisfiedPass, - CommuteThroughMultis, RepeatWithMetricPass, - RebaseCustom, - CXMappingPass, - FullMappingPass, - DefaultMappingPass, - AASRouting, + SequencePass, SquashCustom, ) -from pytket.mapping import ( - LexiLabellingMethod, - LexiRouteRoutingMethod, - MultiGateReorderRoutingMethod, - BoxDecompositionRoutingMethod, -) -from pytket.circuit.named_types import ParamType +from pytket.placement import GraphPlacement, Placement +from pytket.predicates import Predicate def standard_pass_dict(content: Dict[str, Any]) -> Dict[str, Any]: @@ -298,6 +296,9 @@ def nonparam_predicate_dict(name: str) -> Dict[str, Any]: "max_tqe_candidates": 100, "seed": 2, "allow_zzphase": True, + "thread_timeout": 5000, + "only_reduce": False, + "trials": 1, } ), # lists must be sorted by OpType value @@ -589,9 +590,8 @@ def check_arc_dict(arc: Architecture, d: dict) -> bool: if d["links"] != links: return False - else: - nodes = [Node(n[0], n[1]) for n in d["nodes"]] - return set(nodes) == set(arc.nodes) + nodes = [Node(n[0], n[1]) for n in d["nodes"]] + return set(nodes) == set(arc.nodes) def test_pass_deserialisation_only() -> None: @@ -698,7 +698,7 @@ def tk1_rep(a: ParamType, b: ParamType, c: ParamType) -> Circuit: np_pass = dm_pass_0.get_sequence()[2] d_pass = dm_pass.get_sequence()[1] assert d_pass.to_dict()["StandardPass"]["name"] == "DelayMeasures" - assert d_pass.to_dict()["StandardPass"]["allow_partial"] == False + assert d_pass.to_dict()["StandardPass"]["allow_partial"] == False # noqa: E712 assert p_pass.to_dict()["StandardPass"]["name"] == "PlacementPass" assert np_pass.to_dict()["StandardPass"]["name"] == "NaivePlacementPass" assert r_pass.to_dict()["StandardPass"]["name"] == "RoutingPass" @@ -771,3 +771,15 @@ def no_CX(circ: Circuit) -> bool: rps.to_dict()["RepeatUntilSatisfiedPass"]["predicate"]["type"] == "UserDefinedPredicate" ) + + +def test_custom_deserialisation() -> None: + def t(c: Circuit) -> Circuit: + return Circuit(2).CX(0, 1) + + custom_pass_post = BasePass.from_dict( + CustomPass(t, label="test").to_dict(), {"test": t} + ) + c: Circuit = Circuit(3).H(0).H(1).H(2) + custom_pass_post.apply(c) + assert c == Circuit(2).CX(0, 1) diff --git a/pytket/tests/placement_test.py b/pytket/tests/placement_test.py index 346d1861aa..8ce27d75fd 100644 --- a/pytket/tests/placement_test.py +++ b/pytket/tests/placement_test.py @@ -12,25 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json from pathlib import Path + +import pytest + from pytket import Circuit -from pytket.circuit import Node, Qubit from pytket.architecture import Architecture, FullyConnected +from pytket.circuit import Node, Qubit +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager +from pytket.passes import DefaultMappingPass, PauliSimp from pytket.placement import ( - Placement, - LinePlacement, GraphPlacement, + LinePlacement, NoiseAwarePlacement, - place_with_map, + Placement, place_fully_connected, + place_with_map, ) -from pytket.passes import PauliSimp, DefaultMappingPass -from pytket.mapping import MappingManager, LexiRouteRoutingMethod, LexiLabellingMethod from pytket.qasm import circuit_from_qasm -import json -import pytest - def test_placements() -> None: test_coupling = [(0, 1), (1, 2), (1, 3), (4, 1), (4, 5)] @@ -92,13 +93,13 @@ def test_placements() -> None: def test_placements_serialization() -> None: with open( - Path(__file__).resolve().parent / "json_test_files" / "placements.json", "r" + Path(__file__).resolve().parent / "json_test_files" / "placements.json" ) as f: - dict = json.load(f) - base_pl_serial = dict["base_placement"] - line_pl_serial = dict["line_placement"] - graph_pl_serial = dict["graph_placement"] - noise_pl_serial = dict["noise_placement"] + d = json.load(f) + base_pl_serial = d["base_placement"] + line_pl_serial = d["line_placement"] + graph_pl_serial = d["graph_placement"] + noise_pl_serial = d["noise_placement"] assert Placement.from_dict(base_pl_serial).to_dict() == base_pl_serial assert LinePlacement.from_dict(line_pl_serial).to_dict() == line_pl_serial diff --git a/pytket/tests/predicates_test.py b/pytket/tests/predicates_test.py index ea805321a4..1bda5404cc 100644 --- a/pytket/tests/predicates_test.py +++ b/pytket/tests/predicates_test.py @@ -11,99 +11,95 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import sympy +from typing import Any, Dict, List + +import numpy as np import pytest -from pytket import logging +from sympy import Symbol + +from pytket.architecture import Architecture from pytket.circuit import ( + Bit, + CircBox, Circuit, - OpType, + Conditional, + MultiBitOp, + Node, Op, - CircBox, + OpType, PauliExpBox, - Unitary1qBox, - Unitary2qBox, - Node, Qubit, - UnitID, - Conditional, - Bit, RangePredicateOp, SetBitsOp, - MultiBitOp, + Unitary1qBox, + Unitary2qBox, ) from pytket.circuit.named_types import ParamType, RenameUnitsMap -from pytket.pauli import Pauli +from pytket.circuit.named_types import ParamType as Param +from pytket.mapping import ( + LexiLabellingMethod, + LexiRouteRoutingMethod, +) from pytket.passes import ( - SequencePass, - RemoveRedundancies, - SynthesiseTket, - SynthesiseTK, - SynthesiseUMD, - RepeatUntilSatisfiedPass, + AASRouting, + AutoRebase, + CliffordPushThroughMeasures, + CliffordResynthesis, + CliffordSimp, + CnXPairwiseDecomposition, CommuteThroughMultis, - RepeatPass, - DecomposeMultiQubitsCX, + CXMappingPass, DecomposeBoxes, - SquashTK1, - SquashRzPhasedX, - RepeatWithMetricPass, - RebaseCustom, + DecomposeMultiQubitsCX, + DecomposeSwapsToCircuit, + DecomposeSwapsToCXs, + DefaultMappingPass, EulerAngleReduction, - RoutingPass, - CXMappingPass, - PlacementPass, - NaivePlacementPass, - RenameQubitsPass, + FlattenRelabelRegistersPass, FullMappingPass, - DefaultMappingPass, - AASRouting, - DecomposeSwapsToCXs, - DecomposeSwapsToCircuit, + GreedyPauliSimp, + NaivePlacementPass, PauliSimp, - ThreeQubitSquash, + PauliSquash, + PeepholeOptimise2Q, + PlacementPass, + RebaseCustom, RebaseTket, - RemoveDiscarded, - SimplifyMeasured, - SimplifyInitial, RemoveBarriers, - PauliSquash, - AutoRebase, - AutoSquash, - auto_rebase_pass, - auto_squash_pass, - ZZPhaseToRz, - CnXPairwiseDecomposition, + RemoveDiscarded, RemoveImplicitQubitPermutation, - FlattenRelabelRegistersPass, + RemoveRedundancies, + RenameQubitsPass, + RepeatPass, + RepeatUntilSatisfiedPass, + RepeatWithMetricPass, RoundAngles, - PeepholeOptimise2Q, - CliffordResynthesis, - CliffordPushThroughMeasures, - CliffordSimp, + RoutingPass, + SequencePass, + SimplifyInitial, + SimplifyMeasured, + SquashRzPhasedX, + SquashTK1, + SynthesiseTK, + SynthesiseTket, + SynthesiseUMD, + ThreeQubitSquash, ZXGraphlikeOptimisation, - GreedyPauliSimp, + ZZPhaseToRz, + auto_rebase_pass, + auto_squash_pass, ) +from pytket.pauli import Pauli +from pytket.placement import GraphPlacement, Placement from pytket.predicates import ( - GateSetPredicate, - NoClassicalControlPredicate, - DirectednessPredicate, - NoBarriersPredicate, CompilationUnit, + DirectednessPredicate, + GateSetPredicate, MaxNClRegPredicate, + NoBarriersPredicate, + NoClassicalControlPredicate, ) -from pytket.mapping import ( - LexiLabellingMethod, - LexiRouteRoutingMethod, -) -from pytket.architecture import Architecture -from pytket.placement import Placement, GraphPlacement -from pytket.transform import Transform, PauliSynthStrat, CXConfigType -import numpy as np -from sympy import Symbol -from typing import Dict, Any, List - -from pytket.circuit.named_types import ParamType as Param - +from pytket.transform import CXConfigType, PauliSynthStrat, Transform circ2 = Circuit(1) circ2.Rx(0.25, 0) @@ -307,7 +303,7 @@ def test_routing_and_placement_pass() -> None: def test_default_mapping_pass() -> None: circ = Circuit() - q = circ.add_q_register("q", 6) + circ.add_q_register("q", 6) circ.CX(0, 1) circ.H(0) circ.Z(1) @@ -343,7 +339,7 @@ def test_default_mapping_pass() -> None: def test_default_mapping_pass_phase_poly_aas() -> None: circ = Circuit() - q = circ.add_q_register("q", 5) + circ.add_q_register("q", 5) circ.CX(0, 1) circ.H(0) circ.Z(1) @@ -365,7 +361,7 @@ def test_default_mapping_pass_phase_poly_aas() -> None: def test_rename_qubits_pass() -> None: circ = Circuit() - qbs = circ.add_q_register("a", 2) + circ.add_q_register("a", 2) circ.CX(Qubit("a", 0), Qubit("a", 1)) qm = {Qubit("a", 0): Qubit("b", 1), Qubit("a", 1): Qubit("b", 0)} p = RenameQubitsPass(qm) @@ -612,18 +608,18 @@ def test_squash_chains() -> None: def test_apply_pass_with_callbacks() -> None: class CallbackHandler: def __init__(self) -> None: - self.pass_names: List[str] = [] + self.pass_names: list[str] = [] - def before_apply(self, cu: CompilationUnit, config: Dict[str, Any]) -> None: + def before_apply(self, cu: CompilationUnit, config: dict[str, Any]) -> None: if "StandardPass" in config: self.pass_names.append(config["StandardPass"]["name"]) else: self.pass_names.append(config["pass_class"]) - def after_apply(self, cu: CompilationUnit, config: Dict[str, Any]) -> None: + def after_apply(self, cu: CompilationUnit, config: dict[str, Any]) -> None: return - def compile(circ: Circuit, handler: CallbackHandler) -> bool: + def apply_pass(circ: Circuit, handler: CallbackHandler) -> bool: p = SequencePass([CommuteThroughMultis(), RemoveRedundancies()]) return p.apply(circ, handler.before_apply, handler.after_apply) @@ -633,7 +629,7 @@ def compile(circ: Circuit, handler: CallbackHandler) -> bool: circ.CX(0, 1) handler = CallbackHandler() - compile(circ, handler) + apply_pass(circ, handler) assert circ.n_gates_of_type(OpType.CX) == 1 assert len(handler.pass_names) == 3 @@ -846,6 +842,14 @@ def test_conditional_phase() -> None: assert any(any_check_list) +def test_rz_sx_decomp() -> None: + c = Circuit(1).TK1(0, 1.5, 0, 0) + AutoRebase({OpType.CX, OpType.SX, OpType.Rz}).apply(c) + comp = Circuit(1).Rz(1, 0).SX(0).Rz(1, 0) + comp.add_phase(1.75) + assert c == comp + + def test_flatten_relabel_pass() -> None: c = Circuit(3) c.H(1).H(2) @@ -894,7 +898,7 @@ def test_PeepholeOptimise2Q() -> None: perm = c.implicit_qubit_permutation() assert any(k != v for k, v in perm.items()) c = Circuit(2).CX(0, 1).CX(1, 0) - assert PeepholeOptimise2Q(allow_swaps=False).apply(c) == False + assert not PeepholeOptimise2Q(allow_swaps=False).apply(c) perm = c.implicit_qubit_permutation() assert all(k == v for k, v in perm.items()) @@ -1031,8 +1035,8 @@ def test_greedy_pauli_synth() -> None: rega[0], regb[0] ).SWAP(regb[1], rega[0]) d = circ.copy() - pss = GreedyPauliSimp(0.5, 0.5) - assert pss.apply(d) + assert GreedyPauliSimp(0.5, 0.5, thread_timeout=10, trials=5).apply(d) + assert np.allclose(circ.get_unitary(), d.get_unitary()) assert d.name == "test" # test gateset @@ -1054,7 +1058,7 @@ def test_greedy_pauli_synth() -> None: circ.measure_all() circ.Reset(0) circ.add_pauliexpbox(pg1, [2, 3]) - assert GreedyPauliSimp(0.5, 0.5, 100, 100, 0, True).apply(circ) + assert GreedyPauliSimp(0.5, 0.5, 100, 100, 0, True, 100).apply(circ) # PauliExpBoxes implemented using ZZPhase d = Circuit(4, 4, name="test") d.H(0) @@ -1078,15 +1082,25 @@ def test_greedy_pauli_synth() -> None: GreedyPauliSimp().apply(circ) err_msg = "Predicate requirements are not satisfied" assert err_msg in str(e.value) + # large circuit that doesn't complete within thread_timeout argument + c = Circuit(13) + for _ in range(20): + for i in range(13): + for j in range(i + 1, 13): + c.CX(i, j) + c.Rz(0.23, j) + c.H(i) + assert not GreedyPauliSimp(thread_timeout=1).apply(c) + assert GreedyPauliSimp().apply(c) def test_auto_rebase_deprecation(recwarn: Any) -> None: - p = auto_rebase_pass({OpType.TK1, OpType.CX}) + _ = auto_rebase_pass({OpType.TK1, OpType.CX}) assert len(recwarn) == 1 w = recwarn.pop(DeprecationWarning) assert issubclass(w.category, DeprecationWarning) assert "deprecated" in str(w.message) - p = auto_squash_pass({OpType.TK1}) + _ = auto_squash_pass({OpType.TK1}) assert len(recwarn) == 1 w = recwarn.pop(DeprecationWarning) assert issubclass(w.category, DeprecationWarning) @@ -1112,3 +1126,4 @@ def test_auto_rebase_deprecation(recwarn: Any) -> None: test_rebase_custom_tk2() test_selectively_decompose_boxes() test_clifford_push_through_measures() + test_rz_sx_decomp() diff --git a/pytket/tests/pytket_config_test.py b/pytket/tests/pytket_config_test.py index fa307d23b0..3619c39d4e 100644 --- a/pytket/tests/pytket_config_test.py +++ b/pytket/tests/pytket_config_test.py @@ -12,18 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass -from typing import Any, ClassVar, Dict, Optional, Type, Iterator import json +from collections.abc import Iterator +from dataclasses import dataclass from pathlib import Path +from typing import Any, ClassVar, Dict, Optional, Type -from jsonschema import validate # type: ignore import pytest +from jsonschema import validate # type: ignore from pytket.config import ( - get_config_file_path, PytketConfig, PytketExtConfig, + get_config_file_path, load_config_file, write_config_file, ) @@ -33,25 +34,25 @@ class SampleExtConfig(PytketExtConfig): ext_dict_key: ClassVar[str] = "tests_sample" - field1: Optional[str] - field2: Optional[int] + field1: str | None + field2: int | None @classmethod def from_extension_dict( - cls: Type["SampleExtConfig"], ext_dict: Dict[str, Any] + cls: type["SampleExtConfig"], ext_dict: dict[str, Any] ) -> "SampleExtConfig": - return cls(ext_dict.get("field1", None), ext_dict.get("field2", None)) + return cls(ext_dict.get("field1"), ext_dict.get("field2")) def test_pytket_config() -> None: config_file = get_config_file_path() assert config_file.exists() - with open(config_file, "r") as f: + with open(config_file) as f: config_dict = json.load(f) curr_file_path = Path(__file__).resolve().parent - with open(curr_file_path.parent.parent / "schemas/pytket_config_v1.json", "r") as f: + with open(curr_file_path.parent.parent / "schemas/pytket_config_v1.json") as f: schema = json.load(f) validate(instance=config_dict, schema=schema) diff --git a/pytket/tests/qasm_test.py b/pytket/tests/qasm_test.py index 8ed36b7fb4..a3824c03fe 100644 --- a/pytket/tests/qasm_test.py +++ b/pytket/tests/qasm_test.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from io import StringIO import re +from io import StringIO from pathlib import Path from typing import List @@ -21,42 +21,42 @@ from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE from pytket.circuit import ( + Bit, + BitRegister, Circuit, + CustomGate, OpType, - fresh_symbol, Qubit, - Bit, + RangePredicateOp, + fresh_symbol, + if_not_bit, reg_eq, - reg_neq, - reg_lt, + reg_geq, reg_gt, reg_leq, - reg_geq, - if_not_bit, - BitRegister, - CustomGate, - RangePredicateOp, + reg_lt, + reg_neq, ) from pytket.circuit.decompose_classical import DecomposeClassicalError from pytket.circuit.logic_exp import BitWiseOp, create_bit_logic_exp +from pytket.passes import DecomposeBoxes, DecomposeClassicalExp from pytket.qasm import ( circuit_from_qasm, - circuit_to_qasm, circuit_from_qasm_str, - circuit_to_qasm_str, circuit_from_qasm_wasm, + circuit_to_qasm, + circuit_to_qasm_str, ) -from pytket.qasm.qasm import QASMParseError, QASMUnsupportedError from pytket.qasm.includes.load_includes import ( _get_declpath, - _get_files, _get_defpath, - _write_defs, - _write_decls, + _get_files, _load_gdict, + _write_decls, + _write_defs, ) +from pytket.qasm.qasm import QASMParseError, QASMUnsupportedError from pytket.transform import Transform -from pytket.passes import DecomposeClassicalExp, DecomposeBoxes curr_file_path = Path(__file__).resolve().parent @@ -94,6 +94,36 @@ def test_qasm_correct() -> None: assert str(coms3) == correct_str3 +def test_long_registers() -> None: + fname = str(curr_file_path / "qasm_test_files/longreg.qasm") + c = circuit_from_qasm(fname, maxwidth=64) + assert c.n_qubits == 0 + assert c.n_bits == 79 + assert len(c.c_registers) == 5 + for creg in c.c_registers: + assert creg.size <= 64 + + +def test_long_registers_2() -> None: + fname = str(curr_file_path / "qasm_test_files/longreg.qasm") + c = circuit_from_qasm(fname, maxwidth=100) + assert c.n_qubits == 0 + assert c.n_bits == 79 + assert len(c.c_registers) == 4 + for creg in c.c_registers: + assert creg.size <= 100 + + +def test_incompete_registers() -> None: + c = Circuit(1) + c.add_bit(Bit("d", 1)) + c.add_bit(Bit("d", 2)) + c.Measure(Qubit(0), Bit("d", 1)) + with pytest.raises(QASMUnsupportedError) as errorinfo: + circuit_to_qasm_str(c, header="hqslib1") + assert "Circuit contains an invalid classical register d." in str(errorinfo.value) + + def test_qasm_qubit() -> None: with pytest.raises(RuntimeError) as errorinfo: fname = str(curr_file_path / "qasm_test_files/test2.qasm") @@ -147,7 +177,7 @@ def test_qasm_roundtrip() -> None: def test_qasm_str_roundtrip() -> None: - with open(curr_file_path / "qasm_test_files/test1.qasm", "r") as f: + with open(curr_file_path / "qasm_test_files/test1.qasm") as f: c = circuit_from_qasm_str(f.read()) qasm_str = circuit_to_qasm_str(c) c2 = circuit_from_qasm_str(qasm_str) @@ -220,6 +250,9 @@ def test_conditional_gates() -> None: def test_named_conditional_barrier() -> None: circ = Circuit(2, 2) + circ.add_bit(Bit("test", 0)) + circ.add_bit(Bit("test", 1)) + circ.add_bit(Bit("test", 2)) circ.add_bit(Bit("test", 3)) circ.Z(0, condition_bits=[0, 1], condition_value=2) circ.add_conditional_barrier( @@ -414,7 +447,7 @@ def test_h1_rzz() -> None: hqs_qasm_str = circuit_to_qasm_str(c, header="hqslib1") assert "RZZ" in hqs_qasm_str - with open(str(curr_file_path / "qasm_test_files/zzphase.qasm"), "r") as f: + with open(str(curr_file_path / "qasm_test_files/zzphase.qasm")) as f: fread_str = str(f.read()) assert str(hqs_qasm_str) == fread_str @@ -432,14 +465,18 @@ def test_extended_qasm() -> None: assert circuit_to_qasm_str(c2, "hqslib1") - assert not DecomposeClassicalExp().apply(c) + with pytest.raises(DecomposeClassicalError): + DecomposeClassicalExp().apply(c) -def test_decomposable_extended() -> None: +@pytest.mark.parametrize("use_clexpr", [True, False]) +def test_decomposable_extended(use_clexpr: bool) -> None: fname = str(curr_file_path / "qasm_test_files/test18.qasm") out_fname = str(curr_file_path / "qasm_test_files/test18_output.qasm") - c = circuit_from_qasm_wasm(fname, "testfile.wasm", maxwidth=64, use_clexpr=True) + c = circuit_from_qasm_wasm( + fname, "testfile.wasm", maxwidth=64, use_clexpr=use_clexpr + ) DecomposeClassicalExp().apply(c) out_qasm = circuit_to_qasm_str(c, "hqslib1", maxwidth=64) @@ -451,9 +488,9 @@ def test_opaque() -> None: c = circuit_from_qasm_str( 'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[4];\nopaque myopaq() q1, q2;\n myopaq() q[0], q[1];' ) - with pytest.raises(QASMUnsupportedError) as e: - circuit_to_qasm_str(c) - assert "Empty CustomGates and opaque gates are not supported" in str(e.value) + assert ( + circuit_to_qasm_str(c) == 'OPENQASM 2.0;\ninclude "qelib1.inc";\n\nqreg q[4];\n' + ) def test_alternate_encoding() -> None: @@ -462,7 +499,7 @@ def test_alternate_encoding() -> None: "utf-32": str(curr_file_path / "qasm_test_files/utf32.qasm"), } for enc, fil in encoded_files.items(): - with pytest.raises(Exception) as e: + with pytest.raises(Exception): circuit_from_qasm(fil) c = circuit_from_qasm(fil, encoding=enc) assert c.n_gates == 6 @@ -574,7 +611,7 @@ def test_scratch_bits_filtering() -> None: assert _TEMP_BIT_NAME not in qstr qasm_out = str(curr_file_path / "qasm_test_files/testout6.qasm") circuit_to_qasm(c, qasm_out, "hqslib1") - with open(qasm_out, "r") as f: + with open(qasm_out) as f: assert _TEMP_BIT_NAME not in f.read() # test keeping used @@ -586,7 +623,7 @@ def test_scratch_bits_filtering() -> None: qstr = circuit_to_qasm_str(c, "hqslib1") assert _TEMP_BIT_NAME in qstr circuit_to_qasm(c, qasm_out, "hqslib1") - with open(qasm_out, "r") as f: + with open(qasm_out) as f: assert _TEMP_BIT_NAME in f.read() # test multiple scratch registers @@ -602,7 +639,8 @@ def test_scratch_bits_filtering() -> None: creg {_TEMP_BIT_NAME}_1[32]; {_TEMP_BIT_NAME}[0] = (a[0] ^ b[0]); if({_TEMP_BIT_NAME}[0]==1) x q[0]; - """ + """, + maxwidth=64, ) assert c.get_c_register(_TEMP_BIT_NAME) assert c.get_c_register(f"{_TEMP_BIT_NAME}_1") @@ -610,7 +648,7 @@ def test_scratch_bits_filtering() -> None: assert _TEMP_BIT_NAME in qstr assert f"{_TEMP_BIT_NAME}_1" not in qstr circuit_to_qasm(c, qasm_out, "hqslib1") - with open(qasm_out, "r") as f: + with open(qasm_out) as f: fstr = f.read() assert _TEMP_BIT_NAME in fstr assert f"{_TEMP_BIT_NAME}_1" not in fstr @@ -630,7 +668,7 @@ def test_scratch_bits_filtering() -> None: qstr = circuit_to_qasm_str(c, "hqslib1") assert f"creg {_TEMP_BIT_REG_BASE}_0[32]" in qstr circuit_to_qasm(c, qasm_out, "hqslib1") - with open(qasm_out, "r") as f: + with open(qasm_out) as f: fstr = f.read() assert f"creg {_TEMP_BIT_REG_BASE}_0[32]" in fstr @@ -694,7 +732,7 @@ def test_RZZ_read_from() -> None: def test_conditional_expressions() -> None: - def cond_circ(bits: List[int]) -> Circuit: + def cond_circ(bits: list[int]) -> Circuit: c = Circuit(4, 4) c.X(0) c.X(1) @@ -836,14 +874,14 @@ def test_classical_expbox_arg_order(use_clexpr: bool) -> None: qasm = """ OPENQASM 2.0; include "hqslib1.inc"; - + qreg q[1]; - + creg a[4]; creg b[4]; creg c[4]; creg d[4]; - + c = a ^ b | d; """ @@ -865,11 +903,11 @@ def test_register_name_check() -> None: qasm = """ OPENQASM 2.0; include "hqslib1.inc"; - + qreg Q[1]; """ with pytest.raises(QASMParseError) as e: - circ = circuit_from_qasm_str(qasm) + _ = circuit_from_qasm_str(qasm) err_msg = "Invalid register definition 'Q[1]'" assert err_msg in str(e.value) @@ -1026,16 +1064,62 @@ def test_conditional_multi_line_ops() -> None: def test_conditional_range_predicate() -> None: - range_predicate = RangePredicateOp(6, 0, 27) - c = Circuit(0, 8) - c.add_gate(range_predicate, [0, 1, 2, 3, 4, 5, 6], condition=Bit(7)) - # remove once https://github.com/CQCL/tket/issues/1508 - # is resolved + range_predicate = RangePredicateOp(2, 0, 2) + c = Circuit(0, 5) + c.add_gate(range_predicate, [1, 2, 4]) + # https://github.com/CQCL/tket/issues/1642 with pytest.raises(Exception) as errorinfo: - circuit_to_qasm_str(c, header="hqslib1") - assert "Conditional RangePredicate is currently unsupported." in str( + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert "RangePredicate conditions must be an entire classical register" in str( errorinfo.value ) + # https://github.com/CQCL/tket/issues/1508 + range_predicate = RangePredicateOp(6, 0, 27) + c = Circuit(0, 6) + c.add_gate(range_predicate, [0, 1, 2, 3, 4, 5, 5], condition=Bit(5)) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg c[6]; +creg tk_SCRATCH_BITREG_0[4]; +if(c[5]==1) tk_SCRATCH_BITREG_0[0] = 1; +if(c<=27) tk_SCRATCH_BITREG_0[1] = 1; +tk_SCRATCH_BITREG_0[2] = tk_SCRATCH_BITREG_0[0] & tk_SCRATCH_BITREG_0[1]; +tk_SCRATCH_BITREG_0[3] = tk_SCRATCH_BITREG_0[0] & (~ tk_SCRATCH_BITREG_0[1]); +if(tk_SCRATCH_BITREG_0[2]==1) c[5] = 1; +if(tk_SCRATCH_BITREG_0[3]==1) c[5] = 0; +""" + ) + # more test + range_predicate = RangePredicateOp(2, 0, 2) + c = Circuit() + reg_a = c.add_c_register("a", 2) + reg_b = c.add_c_register("b", 2) + reg_d = c.add_c_register("d", 1) + c.add_gate( + range_predicate, reg_a.to_list() + reg_d.to_list(), condition=reg_gt(reg_b, 1) + ) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg a[2]; +creg b[2]; +creg d[1]; +creg tk_SCRATCH_BITREG_0[4]; +if(b>=2) tk_SCRATCH_BITREG_0[0] = 1; +if(a<=2) tk_SCRATCH_BITREG_0[1] = 1; +tk_SCRATCH_BITREG_0[2] = tk_SCRATCH_BITREG_0[0] & tk_SCRATCH_BITREG_0[1]; +tk_SCRATCH_BITREG_0[3] = tk_SCRATCH_BITREG_0[0] & (~ tk_SCRATCH_BITREG_0[1]); +if(tk_SCRATCH_BITREG_0[2]==1) d[0] = 1; +if(tk_SCRATCH_BITREG_0[3]==1) d[0] = 0; +""" + ) def test_range_with_maxwidth() -> None: @@ -1154,7 +1238,8 @@ def test_multibitop() -> None: test_hqs_conditional_params() test_barrier() test_barrier_2() - test_decomposable_extended() + test_decomposable_extended(True) + test_decomposable_extended(False) test_alternate_encoding() test_header_stops_gate_definition() test_tk2_definition() diff --git a/pytket/tests/qasm_test_files/longreg.qasm b/pytket/tests/qasm_test_files/longreg.qasm new file mode 100644 index 0000000000..7fbe3d6dcd --- /dev/null +++ b/pytket/tests/qasm_test_files/longreg.qasm @@ -0,0 +1,83 @@ +OPENQASM 2.0; +include "hqslib1.inc"; + +creg a[1]; +creg b[1]; +creg c[1]; + +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; diff --git a/pytket/tests/qasm_test_files/test18_output.qasm b/pytket/tests/qasm_test_files/test18_output.qasm index c635f280ab..f2ab0b7d65 100644 --- a/pytket/tests/qasm_test_files/test18_output.qasm +++ b/pytket/tests/qasm_test_files/test18_output.qasm @@ -6,16 +6,22 @@ creg a[2]; creg b[3]; creg c[4]; creg d[1]; +creg tk_SCRATCH_BIT[7]; +creg tk_SCRATCH_BITREG_0[64]; c = 2; +tk_SCRATCH_BITREG_0[0] = b[0] & a[0]; +tk_SCRATCH_BITREG_0[1] = b[1] & a[1]; c[0] = a[0]; c[1] = a[1]; -if(b!=2) c[1] = ((b[1] & a[1]) | a[0]); -c = ((b & a) | d); -d[0] = (a[0] ^ 1); -a = CCE(a, b); +if(b!=2) tk_SCRATCH_BIT[6] = b[1] & a[1]; +c[0] = tk_SCRATCH_BITREG_0[0] | d[0]; +if(b!=2) c[1] = tk_SCRATCH_BIT[6] | a[0]; +tk_SCRATCH_BIT[6] = 1; +d[0] = a[0] ^ tk_SCRATCH_BIT[6]; if(c>=2) h q[0]; -CCE(c); +a = CCE(a, b); if(c<=2) h q[0]; +CCE(c); if(c<=1) h q[0]; if(c>=3) h q[0]; if(c!=2) h q[0]; diff --git a/pytket/tests/qubitpaulioperator_test.py b/pytket/tests/qubitpaulioperator_test.py index b27a5cfb87..34a3637404 100644 --- a/pytket/tests/qubitpaulioperator_test.py +++ b/pytket/tests/qubitpaulioperator_test.py @@ -12,20 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -import copy -from hypothesis import given, settings import json import pickle -import pytest import numpy as np -from sympy import Symbol, re, im +import pytest +import strategies as st # type: ignore +from hypothesis import given, settings +from sympy import Symbol, im, re -from pytket.utils import QubitPauliOperator -from pytket.pauli import Pauli, QubitPauliString, pauli_string_mult from pytket.circuit import Qubit - -import strategies as st # type: ignore +from pytket.pauli import Pauli, QubitPauliString +from pytket.utils import QubitPauliOperator def test_QubitPauliOperator_addition() -> None: @@ -150,7 +148,7 @@ def test_QubitPauliOperator_compression() -> None: op = QubitPauliOperator({qpsXY: 2, qpsZI: 1e-11j * x, qpsYY: 1e-11 * x + 1j}) op.compress() with pytest.raises(KeyError) as errorinfo: - term = op[qpsZI] + _ = op[qpsZI] assert "(Zq[0], Iq[1])" in str(errorinfo.value) assert op[qpsXY] == 2 assert re(op[qpsYY]) == 0 diff --git a/pytket/tests/quipper_test.py b/pytket/tests/quipper_test.py index 4247fb660a..f3803f03a5 100644 --- a/pytket/tests/quipper_test.py +++ b/pytket/tests/quipper_test.py @@ -13,14 +13,15 @@ # limitations under the License. from pathlib import Path +from typing import Any +import numpy as np import pytest + from pytket import Circuit from pytket.quipper import circuit_from_quipper from pytket.transform import Transform from pytket.utils.results import compare_unitaries -import numpy as np -from typing import Any curr_file_path = Path(__file__).resolve().parent @@ -40,7 +41,6 @@ def unitary_from_simulate_output(simout: Any, n: int) -> np.ndarray: fmt = "{0:0%db}" % n reps = ["|" + fmt.format(i) + ">" for i in range(N)] lines = simout.split("\n") - n_lines = len(lines) pos = 0 a = np.zeros((N, N), dtype=complex) for i in range(N): @@ -109,6 +109,6 @@ def test_quipper_4() -> None: def test_quipper_5() -> None: # Invalid operation (CCH gate). with pytest.raises(NotImplementedError): - circ = circuit_from_quipper( + _ = circuit_from_quipper( str(curr_file_path / "quipper_test_files" / "test5.quip") ) diff --git a/pytket/tests/simulator/__init__.py b/pytket/tests/simulator/__init__.py index 3067b1fa68..f84e49bb36 100644 --- a/pytket/tests/simulator/__init__.py +++ b/pytket/tests/simulator/__init__.py @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .tket_sim_backend import TketSimShotBackend, TketSimBackend +from .tket_sim_backend import TketSimBackend, TketSimShotBackend from .tket_sim_wrapper import TketSimWrapper diff --git a/pytket/tests/simulator/tket_sim_backend.py b/pytket/tests/simulator/tket_sim_backend.py index 8824a4d5d2..ca1eaecc8d 100644 --- a/pytket/tests/simulator/tket_sim_backend.py +++ b/pytket/tests/simulator/tket_sim_backend.py @@ -17,26 +17,26 @@ ### WARNING: TketSimBackend accepts Measure gates but does not apply them ### TketSimBackend will not work properly if there are extra bits that are unwritten to. -from .tket_sim_wrapper import TketSimWrapper - -from typing import TYPE_CHECKING, List, Optional, Sequence, Union, cast +from collections.abc import Sequence +from typing import List, Optional, Union, cast from uuid import uuid4 import numpy as np -from pytket.circuit import BasisOrder, Circuit, OpType + from pytket.backends.backend import Backend, KwargTypes from pytket.backends.backend_exceptions import CircuitNotRunError from pytket.backends.backendresult import BackendResult from pytket.backends.resulthandle import ResultHandle, _ResultIdTuple from pytket.backends.status import CircuitStatus, StatusEnum +from pytket.circuit import BasisOrder, Circuit, OpType from pytket.passes import ( + AutoRebase, BasePass, - SequencePass, - SynthesiseTket, - FullPeepholeOptimise, DecomposeBoxes, + FullPeepholeOptimise, + SequencePass, SimplifyInitial, - AutoRebase, + SynthesiseTket, ) from pytket.predicates import ( NoClassicalControlPredicate, @@ -47,6 +47,7 @@ from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import probs_from_state +from .tket_sim_wrapper import TketSimWrapper _GATE_SET = { OpType.SWAP, @@ -90,7 +91,7 @@ def _result_id_type(self) -> _ResultIdTuple: return (str,) @property - def required_predicates(self) -> List[Predicate]: + def required_predicates(self) -> list[Predicate]: # We don't include a GateSetPredicate; don't list allowed gates. # It's only for testing, so let it raise an exception naturally # if an unknown gate is passed in. @@ -106,22 +107,21 @@ def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: assert optimisation_level in range(3) if optimisation_level == 0: return SequencePass([DecomposeBoxes(), self.rebase_pass()]) - elif optimisation_level == 1: + if optimisation_level == 1: return SequencePass( [DecomposeBoxes(), SynthesiseTket(), self.rebase_pass()] ) - else: - return SequencePass( - [DecomposeBoxes(), FullPeepholeOptimise(), self.rebase_pass()] - ) + return SequencePass( + [DecomposeBoxes(), FullPeepholeOptimise(), self.rebase_pass()] + ) def process_circuits( self, circuits: Sequence[Circuit], - n_shots: Optional[Union[int, Sequence[int]]] = None, + n_shots: int | Sequence[int] | None = None, valid_check: bool = True, **kwargs: KwargTypes, - ) -> List[ResultHandle]: + ) -> list[ResultHandle]: circuits = list(circuits) if valid_check: self._check_all_circuits(circuits) @@ -157,7 +157,7 @@ def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: assert optimisation_level in range(3) if optimisation_level == 0: return SequencePass([DecomposeBoxes(), self.rebase_pass()]) - elif optimisation_level == 1: + if optimisation_level == 1: return SequencePass( [ DecomposeBoxes(), @@ -166,32 +166,31 @@ def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: SimplifyInitial(allow_classical=False, create_all_qubits=True), ] ) - else: - return SequencePass( - [ - DecomposeBoxes(), - FullPeepholeOptimise(), - self.rebase_pass(), - SimplifyInitial(allow_classical=False, create_all_qubits=True), - ] - ) + return SequencePass( + [ + DecomposeBoxes(), + FullPeepholeOptimise(), + self.rebase_pass(), + SimplifyInitial(allow_classical=False, create_all_qubits=True), + ] + ) def process_circuits( self, circuits: Sequence[Circuit], - n_shots: Optional[Union[int, Sequence[int]]] = None, + n_shots: int | Sequence[int] | None = None, valid_check: bool = True, **kwargs: KwargTypes, - ) -> List[ResultHandle]: + ) -> list[ResultHandle]: circuits = list(circuits) - n_shots_list: List[int] = [] + n_shots_list: list[int] = [] if hasattr(n_shots, "__iter__"): - if any(n is None or n < 1 for n in cast(Sequence[Optional[int]], n_shots)): + if any(n is None or n < 1 for n in cast(Sequence[int | None], n_shots)): raise ValueError( "Shots are artificially generated, specify a positive value " "for all list entries of n_shots." ) - n_shots_list = cast(List[int], n_shots) + n_shots_list = cast(list[int], n_shots) if len(n_shots_list) != len(circuits): raise ValueError("The length of n_shots and circuits must match") else: diff --git a/pytket/tests/simulator/tket_sim_wrapper.py b/pytket/tests/simulator/tket_sim_wrapper.py index 20d9351d88..769d60fedd 100644 --- a/pytket/tests/simulator/tket_sim_wrapper.py +++ b/pytket/tests/simulator/tket_sim_wrapper.py @@ -13,7 +13,8 @@ # limitations under the License. import numpy as np -from pytket.circuit import Circuit, BasisOrder + +from pytket.circuit import BasisOrder, Circuit from pytket.utils.results import ( permute_basis_indexing, permute_qubits_in_statevector, @@ -45,5 +46,4 @@ def get_state(self, basis: BasisOrder = BasisOrder.ilo) -> np.ndarray: # tketsim always uses BasisOrder.ilo, so we must convert. rev_perm = tuple(range(self._n_qubits - 1, -1, -1)) statevector = permute_basis_indexing(statevector, rev_perm) - statevector = permute_qubits_in_statevector(statevector, self._qmap_perm) - return statevector + return permute_qubits_in_statevector(statevector, self._qmap_perm) diff --git a/pytket/tests/strategies.py b/pytket/tests/strategies.py index 8975c896a3..71e2ac2d4b 100644 --- a/pytket/tests/strategies.py +++ b/pytket/tests/strategies.py @@ -12,24 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +import re from collections import Counter -import hypothesis.strategies as st -from hypothesis.strategies._internal import SearchStrategy -from hypothesis.extra.numpy import arrays -from typing import Any, Callable +from collections.abc import Callable +from typing import Any +import hypothesis.strategies as st import numpy as np -import re +from hypothesis.extra.numpy import arrays +from hypothesis.strategies._internal import SearchStrategy -from pytket import Circuit, Qubit, Bit -from pytket.circuit import Node, OpType +from pytket import Bit, Circuit, Qubit from pytket.architecture import Architecture +from pytket.backends.backendinfo import BackendInfo +from pytket.backends.backendresult import BackendResult +from pytket.circuit import Node, OpType from pytket.pauli import Pauli, QubitPauliString from pytket.utils import QubitPauliOperator from pytket.utils.outcomearray import OutcomeArray -from pytket.backends.backendresult import BackendResult -from pytket.backends.backendinfo import BackendInfo - binary_digits = st.sampled_from((0, 1)) uint32 = st.integers(min_value=1, max_value=1 << 32 - 1) @@ -131,10 +131,9 @@ def architecture( draw: Callable[[SearchStrategy[Any]], Any], ) -> Architecture: n_nodes = draw(st.integers(min_value=4, max_value=15)) - n_edges = draw(st.integers(min_value=1, max_value=n_nodes)) vertex = st.integers(min_value=0, max_value=n_nodes - 1) edge = st.lists(vertex, min_size=2, max_size=2, unique=True) - edges = st.lists(edge) + edges = st.lists(edge, min_size=1, max_size=n_nodes) return Architecture(draw(edges)) diff --git a/pytket/tests/tableau_test.py b/pytket/tests/tableau_test.py index 1f734f3c8f..cd39c3b331 100644 --- a/pytket/tests/tableau_test.py +++ b/pytket/tests/tableau_test.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest +import numpy as np + from pytket.circuit import Circuit, OpType, Qubit from pytket.pauli import Pauli, QubitPauliTensor -from pytket.tableau import UnitaryTableau, UnitaryRevTableau, UnitaryTableauBox +from pytket.tableau import UnitaryRevTableau, UnitaryTableau, UnitaryTableauBox from pytket.utils.results import compare_unitaries -import numpy as np def test_tableau_box_from_gates() -> None: diff --git a/pytket/tests/tket_sim_test.py b/pytket/tests/tket_sim_test.py index 193d1edcfc..01beb41d0c 100644 --- a/pytket/tests/tket_sim_test.py +++ b/pytket/tests/tket_sim_test.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest -from pytket.circuit import Circuit, CircBox, QControlBox, Op, OpType -from enum import Enum -import numpy as np import math +from enum import Enum from typing import Any, Tuple +import numpy as np +import pytest + +from pytket.circuit import CircBox, Circuit, Op, OpType, QControlBox + # Note: of course, one could write many more (circuit -> unitary) tests. # But the functions are just wrappers around Simulation functions in tket, # which already has quite extensive C++ unit tests. @@ -61,7 +63,7 @@ def append_gates_sequence_1(circ: Circuit) -> None: circ.Rz(0.15, 0) -def get_circuit_triple() -> Tuple[Circuit, Circuit, Circuit]: +def get_circuit_triple() -> tuple[Circuit, Circuit, Circuit]: """Returns 3 circuits, where the final circuit is the first followed by the second.""" c0 = Circuit(2) @@ -104,11 +106,11 @@ def check_that_premultiplication_fails( to see what happens in normal Python code where we pass in objects which are implicitly converted to NumPy objects (or not).""" with pytest.raises(ValueError) as e1: - product = unitary @ matr + _ = unitary @ matr check_matmul_failure_exception_string(str(e1.value)) with pytest.raises(RuntimeError) as e2: - product = circ.get_unitary_times_other(matr) + _ = circ.get_unitary_times_other(matr) message = str(e2) assert "M has wrong number of" in message diff --git a/pytket/tests/transform_test.py b/pytket/tests/transform_test.py index 4edac7b179..4dfc15116a 100644 --- a/pytket/tests/transform_test.py +++ b/pytket/tests/transform_test.py @@ -13,60 +13,55 @@ # limitations under the License. import itertools -from typing import List +import json from pathlib import Path +from typing import List -import sympy +import numpy as np +import pytest +from sympy import Symbol +import pytket.circuit_library as _library +from pytket.architecture import Architecture from pytket.circuit import ( + CircBox, Circuit, + Node, OpType, - CircBox, - Unitary1qBox, PauliExpBox, - Node, Qubit, + Unitary1qBox, ) -import pytket.circuit_library as _library -from pytket.pauli import Pauli +from pytket.circuit.named_types import ParamType +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager from pytket.passes import ( - RemoveRedundancies, - KAKDecomposition, - SquashCustom, - SquashRzPhasedX, + AutoRebase, + AutoSquash, CommuteThroughMultis, - PauliSquash, - FullPeepholeOptimise, + CustomPass, + CustomRoutingPass, + CXMappingPass, DefaultMappingPass, FullMappingPass, - RoutingPass, - CustomRoutingPass, + FullPeepholeOptimise, + KAKDecomposition, + PauliSquash, PlacementPass, - CXMappingPass, - CustomPass, + RemoveRedundancies, + RoutingPass, SequencePass, SynthesiseTket, - AutoRebase, - AutoSquash, ) -from pytket.predicates import CompilationUnit, NoMidMeasurePredicate -from pytket.transform import Transform, CXConfigType, PauliSynthStrat -from pytket.qasm import circuit_from_qasm -from pytket.architecture import Architecture -from pytket.mapping import MappingManager, LexiRouteRoutingMethod, LexiLabellingMethod +from pytket.pauli import Pauli from pytket.placement import ( - Placement, GraphPlacement, LinePlacement, NoiseAwarePlacement, + Placement, ) - -from sympy import Symbol -import numpy as np -import json -import pytest - -from pytket.circuit.named_types import ParamType +from pytket.predicates import CompilationUnit, NoMidMeasurePredicate +from pytket.qasm import circuit_from_qasm +from pytket.transform import CXConfigType, PauliSynthStrat, Transform def get_test_circuit() -> Circuit: @@ -406,18 +401,14 @@ def test_while_repeat() -> None: c.CX(0, 1) c.Rz(0.34, 0) c.Rx(0.63, 1) - assert ( + assert not ( Transform.while_repeat( Transform.RebaseToCliffordSingles(), Transform.RemoveRedundancies() ).apply(c) - == False - ) - assert ( - Transform.while_repeat( - Transform.CommuteThroughMultis(), Transform.RemoveRedundancies() - ).apply(c) - == True ) + assert Transform.while_repeat( + Transform.CommuteThroughMultis(), Transform.RemoveRedundancies() + ).apply(c) assert c.n_gates_of_type(OpType.CX) == 1 assert c.n_gates_of_type(OpType.Rz) == 0 assert c.n_gates_of_type(OpType.Rx) == 0 @@ -817,7 +808,7 @@ def test_determinism() -> None: def test_full_peephole_optimise() -> None: with open( - Path(__file__).resolve().parent / "json_test_files" / "circuit.json", "r" + Path(__file__).resolve().parent / "json_test_files" / "circuit.json" ) as f: circ = Circuit.from_dict(json.load(f)) @@ -943,8 +934,8 @@ def test_CXMappingPass() -> None: out_circ_1 = cu_1.circuit measure_pred = NoMidMeasurePredicate() - assert measure_pred.verify(cu_0.circuit) == True - assert measure_pred.verify(cu_1.circuit) == False + assert measure_pred.verify(cu_0.circuit) + assert not measure_pred.verify(cu_1.circuit) assert out_circ_0.valid_connectivity(arc, True) assert out_circ_1.valid_connectivity(arc, False) @@ -965,8 +956,8 @@ def test_DefaultMappingPass() -> None: out_circ_0 = cu_0.circuit out_circ_1 = cu_1.circuit measure_pred = NoMidMeasurePredicate() - assert measure_pred.verify(out_circ_0) == True - assert measure_pred.verify(out_circ_1) == False + assert measure_pred.verify(out_circ_0) + assert not measure_pred.verify(out_circ_1) assert out_circ_0.valid_connectivity(arc, False, True) assert out_circ_1.valid_connectivity(arc, False, True) @@ -1161,7 +1152,7 @@ def test_auto_squash() -> None: for gate in itertools.islice(itertools.cycle(gateset), 5): # make a sequence of 5 gates from gateset to make sure squash does # something - params: List[ParamType] = [] + params: list[ParamType] = [] while True: try: circ.add_gate(gate, params, [0]) diff --git a/pytket/tests/unit_id/copy_test.py b/pytket/tests/unit_id/copy_test.py index fa44a96411..df2662235a 100644 --- a/pytket/tests/unit_id/copy_test.py +++ b/pytket/tests/unit_id/copy_test.py @@ -13,7 +13,7 @@ # limitations under the License. from copy import copy, deepcopy -from pytket.unit_id import Bit, Qubit, Node +from pytket.unit_id import Bit, Node, Qubit def test_copying_qubits() -> None: diff --git a/pytket/tests/utils_test.py b/pytket/tests/utils_test.py index 38c3c7bb4c..9eaa183e80 100644 --- a/pytket/tests/utils_test.py +++ b/pytket/tests/utils_test.py @@ -12,44 +12,46 @@ # See the License for the specific language governing permissions and # limitations under the License. +import types from collections import Counter +from collections.abc import Callable +from typing import Any, Dict, List, Tuple + import numpy as np -from pytket.backends.backend import Backend -from hypothesis import strategies, given, settings, HealthCheck +import pytest +from hypothesis import HealthCheck, given, settings, strategies from hypothesis.strategies._internal import SearchStrategy +from simulator import TketSimBackend, TketSimShotBackend # type: ignore +from sympy import symbols -from pytket.circuit import Qubit, Circuit, OpType +from pytket.backends.backend import Backend +from pytket.circuit import Circuit, OpType, Qubit +from pytket.partition import GraphColourMethod, PauliPartitionStrat from pytket.pauli import Pauli, QubitPauliString -from pytket.partition import PauliPartitionStrat, GraphColourMethod from pytket.transform import Transform +from pytket.utils import Graph, QubitPauliOperator from pytket.utils.expectations import ( - expectation_from_shots, expectation_from_counts, + expectation_from_shots, get_operator_expectation_value, get_pauli_expectation_value, ) -from pytket.utils.measurements import append_pauli_measurement, _all_pauli_measurements +from pytket.utils.measurements import _all_pauli_measurements, append_pauli_measurement +from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import ( + compare_statevectors, counts_from_shot_table, get_n_qb_from_statevector, - probs_from_counts, - probs_from_state, int_dist_from_state, permute_basis_indexing, - compare_statevectors, + probs_from_counts, + probs_from_state, ) -from pytket.utils.outcomearray import OutcomeArray -from pytket.utils import QubitPauliOperator, Graph +from pytket.utils.stats import gate_counts from pytket.utils.symbolic import ( circuit_apply_symbolic_statevector, circuit_to_symbolic_unitary, ) -from pytket.utils.stats import gate_counts -import pytest -import types -from sympy import symbols -from typing import Any, Callable, Tuple, Dict, List -from simulator import TketSimShotBackend, TketSimBackend # type: ignore def test_append_measurements() -> None: @@ -97,7 +99,7 @@ def test_shots_to_counts() -> None: def test_counts_to_probs() -> None: - counts: Dict[Tuple[int, ...], int] = {(0, 0): 4, (0, 1): 1, (1, 1): 3} + counts: dict[tuple[int, ...], int] = {(0, 0): 4, (0, 1): 1, (1, 1): 3} probs = probs_from_counts(counts) assert len(probs) == 3 assert probs[(0, 0)] == 0.5 @@ -186,7 +188,7 @@ def test_shot_expectation() -> None: def test_count_expectation() -> None: - counts: Dict[Tuple[int, ...], int] = { + counts: dict[tuple[int, ...], int] = { (0, 0, 1): 4, (0, 1, 0): 7, (1, 0, 0): 1, @@ -248,7 +250,7 @@ def test_outcomearray() -> None: assert counts1D[outcomeA] == 1 # 0 width outcomearrays - readouts: List[List[int]] = [[] for _ in range(10)] + readouts: list[list[int]] = [[] for _ in range(10)] empty_array = OutcomeArray.from_readouts(readouts) assert np.array_equal(empty_array.to_readouts(), readouts) assert empty_array.counts() == Counter( diff --git a/pytket/tests/zx_diagram_test.py b/pytket/tests/zx_diagram_test.py index 839928f6fd..ac8e7aebea 100644 --- a/pytket/tests/zx_diagram_test.py +++ b/pytket/tests/zx_diagram_test.py @@ -12,41 +12,44 @@ # See the License for the specific language governing permissions and # limitations under the License. -from math import pow, isclose +from math import isclose, pow +from typing import Tuple + import numpy as np import pytest -from pytket import Qubit, Circuit, OpType +from sympy import sympify + +from pytket import Circuit, OpType, Qubit from pytket.passes import AutoRebase from pytket.pauli import Pauli, QubitPauliString from pytket.utils.results import compare_unitaries from pytket.zx import ( + CliffordGen, + DirectedGen, + PhasedGen, + QuantumType, + Rewrite, + ZXBox, ZXDiagram, + ZXGen, ZXType, - QuantumType, ZXWireType, - ZXGen, - Rewrite, circuit_to_zx, - PhasedGen, - CliffordGen, - DirectedGen, - ZXBox, ) -from sympy import sympify -from typing import Tuple have_quimb: bool = True try: import quimb.tensor # type: ignore + from pytket.zx.tensor_eval import ( - unitary_from_quantum_diagram, + density_matrix_from_cptp_diagram, fix_boundaries_to_binary_states, fix_inputs_to_binary_state, fix_outputs_to_binary_state, - tensor_from_quantum_diagram, tensor_from_mixed_diagram, + tensor_from_quantum_diagram, unitary_from_classical_diagram, - density_matrix_from_cptp_diagram, + unitary_from_quantum_diagram, ) except ModuleNotFoundError: have_quimb = False @@ -770,7 +773,7 @@ def test_constructors() -> None: phased_gen = PhasedGen(ZXType.ZSpider, 0.5, QuantumType.Quantum) assert phased_gen.param == 0.5 clifford_gen = CliffordGen(ZXType.PX, True, QuantumType.Quantum) - assert clifford_gen.param == True + assert clifford_gen.param directed_gen = DirectedGen(ZXType.Triangle, QuantumType.Quantum) assert directed_gen.signature == [QuantumType.Quantum] * 2 diag = ZXDiagram(4, 4, 0, 0) @@ -780,9 +783,9 @@ def test_constructors() -> None: def joint_normalise_tensor( a: np.ndarray, b: np.ndarray -) -> Tuple[np.ndarray, np.ndarray]: - a_linear = a.reshape((a.size)) - b_linear = b.reshape((b.size)) +) -> tuple[np.ndarray, np.ndarray]: + a_linear = a.reshape(a.size) + b_linear = b.reshape(b.size) max_i = 0 max_val = 0 for i in range(a.size): diff --git a/schemas/circuit_v1.json b/schemas/circuit_v1.json index c801393fbd..d788e573d3 100644 --- a/schemas/circuit_v1.json +++ b/schemas/circuit_v1.json @@ -302,6 +302,9 @@ }, "classical": { "$ref": "#/definitions/classical" + }, + "expr": { + "$ref": "#/definitions/wiredclexpr" } }, "required": [ @@ -357,6 +360,20 @@ "classical" ] } + }, + { + "if": { + "properties": { + "type": { + "const": "ClExpr" + } + } + }, + "then": { + "required": [ + "expr" + ] + } } ] }, @@ -1178,6 +1195,147 @@ "qubits" ], "additionalProperties": false + }, + "wiredclexpr": { + "type": "object", + "description": "A classical operation defined over a sequence of bits.", + "properties": { + "expr": { + "$ref": "#/definitions/clexpr" + }, + "bit_posn": { + "type": "array", + "description": "List of pairs representing map from bit-variable indices to wire positions.", + "items": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "reg_posn": { + "type": "array", + "description": "List of pairs representing map from register-variable indices to lists of wire positions.", + "items": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "type": "integer" + } + } + ] + } + }, + "output_posn": { + "type": "array", + "description": "List of wire positions of output bits.", + "items": { + "type": "integer" + } + } + }, + "required": [ + "expr", + "bit_posn", + "reg_posn", + "output_posn" + ], + "additionalProperties": false + }, + "clexpr": { + "type": "object", + "description": "An abstract classical expression.", + "properties": { + "op": { + "description": "The operation type.", + "type": "string" + }, + "args": { + "type": "array", + "description": "List of arguments to the operation.", + "items": { + "$ref": "#/definitions/clexprarg" + } + } + } + }, + "clexprarg":{ + "type": "object", + "description": "Argument to a classical expression.", + "properties": { + "type": { + "description": "The type of argument.", + "type": "string", + "enum": [ + "term", + "expr" + ] + }, + "input": { + "anyOf": [ + { + "$ref": "#/definitions/clexprterm" + }, + { + "$ref": "#/definitions/clexpr" + } + ] + } + } + }, + "clexprterm": { + "type": "object", + "description": "A term in a classical expression.", + "properties": { + "type": { + "description": "The type of term.", + "type": "string", + "enum": [ + "int", + "var" + ] + }, + "term": { + "anyOf": [ + { + "type": "integer" + }, + { + "$ref": "#/definitions/clvar" + } + ] + } + } + }, + "clvar": { + "type": "object", + "description": "A free variable in a classical expression.", + "properties": { + "type": { + "description": "The type of variable.", + "type": "string", + "enum": [ + "bit", + "ref" + ] + }, + "var": { + "$ref": "#/definitions/clindex" + } + } + }, + "clindex": { + "type": "object", + "properties": { + "index": { + "type": "integer" + } + } } } } diff --git a/schemas/compiler_pass_v1.json b/schemas/compiler_pass_v1.json index b8175c8b92..3a3dd7d85f 100644 --- a/schemas/compiler_pass_v1.json +++ b/schemas/compiler_pass_v1.json @@ -373,6 +373,18 @@ "allow_zzphase": { "type": "boolean", "definition": "parameter controlling the use of ZZPhase gates in \"GreedyPauliSimp\"" + }, + "thread_timeout": { + "type": "number", + "definition": "parameter controlling the maximum runtime of a single thread in \"GreedyPauliSimp\"" + }, + "only_reduce": { + "type": "boolean", + "definition": "parameter controlling whether \"GreedyPauliSimp\" can return circuits with more two qubit gates" + }, + "trials": { + "type": "number", + "definition": "parameter controlling the number of random solutions found when calling \"GreedyPauliSimp\"" } }, "required": [ @@ -644,9 +656,10 @@ }, "then": { "required": [ - "label" + "label", + "relabel_classical_registers" ], - "maxProperties": 2 + "maxProperties": 3 } }, { @@ -903,9 +916,12 @@ "max_lookahead", "max_tqe_candidates", "seed", - "allow_zzphase" + "allow_zzphase", + "thread_timeout", + "only_reduce", + "trials" ], - "maxProperties": 7 + "maxProperties": 10 } }, { diff --git a/tket/conanfile.py b/tket/conanfile.py index 6439e45b7a..fa46ab76f8 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.3.35" + version = "1.3.48" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" @@ -114,7 +114,7 @@ def requirements(self): self.requires("boost/1.86.0", transitive_headers=True) self.requires("eigen/3.4.0", transitive_headers=True) self.requires("nlohmann_json/3.11.3", transitive_headers=True) - self.requires("symengine/0.12.0", transitive_headers=True) + self.requires("symengine/0.13.0", transitive_headers=True) self.requires("tkassert/0.3.4@tket/stable", transitive_headers=True) self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") diff --git a/tket/include/tket/Circuit/Boxes.hpp b/tket/include/tket/Circuit/Boxes.hpp index af2d6a859a..68a4b874b8 100644 --- a/tket/include/tket/Circuit/Boxes.hpp +++ b/tket/include/tket/Circuit/Boxes.hpp @@ -494,6 +494,10 @@ class CustomGate : public Box { bool is_clifford() const override; + Op_ptr dagger() const override; + + Op_ptr transpose() const override; + protected: void generate_circuit() const override; CustomGate() : Box(OpType::CustomGate), gate_(), params_() {} diff --git a/tket/include/tket/Circuit/Circuit.hpp b/tket/include/tket/Circuit/Circuit.hpp index 64a4f338c9..606c2c3b0f 100644 --- a/tket/include/tket/Circuit/Circuit.hpp +++ b/tket/include/tket/Circuit/Circuit.hpp @@ -749,9 +749,12 @@ class Circuit { /** * Convert all quantum and classical bits to use default registers. * + * @param relabel_classical_expression Whether expressions in ClassicalExpBox + * have their expr updated to match the input wires + * * @return mapping from old to new unit IDs */ - unit_map_t flatten_registers(); + unit_map_t flatten_registers(bool relabel_classical_expression = true); //_________________________________________________ @@ -1044,7 +1047,8 @@ class Circuit { * @return true iff circuit was modified */ template - bool rename_units(const std::map &qm); + bool rename_units( + const std::map &qm, bool relabel_classicalexpbox = true); /** Automatically rewire holes when removing vertices from the circuit? */ enum class GraphRewiring { Yes, No }; @@ -1118,7 +1122,8 @@ class Circuit { */ std::map op_counts() const; - unsigned count_gates(const OpType &op_type) const; + unsigned count_gates( + const OpType &op_type, const bool include_conditional = false) const; VertexSet get_gates_of_type(const OpType &op_type) const; /** @@ -1718,7 +1723,8 @@ JSON_DECL(Circuit) /** Templated method definitions */ template -bool Circuit::rename_units(const std::map &qm) { +bool Circuit::rename_units( + const std::map &qm, bool relabel_classicalexpbox) { // Can only work for Unit classes static_assert(std::is_base_of::value); static_assert(std::is_base_of::value); @@ -1767,21 +1773,18 @@ bool Circuit::rename_units(const std::map &qm) { "Unit already exists in circuit: " + pair.first.repr()); TKET_ASSERT(modified); } - // For every ClassicalExpBox, update its logic expressions - if (!bm.empty()) { + if (!bm.empty() && relabel_classicalexpbox) { BGL_FORALL_VERTICES(v, dag, DAG) { Op_ptr op = get_Op_ptr_from_Vertex(v); if (op->get_type() == OpType::ClassicalExpBox) { const ClassicalExpBoxBase &cbox = static_cast(*op); // rename_units is marked as const to get around the Op_ptr - // cast, but it can still mutate a python object modified |= cbox.rename_units(bm); } } } - return modified; } diff --git a/tket/include/tket/Ops/ClExpr.hpp b/tket/include/tket/Ops/ClExpr.hpp index 1adf73d696..712aad2af6 100644 --- a/tket/include/tket/Ops/ClExpr.hpp +++ b/tket/include/tket/Ops/ClExpr.hpp @@ -19,6 +19,7 @@ * @brief Classical expressions involving bits and registers */ +#include #include #include #include @@ -124,7 +125,7 @@ void from_json(const nlohmann::json& j, ClExprVar& var); /** * A term in a classical expression (either a constant or a variable) */ -typedef std::variant ClExprTerm; +typedef std::variant ClExprTerm; std::ostream& operator<<(std::ostream& os, const ClExprTerm& term); diff --git a/tket/include/tket/Predicates/CompilerPass.hpp b/tket/include/tket/Predicates/CompilerPass.hpp index b40e6206e8..f04750f97f 100644 --- a/tket/include/tket/Predicates/CompilerPass.hpp +++ b/tket/include/tket/Predicates/CompilerPass.hpp @@ -33,8 +33,6 @@ typedef std::pair PassConditions; typedef std::function PassCallback; -JSON_DECL(PassPtr) - class IncompatibleCompilerPasses : public std::logic_error { public: explicit IncompatibleCompilerPasses(const std::type_index& typeid1) @@ -301,6 +299,14 @@ class RepeatUntilSatisfiedPass : public BasePass { PredicatePtr pred_; }; +nlohmann::json serialise(const BasePass& bp); +nlohmann::json serialise(const PassPtr& pp); +nlohmann::json serialise(const std::vector& pp); + +PassPtr deserialise( + const nlohmann::json& j, + const std::map>& + custom_deserialise = {}); // TODO: Repeat with a metric, repeat until a Predicate is satisfied... } // namespace tket diff --git a/tket/include/tket/Predicates/PassGenerators.hpp b/tket/include/tket/Predicates/PassGenerators.hpp index 9e7f87931c..65861a4659 100644 --- a/tket/include/tket/Predicates/PassGenerators.hpp +++ b/tket/include/tket/Predicates/PassGenerators.hpp @@ -110,7 +110,8 @@ PassPtr gen_clifford_push_through_pass(); * Qubits removed from the Circuit are preserved in the bimap, but not updated * to a new labelling. */ -PassPtr gen_flatten_relabel_registers_pass(const std::string& label); +PassPtr gen_flatten_relabel_registers_pass( + const std::string& label, bool relabel_classical_expressions = true); /** * Pass to rename some or all qubits according to the given map. * @@ -354,12 +355,17 @@ PassPtr gen_special_UCC_synthesis( * @param max_tqe_candidates * @param seed * @param allow_zzphase + * @param thread_timeout + * @param only_reduce + * @param trials * @return PassPtr */ PassPtr gen_greedy_pauli_simp( double discount_rate = 0.7, double depth_weight = 0.3, unsigned max_lookahead = 500, unsigned max_tqe_candidates = 500, - unsigned seed = 0, bool allow_zzphase = false); + unsigned seed = 0, bool allow_zzphase = false, + unsigned thread_timeout = 100, bool only_reduce = false, + unsigned trials = 1); /** * Generate a pass to simplify the circuit where it acts on known basis states. diff --git a/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp b/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp index de6bb19d5c..8f53b08bde 100644 --- a/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp +++ b/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp @@ -14,6 +14,8 @@ #pragma once +#include + #include "Transform.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Clifford/UnitaryTableau.hpp" @@ -600,6 +602,29 @@ class GPGraph { std::tuple, std::vector> gpg_from_unordered_set(const std::vector& unordered_set); +/** + * @brief Converts the given circuit into a GPGraph and conjugates each node + * by greedily applying 2-qubit Clifford gates until the node can be realised + * as a single-qubit gate, a measurement, or a reset. The final Clifford + * operator is synthesized in a similar fashion. Allows early termination + * from a thread via a stop_flag. + * + * @param circ + * @param stop_flag + * @param discount_rate + * @param depth_weight + * @param max_lookahead + * @param max_tqe_candidates + * @param seed + * @param allow_zzphase + * @return Circuit + */ +Circuit greedy_pauli_graph_synthesis_flag( + Circuit circ, std::atomic& stop_flag, double discount_rate = 0.7, + double depth_weight = 0.3, unsigned max_lookahead = 500, + unsigned max_tqe_candidates = 500, unsigned seed = 0, + bool allow_zzphase = false); + /** * @brief Converts the given circuit into a GPGraph and conjugates each node * by greedily applying 2-qubit Clifford gates until the node can be realised @@ -643,7 +668,8 @@ Circuit greedy_pauli_set_synthesis( Transform greedy_pauli_optimisation( double discount_rate = 0.7, double depth_weight = 0.3, unsigned max_lookahead = 500, unsigned max_tqe_candidates = 500, - unsigned seed = 0, bool allow_zzphase = false); + unsigned seed = 0, bool allow_zzphase = false, + unsigned thread_timeout = 100, unsigned trials = 1); } // namespace Transforms diff --git a/tket/src/Circuit/Boxes.cpp b/tket/src/Circuit/Boxes.cpp index 40d00fb6b8..4679fc79c7 100644 --- a/tket/src/Circuit/Boxes.cpp +++ b/tket/src/Circuit/Boxes.cpp @@ -372,6 +372,22 @@ bool CustomGate::is_clifford() const { return true; } +Op_ptr CustomGate::dagger() const { + Circuit inner_c_dag = gate_->get_def()->dagger(); + composite_def_ptr_t dag_def_ptr = std::make_shared( + gate_->get_name() + "_dagger", gate_->get_def()->dagger(), + gate_->get_args()); + return std::make_shared(dag_def_ptr, params_); +} + +Op_ptr CustomGate::transpose() const { + Circuit inner_c_dag = gate_->get_def()->transpose(); + composite_def_ptr_t dag_def_ptr = std::make_shared( + gate_->get_name() + "_transpose", gate_->get_def()->transpose(), + gate_->get_args()); + return std::make_shared(dag_def_ptr, params_); +} + QControlBox::QControlBox( const Op_ptr &op, unsigned n_controls, const std::vector &control_state) diff --git a/tket/src/Circuit/CircPool.cpp b/tket/src/Circuit/CircPool.cpp index 0a99a34f4e..3a5dc98e0d 100644 --- a/tket/src/Circuit/CircPool.cpp +++ b/tket/src/Circuit/CircPool.cpp @@ -1306,18 +1306,6 @@ static Circuit _tk1_to_rzsx( c.add_op(OpType::SX, {0}); c.add_op(OpType::Rz, alpha, {0}); correction_phase = int_half(beta - 0.5) - 0.25; - } else if (equiv_0(beta + 0.5) && equiv_0(alpha) && equiv_0(gamma)) { - // a = 2k, b = 2m-0.5, c = 2n - // Rz(2k)Rx(2m - 0.5)Rz(2n) = (-1)^{k+m+n}e^{i \pi /4} X.SX - if (allow_x) { - c.add_op(OpType::X, {0}); - } else { - c.add_op(OpType::SX, {0}); - c.add_op(OpType::SX, {0}); - } - c.add_op(OpType::SX, {0}); - correction_phase = - int_half(beta + 0.5) + int_half(alpha) + int_half(gamma) + 0.25; } else if (equiv_0(beta + 0.5)) { // SX.Rz(2m+0.5).SX = (-1)^{m}e^{i \pi /4} Rz(0.5).SX.Rz(0.5) c.add_op(OpType::Rz, gamma + 1, {0}); diff --git a/tket/src/Circuit/Circuit.cpp b/tket/src/Circuit/Circuit.cpp index e85292b2b6..115880b746 100644 --- a/tket/src/Circuit/Circuit.cpp +++ b/tket/src/Circuit/Circuit.cpp @@ -177,7 +177,7 @@ void Circuit::symbol_substitution(const SymEngine::map_basic_basic sub_map) { BGL_FORALL_VERTICES(v, dag, DAG) { Op_ptr new_op = get_Op_ptr_from_Vertex(v)->symbol_substitution(sub_map); if (new_op) { - dag[v] = {new_op}; + dag[v] = {new_op, dag[v].opgroup}; } } phase = phase.subs(sub_map); diff --git a/tket/src/Circuit/basic_circ_manip.cpp b/tket/src/Circuit/basic_circ_manip.cpp index ca3d27faa3..9bb8b11902 100644 --- a/tket/src/Circuit/basic_circ_manip.cpp +++ b/tket/src/Circuit/basic_circ_manip.cpp @@ -422,7 +422,7 @@ void Circuit::remove_edge(const Edge& edge) { boost::remove_edge(edge, this->dag); } -unit_map_t Circuit::flatten_registers() { +unit_map_t Circuit::flatten_registers(bool relabel_classical_expression) { unit_map_t rename_map; unsigned q_index = 0; unsigned c_index = 0; @@ -434,7 +434,7 @@ unit_map_t Circuit::flatten_registers() { } } try { - rename_units(rename_map); + rename_units(rename_map, relabel_classical_expression); } catch (const std::exception& e) { std::stringstream ss; ss << "Unable to flatten registers: " << e.what(); diff --git a/tket/src/Circuit/macro_circ_info.cpp b/tket/src/Circuit/macro_circ_info.cpp index 5f6e2bf9f6..73fc0a3b31 100644 --- a/tket/src/Circuit/macro_circ_info.cpp +++ b/tket/src/Circuit/macro_circ_info.cpp @@ -51,11 +51,19 @@ std::map Circuit::op_counts() const { return counts; } -unsigned Circuit::count_gates(const OpType& op_type) const { +unsigned Circuit::count_gates( + const OpType& op_type, const bool include_conditional) const { unsigned counter = 0; BGL_FORALL_VERTICES(v, dag, DAG) { if (get_OpType_from_Vertex(v) == op_type) { ++counter; + } else if ( + include_conditional && + (get_OpType_from_Vertex(v) == OpType::Conditional) && + (static_cast(*get_Op_ptr_from_Vertex(v)) + .get_op() + ->get_type() == op_type)) { + ++counter; } } return counter; diff --git a/tket/src/OpType/OpTypeFunctions.cpp b/tket/src/OpType/OpTypeFunctions.cpp index 0c2a0c98a6..1b6cdadf44 100644 --- a/tket/src/OpType/OpTypeFunctions.cpp +++ b/tket/src/OpType/OpTypeFunctions.cpp @@ -254,10 +254,9 @@ bool is_oneway_type(OpType optype) { // or we do not yet have the dagger gate as an OpType. // If the gate can have an dagger, define it in the dagger() method. static const OpTypeSet no_defined_inverse = { - OpType::Input, OpType::Output, OpType::Measure, - OpType::ClInput, OpType::ClOutput, OpType::Barrier, - OpType::Reset, OpType::Collapse, OpType::CustomGate, - OpType::PhasePolyBox, OpType::Create, OpType::Discard}; + OpType::Input, OpType::Output, OpType::Measure, OpType::ClInput, + OpType::ClOutput, OpType::Barrier, OpType::Reset, OpType::Collapse, + OpType::PhasePolyBox, OpType::Create, OpType::Discard}; return find_in_set(optype, no_defined_inverse); } diff --git a/tket/src/Ops/ClExpr.cpp b/tket/src/Ops/ClExpr.cpp index 0e0f1fdd5a..880bbd986d 100644 --- a/tket/src/Ops/ClExpr.cpp +++ b/tket/src/Ops/ClExpr.cpp @@ -15,6 +15,7 @@ #include "tket/Ops/ClExpr.hpp" #include +#include #include #include #include @@ -131,7 +132,7 @@ void from_json(const nlohmann::json& j, ClExprVar& var) { } std::ostream& operator<<(std::ostream& os, const ClExprTerm& term) { - if (const int* n = std::get_if(&term)) { + if (const uint64_t* n = std::get_if(&term)) { return os << *n; } else { ClExprVar var = std::get(term); @@ -141,7 +142,7 @@ std::ostream& operator<<(std::ostream& os, const ClExprTerm& term) { void to_json(nlohmann::json& j, const ClExprTerm& term) { nlohmann::json inner_j; - if (const int* n = std::get_if(&term)) { + if (const uint64_t* n = std::get_if(&term)) { j["type"] = "int"; inner_j = *n; } else { @@ -155,7 +156,7 @@ void to_json(nlohmann::json& j, const ClExprTerm& term) { void from_json(const nlohmann::json& j, ClExprTerm& term) { const std::string termtype = j.at("type").get(); if (termtype == "int") { - term = j.at("term").get(); + term = j.at("term").get(); } else { TKET_ASSERT(termtype == "var"); term = j.at("term").get(); diff --git a/tket/src/Predicates/CompilerPass.cpp b/tket/src/Predicates/CompilerPass.cpp index 43691b0faa..e09262fcc7 100644 --- a/tket/src/Predicates/CompilerPass.cpp +++ b/tket/src/Predicates/CompilerPass.cpp @@ -251,7 +251,7 @@ std::string SequencePass::to_string() const { nlohmann::json SequencePass::get_config() const { nlohmann::json j; j["pass_class"] = "SequencePass"; - j["SequencePass"]["sequence"] = seq_; + j["SequencePass"]["sequence"] = serialise(seq_); return j; } @@ -270,7 +270,7 @@ std::string RepeatPass::to_string() const { nlohmann::json RepeatPass::get_config() const { nlohmann::json j; j["pass_class"] = "RepeatPass"; - j["RepeatPass"]["body"] = pass_; + j["RepeatPass"]["body"] = serialise(pass_); return j; } @@ -313,7 +313,7 @@ std::string RepeatWithMetricPass::to_string() const { nlohmann::json RepeatWithMetricPass::get_config() const { nlohmann::json j; j["pass_class"] = "RepeatWithMetricPass"; - j["RepeatWithMetricPass"]["body"] = pass_; + j["RepeatWithMetricPass"]["body"] = serialise(pass_); j["RepeatWithMetricPass"]["metric"] = "SERIALIZATION OF METRICS NOT YET IMPLEMENTED"; return j; @@ -347,15 +347,27 @@ std::string RepeatUntilSatisfiedPass::to_string() const { nlohmann::json RepeatUntilSatisfiedPass::get_config() const { nlohmann::json j; j["pass_class"] = "RepeatUntilSatisfiedPass"; - j["RepeatUntilSatisfiedPass"]["body"] = pass_; + j["RepeatUntilSatisfiedPass"]["body"] = serialise(pass_); j["RepeatUntilSatisfiedPass"]["predicate"] = pred_; return j; } -void to_json(nlohmann::json& j, const PassPtr& pp) { j = pp->get_config(); } +nlohmann::json serialise(const BasePass& bp) { return bp.get_config(); } +nlohmann::json serialise(const PassPtr& pp) { return pp->get_config(); } +nlohmann::json serialise(const std::vector& pp) { + nlohmann::json j = nlohmann::json::array(); + for (const auto& p : pp) { + j.push_back(serialise(p)); + } + return j; +} -void from_json(const nlohmann::json& j, PassPtr& pp) { +PassPtr deserialise( + const nlohmann::json& j, + const std::map>& + custom_deserialise) { std::string classname = j.at("pass_class").get(); + PassPtr pp; if (classname == "StandardPass") { const nlohmann::json& content = j.at("StandardPass"); std::string passname = content.at("name").get(); @@ -457,7 +469,8 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { pp = gen_euler_pass(q, p, s); } else if (passname == "FlattenRelabelRegistersPass") { pp = gen_flatten_relabel_registers_pass( - content.at("label").get()); + content.at("label").get(), + content.at("relabel_classical_expressions").get()); } else if (passname == "RoutingPass") { Architecture arc = content.at("architecture").get(); std::vector con = content.at("routing_config"); @@ -509,9 +522,12 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { unsigned max_lookahead = content.at("max_lookahead").get(); unsigned seed = content.at("seed").get(); bool allow_zzphase = content.at("allow_zzphase").get(); + unsigned timeout = content.at("thread_timeout").get(); + bool only_reduce = content.at("only_reduce").get(); + unsigned trials = content.at("trials").get(); pp = gen_greedy_pauli_simp( discount_rate, depth_weight, max_lookahead, max_tqe_candidates, seed, - allow_zzphase); + allow_zzphase, timeout, only_reduce, trials); } else if (passname == "PauliSimp") { // SEQUENCE PASS - DESERIALIZABLE ONLY @@ -576,6 +592,17 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { unsigned n = content.at("n").get(); bool only_zeros = content.at("only_zeros").get(); pp = RoundAngles(n, only_zeros); + } else if (passname == "CustomPass") { + std::string label = content.at("label").get(); + auto it = custom_deserialise.find(label); + if (it != custom_deserialise.end()) { + pp = CustomPass(it->second, label); + } else { + throw JsonError( + "Cannot deserialise CustomPass without passing a " + "custom_deserialisation map " + "with a key corresponding to the pass's label."); + } } else { throw JsonError("Cannot load StandardPass of unknown type"); } @@ -583,22 +610,24 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { const nlohmann::json& content = j.at("SequencePass"); std::vector seq; for (const auto& j_entry : content.at("sequence")) { - seq.push_back(j_entry.get()); + seq.push_back(deserialise(j_entry, custom_deserialise)); } pp = std::make_shared(seq); } else if (classname == "RepeatPass") { const nlohmann::json& content = j.at("RepeatPass"); - pp = std::make_shared(content.at("body").get()); + pp = std::make_shared( + deserialise(content.at("body"), custom_deserialise)); } else if (classname == "RepeatWithMetricPass") { throw PassNotSerializable(classname); } else if (classname == "RepeatUntilSatisfiedPass") { const nlohmann::json& content = j.at("RepeatUntilSatisfiedPass"); - PassPtr body = content.at("body").get(); + PassPtr body = deserialise(content.at("body"), custom_deserialise); PredicatePtr pred = content.at("predicate").get(); pp = std::make_shared(body, pred); } else { throw JsonError("Cannot load PassPtr of unknown type."); } + return pp; } } // namespace tket diff --git a/tket/src/Predicates/PassGenerators.cpp b/tket/src/Predicates/PassGenerators.cpp index 610caabd84..88766bbb2e 100644 --- a/tket/src/Predicates/PassGenerators.cpp +++ b/tket/src/Predicates/PassGenerators.cpp @@ -244,7 +244,6 @@ PassPtr gen_auto_rebase_pass(const OpTypeSet& allowed_gates, bool allow_swaps) { PredicatePtrMap precons; OpTypeSet all_types(allowed_gates); all_types.insert(OpType::Measure); - all_types.insert(OpType::Collapse); all_types.insert(OpType::Reset); PredicatePtr postcon1 = std::make_shared(all_types); PredicatePtr postcon2 = std::make_shared(); @@ -354,7 +353,8 @@ PassPtr gen_clifford_push_through_pass() { return std::make_shared(precons, t, pc, j); } -PassPtr gen_flatten_relabel_registers_pass(const std::string& label) { +PassPtr gen_flatten_relabel_registers_pass( + const std::string& label, bool relabel_classical_expressions) { Transform t = Transform([=](Circuit& circuit, std::shared_ptr maps) { unsigned n_qubits = circuit.n_qubits(); @@ -366,7 +366,7 @@ PassPtr gen_flatten_relabel_registers_pass(const std::string& label) { relabelling_map.insert({all_qubits[i], Qubit(label, i)}); } - circuit.rename_units(relabelling_map); + circuit.rename_units(relabelling_map, relabel_classical_expressions); changed |= update_maps(maps, relabelling_map, relabelling_map); return changed; }); @@ -376,6 +376,7 @@ PassPtr gen_flatten_relabel_registers_pass(const std::string& label) { nlohmann::json j; j["name"] = "FlattenRelabelRegistersPass"; j["label"] = label; + j["relabel_classical_expressions"] = relabel_classical_expressions; return std::make_shared(precons, t, postcons, j); } @@ -1015,10 +1016,34 @@ PassPtr gen_synthesise_pauli_graph( PassPtr gen_greedy_pauli_simp( double discount_rate, double depth_weight, unsigned max_lookahead, - unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) { - Transform t = Transforms::greedy_pauli_optimisation( - discount_rate, depth_weight, max_lookahead, max_tqe_candidates, seed, - allow_zzphase); + unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase, + unsigned thread_timeout, bool only_reduce, unsigned trials) { + Transform t = Transform([discount_rate, depth_weight, max_lookahead, + max_tqe_candidates, seed, allow_zzphase, + thread_timeout, only_reduce, trials](Circuit& circ) { + Transform gpo = Transforms::greedy_pauli_optimisation( + discount_rate, depth_weight, max_lookahead, max_tqe_candidates, seed, + allow_zzphase, thread_timeout, trials); + if (only_reduce) { + Circuit gpo_circ = circ; + // comparison will be inaccurate if circuit has PauliExpBox + gpo_circ.decompose_boxes_recursively(); + unsigned original_n_2qb_gates = gpo_circ.count_n_qubit_gates(2); + unsigned original_n_gates = gpo_circ.n_gates(); + unsigned original_depth = gpo_circ.depth(); + if (gpo.apply(gpo_circ) && + std::make_tuple( + gpo_circ.count_n_qubit_gates(2), gpo_circ.n_gates(), + gpo_circ.depth()) < + std::make_tuple( + original_n_2qb_gates, original_n_gates, original_depth)) { + circ = gpo_circ; + return true; + } + return false; + } + return gpo.apply(circ); + }); OpTypeSet ins = { OpType::Z, OpType::X, @@ -1067,6 +1092,9 @@ PassPtr gen_greedy_pauli_simp( j["max_tqe_candidates"] = max_tqe_candidates; j["seed"] = seed; j["allow_zzphase"] = allow_zzphase; + j["thread_timeout"] = thread_timeout; + j["only_reduce"] = only_reduce; + j["trials"] = trials; return std::make_shared(precons, t, postcon, j); } diff --git a/tket/src/Transformations/GreedyPauliOptimisation.cpp b/tket/src/Transformations/GreedyPauliOptimisation.cpp index e052102656..3c80ee1eed 100644 --- a/tket/src/Transformations/GreedyPauliOptimisation.cpp +++ b/tket/src/Transformations/GreedyPauliOptimisation.cpp @@ -15,6 +15,9 @@ #include "tket/Transformations/GreedyPauliOptimisation.hpp" #include +#include +#include +#include #include #include "tket/Circuit/PauliExpBoxes.hpp" @@ -325,7 +328,8 @@ struct DepthTracker { static void tableau_row_nodes_synthesis( std::vector& rows, Circuit& circ, DepthTracker& depth_tracker, double depth_weight, unsigned max_lookahead, - unsigned max_tqe_candidates, unsigned seed) { + unsigned max_tqe_candidates, unsigned seed, + std::shared_ptr> stop_flag) { // only consider nodes with a non-zero cost std::vector remaining_indices; for (unsigned i = 0; i < rows.size(); i++) { @@ -334,6 +338,10 @@ static void tableau_row_nodes_synthesis( } } while (remaining_indices.size() != 0) { + // check early termination + if (stop_flag.get()->load()) { + return; + }; // get nodes with min cost std::vector min_nodes_indices = {remaining_indices[0]}; unsigned min_cost = rows[remaining_indices[0]]->tqe_cost(); @@ -605,11 +613,18 @@ static void pauli_exps_synthesis( std::vector& rows, Circuit& circ, DepthTracker& depth_tracker, double discount_rate, double depth_weight, unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, - bool allow_zzphase) { + bool allow_zzphase, std::shared_ptr> stop_flag) { while (true) { + // check timeout + if (stop_flag.get()->load()) { + return; + }; + consume_nodes( rotation_sets, circ, depth_tracker, discount_rate, depth_weight); + if (rotation_sets.empty()) break; + std::vector& first_set = rotation_sets[0]; // get nodes with min cost std::vector min_nodes_indices = {0}; @@ -632,6 +647,7 @@ static void pauli_exps_synthesis( // sample std::vector sampled_tqes = sample_tqes(tqe_candidates, max_tqe_candidates, seed); + // for each tqe we compute costs which might subject to normalisation std::map> tqe_candidates_cost; for (const TQE& tqe : sampled_tqes) { @@ -719,28 +735,29 @@ Circuit greedy_pauli_set_synthesis( std::vector> rotation_sets{rotation_set}; DepthTracker depth_tracker(n_qubits); // synthesise Pauli exps + std::shared_ptr> dummy_stop_flag = + std::make_shared>(false); pauli_exps_synthesis( rotation_sets, rows, c, depth_tracker, 0, depth_weight, max_lookahead, - max_tqe_candidates, seed, allow_zzphase); + max_tqe_candidates, seed, allow_zzphase, dummy_stop_flag); // synthesise the tableau tableau_row_nodes_synthesis( rows, c, depth_tracker, depth_weight, max_lookahead, max_tqe_candidates, - seed); + seed, dummy_stop_flag); c.replace_SWAPs(); return c; } -Circuit greedy_pauli_graph_synthesis( - const Circuit& circ, double discount_rate, double depth_weight, - unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, - bool allow_zzphase) { +Circuit greedy_pauli_graph_synthesis_flag( + Circuit circ, std::shared_ptr> stop_flag, + double discount_rate, double depth_weight, unsigned max_lookahead, + unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) { if (max_lookahead == 0) { throw GreedyPauliSimpError("max_lookahead must be greater than 0."); } if (max_tqe_candidates == 0) { throw GreedyPauliSimpError("max_tqe_candidates must be greater than 0."); } - Circuit circ_flat(circ); unsigned n_qubits = circ_flat.n_qubits(); unsigned n_bits = circ_flat.n_bits(); @@ -750,42 +767,112 @@ Circuit greedy_pauli_graph_synthesis( if (name != std::nullopt) { new_circ.set_name(name.value()); } - unit_map_t unit_map = circ_flat.flatten_registers(); + unit_map_t unit_map = circ_flat.flatten_registers(false); unit_map_t rev_unit_map; for (const auto& pair : unit_map) { rev_unit_map.insert({pair.second, pair.first}); } GPGraph gpg(circ_flat); + + // We regularly check whether the timeout has ocurred + if (stop_flag.get()->load()) { + return Circuit(); + } + auto [rotation_sets, rows, measures] = gpg.get_sequence(); + + if (stop_flag.get()->load()) { + return Circuit(); + } + DepthTracker depth_tracker(n_qubits); // synthesise Pauli exps pauli_exps_synthesis( rotation_sets, rows, new_circ, depth_tracker, discount_rate, depth_weight, - max_lookahead, max_tqe_candidates, seed, allow_zzphase); + max_lookahead, max_tqe_candidates, seed, allow_zzphase, stop_flag); + + if (stop_flag.get()->load()) { + return Circuit(); + } // synthesise the tableau tableau_row_nodes_synthesis( rows, new_circ, depth_tracker, depth_weight, max_lookahead, - max_tqe_candidates, seed); + max_tqe_candidates, seed, stop_flag); + + if (stop_flag.get()->load()) { + return Circuit(); + } + for (auto it = measures.begin(); it != measures.end(); ++it) { new_circ.add_measure(it->left, it->right); } - new_circ.rename_units(rev_unit_map); + new_circ.rename_units(rev_unit_map, false); new_circ.replace_SWAPs(); return new_circ; } +Circuit greedy_pauli_graph_synthesis( + const Circuit& circ, double discount_rate, double depth_weight, + unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, + bool allow_zzphase) { + std::shared_ptr> dummy_stop_flag = + std::make_shared>(false); + return greedy_pauli_graph_synthesis_flag( + circ, dummy_stop_flag, discount_rate, depth_weight, max_lookahead, + max_tqe_candidates, seed, allow_zzphase); +} + } // namespace GreedyPauliSimp Transform greedy_pauli_optimisation( double discount_rate, double depth_weight, unsigned max_lookahead, - unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) { + unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase, + unsigned thread_timeout, unsigned trials) { return Transform([discount_rate, depth_weight, max_lookahead, - max_tqe_candidates, seed, allow_zzphase](Circuit& circ) { - circ = GreedyPauliSimp::greedy_pauli_graph_synthesis( - circ, discount_rate, depth_weight, max_lookahead, max_tqe_candidates, - seed, allow_zzphase); - // decompose the conditional CircBoxes - circ.decompose_boxes_recursively(); + max_tqe_candidates, seed, allow_zzphase, thread_timeout, + trials](Circuit& circ) { + std::mt19937 seed_gen(seed); + std::vector circuits; + unsigned threads_started = 0; + + while (threads_started < trials) { + std::shared_ptr> stop_flag = + std::make_shared>(false); + std::future future = std::async( + std::launch::async, + [&, stop_flag]() { // Capture `stop_flag` explicitly in the lambda + return GreedyPauliSimp::greedy_pauli_graph_synthesis_flag( + circ, stop_flag, discount_rate, depth_weight, max_lookahead, + max_tqe_candidates, seed_gen(), allow_zzphase); + }); + threads_started++; + + if (future.wait_for(std::chrono::seconds(thread_timeout)) == + std::future_status::ready) { + Circuit c = future.get(); + c.decompose_boxes_recursively(); + circuits.push_back(c); + } else { + // If the thread isn't complete within time, prompt cancelling the + // optimisation and break from while loop + *stop_flag = true; + break; + } + } + + // Return the smallest circuit if any were found within the single + // thread_timeout + // If none are found then return false + if (circuits.empty()) return false; + auto min = std::min_element( + circuits.begin(), circuits.end(), + [](const Circuit& a, const Circuit& b) { + return std::make_tuple( + a.count_n_qubit_gates(2), a.n_gates(), a.depth()) < + std::make_tuple( + b.count_n_qubit_gates(2), b.n_gates(), b.depth()); + }); + circ = *min; return true; }); } diff --git a/tket/test/src/Circuit/test_Circ.cpp b/tket/test/src/Circuit/test_Circ.cpp index 0575e382c7..2f47486862 100644 --- a/tket/test/src/Circuit/test_Circ.cpp +++ b/tket/test/src/Circuit/test_Circ.cpp @@ -22,6 +22,7 @@ #include "../testutil.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/DAGDefs.hpp" +#include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/Simulation/CircuitSimulator.hpp" #include "tket/Gate/GatePtr.hpp" #include "tket/Gate/OpPtrFunctions.hpp" @@ -175,6 +176,25 @@ SCENARIO( } } +SCENARIO("test conditional count") { + GIVEN("conditional circ") { + Circuit circ; + register_t qreg = circ.add_q_register("qb", 2); + register_t creg = circ.add_c_register("b", 2); + circ.add_conditional_gate(OpType::H, {}, {qreg[1]}, {creg[0]}, 1); + circ.add_conditional_gate(OpType::H, {}, {qreg[1]}, {creg[0]}, 1); + circ.add_conditional_gate(OpType::H, {}, {qreg[1]}, {creg[0]}, 1); + circ.add_op(OpType::H, {Qubit(qreg[0])}); + circ.add_op(OpType::H, {Qubit(qreg[0])}); + REQUIRE(circ.n_qubits() == 2); + REQUIRE(circ.n_bits() == 2); + REQUIRE(circ.count_gates(OpType::CX, false) == 0); + REQUIRE(circ.count_gates(OpType::H, false) == 2); + REQUIRE(circ.count_gates(OpType::CX, true) == 0); + REQUIRE(circ.count_gates(OpType::H, true) == 5); + } +} + SCENARIO("Creating gates via Qubits and Registers") { GIVEN("A purely quantum circuit") { Circuit circ; @@ -1292,6 +1312,36 @@ SCENARIO("Functions with symbolic ops") { REQUIRE(test_equiv_val(op2->get_params()[0], 0.2)); REQUIRE(op3->get_type() == OpType::Barrier); } + GIVEN("A circuit with symbolic gates and boxes that belong to opgroups.") { + Sym asym = SymEngine::symbol("a"); + Expr alpha(asym); + Sym bsym = SymEngine::symbol("b"); + Expr beta(bsym); + Circuit circ(2); + circ.add_op(OpType::Rx, alpha, {0}, "Rx"); + Circuit inner_circ(2); + inner_circ.add_op(OpType::Rx, {alpha}, {0}); + inner_circ.add_op(OpType::Ry, {beta}, {0}); + auto cbox = CircBox(inner_circ); + circ.add_box(cbox, {0, 1}, "cbox"); + DensePauliMap paulis0{Pauli::X, Pauli::X}; + DensePauliMap paulis1{Pauli::Z, Pauli::X}; + auto ppbox = PauliExpPairBox( + SymPauliTensor(paulis0, alpha), SymPauliTensor(paulis1, beta)); + circ.add_box(ppbox, {0, 1}, "ppbox"); + symbol_map_t symbol_map; + symbol_map[asym] = Expr(0.2); + symbol_map[bsym] = Expr(0.3); + circ.symbol_substitution(symbol_map); + REQUIRE(!circ.is_symbolic()); + std::unordered_set opgroups({"Rx", "cbox", "ppbox"}); + REQUIRE(circ.get_opgroups() == opgroups); + std::vector cmds = circ.get_commands(); + REQUIRE(cmds.size() == 3); + REQUIRE(cmds[0].get_opgroup().value() == "Rx"); + REQUIRE(cmds[1].get_opgroup().value() == "cbox"); + REQUIRE(cmds[2].get_opgroup().value() == "ppbox"); + } } SCENARIO("Test depth_by_type method") { diff --git a/tket/test/src/test_ClExpr.cpp b/tket/test/src/test_ClExpr.cpp index a49888ce5e..248a8cfbbe 100644 --- a/tket/test/src/test_ClExpr.cpp +++ b/tket/test/src/test_ClExpr.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -127,7 +128,7 @@ SCENARIO("Serialization and stringification") { REQUIRE(var_reg1 == var_reg); } GIVEN("ClExprTerm") { - ClExprTerm term_int = 7; + ClExprTerm term_int = uint64_t{7}; ClExprTerm term_var = ClRegVar{5}; std::stringstream ss; ss << term_int << ", " << term_var; @@ -140,14 +141,14 @@ SCENARIO("Serialization and stringification") { REQUIRE(term_var1 == term_var); } GIVEN("Vector of ClExprArg (1)") { - std::vector args{ClRegVar{2}, int{3}}; + std::vector args{ClRegVar{2}, uint64_t{3}}; nlohmann::json j = args; std::vector args1 = j.get>(); REQUIRE(args == args1); } GIVEN("ClExpr (1)") { // r0 + 7 - ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, int{7}}); + ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, uint64_t{7}}); std::stringstream ss; ss << expr; REQUIRE(ss.str() == "add(r0, 7)"); @@ -156,7 +157,7 @@ SCENARIO("Serialization and stringification") { REQUIRE(expr1 == expr); } GIVEN("Vector of ClExprArg (2)") { - ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, int{8}}); + ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, uint64_t{8}}); std::vector args{expr}; nlohmann::json j = args; std::vector args1 = j.get>(); @@ -165,7 +166,7 @@ SCENARIO("Serialization and stringification") { GIVEN("ClExpr (2)") { // (r0 + r1) / (r2 * 3) ClExpr numer(ClOp::RegAdd, {ClRegVar{0}, ClRegVar{1}}); - ClExpr denom(ClOp::RegMul, {ClRegVar{2}, int{3}}); + ClExpr denom(ClOp::RegMul, {ClRegVar{2}, uint64_t{3}}); ClExpr expr(ClOp::RegDiv, {numer, denom}); std::stringstream ss; ss << expr; diff --git a/tket/test/src/test_GreedyPauli.cpp b/tket/test/src/test_GreedyPauli.cpp index 366143ece4..76e0c0cfc6 100644 --- a/tket/test/src/test_GreedyPauli.cpp +++ b/tket/test/src/test_GreedyPauli.cpp @@ -22,6 +22,7 @@ #include "tket/Gate/SymTable.hpp" #include "tket/Ops/ClassicalOps.hpp" #include "tket/Predicates/PassGenerators.hpp" +#include "tket/Predicates/PassLibrary.hpp" #include "tket/Transformations/GreedyPauliOptimisation.hpp" #include "tket/Utils/Expression.hpp" @@ -619,9 +620,38 @@ SCENARIO("Complete synthesis") { PauliExpBox( SymPauliTensor({Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, 0.125)), {1, 3, 5, 0}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + 0.125)), + {1, 3, 5, 0, 2, 4}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + 0.125)), + {0, 1, 2, 3, 4, 5}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + 0.125)), + {5, 2, 4, 1, 3, 0}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + 0.125)), + {0, 5, 1, 4, 3, 2}); + Circuit d(circ); - REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true) - .apply(d)); + REQUIRE( + !Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true, 0) + .apply(d)); + REQUIRE( + Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true, 10) + .apply(d)); REQUIRE(test_unitary_comparison(circ, d, true)); } GIVEN("Select TQE over ZZPhase") { @@ -709,6 +739,7 @@ SCENARIO("Test GreedyPauliSimp for individual gates") { REQUIRE(test_unitary_comparison(circ, d, true)); } } + SCENARIO("Test GreedyPauliSimp pass construction") { // test pass construction GIVEN("A circuit") { @@ -719,6 +750,84 @@ SCENARIO("Test GreedyPauliSimp pass construction") { CHECK(gen_greedy_pauli_simp(0.3, 0.5)->apply(cu)); REQUIRE(test_unitary_comparison(c, cu.get_circ_ref(), true)); } + GIVEN("Preserving a circuit with only_reduce set to true.") { + Circuit c(4); + c.add_op(OpType::CX, {0, 1}); + c.add_op(OpType::CX, {1, 2}); + c.add_op(OpType::CX, {2, 3}); + CompilationUnit cu(c); + REQUIRE(!gen_greedy_pauli_simp(0.3, 0.5, 500, 500, 0, false, 100, true) + ->apply(cu)); + } +} + +SCENARIO("Test GreedyPauliSimp with multiple trials and threads") { + GIVEN("Large circuit with ZZPhase") { + Circuit circ(6); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::X}, 0.3)), {0, 1}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Y, Pauli::X}, 0.2)), + {0, 1, 2}); + circ.add_box( + PauliExpCommutingSetBox({ + {{Pauli::I, Pauli::Y, Pauli::I, Pauli::Z}, 1.2}, + {{Pauli::X, Pauli::Y, Pauli::Z, Pauli::I}, 0.8}, + {{Pauli::I, Pauli::I, Pauli::I, Pauli::Z}, 1.25}, + }), + {1, 2, 3, 4}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Y, Pauli::X}, 0.1)), {2, 3}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Y, Pauli::X}, 0.11)), + {1, 3, 4}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Y, Pauli::Y}, 0.2)), {4, 5}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Z, Pauli::X}, 0.15)), + {2, 4, 5}); + circ.add_box( + PauliExpBox( + SymPauliTensor({Pauli::X, Pauli::X, Pauli::X, Pauli::X}, 0.25)), + {2, 4, 5, 0}); + circ.add_box( + PauliExpBox( + SymPauliTensor({Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, 0.125)), + {1, 3, 5, 0}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + 0.125)), + {1, 3, 5, 0, 2, 4}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + 0.125)), + {0, 1, 2, 3, 4, 5}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + 0.125)), + {5, 2, 4, 1, 3, 0}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + 0.125)), + {0, 5, 1, 4, 3, 2}); + + Circuit d(circ); + REQUIRE(!Transforms::greedy_pauli_optimisation( + 0.7, 0.3, 500, 500, 0, true, 0, 10) + .apply(d)); + REQUIRE(Transforms::greedy_pauli_optimisation( + 0.7, 0.3, 500, 500, 0, true, 10, 10) + .apply(d)); + REQUIRE(test_unitary_comparison(circ, d, true)); + } } } // namespace test_GreedyPauliSimp } // namespace tket diff --git a/tket/test/src/test_json.cpp b/tket/test/src/test_json.cpp index d2843b6a57..e0e2704709 100644 --- a/tket/test/src/test_json.cpp +++ b/tket/test/src/test_json.cpp @@ -901,12 +901,12 @@ SCENARIO("Test compiler pass serializations") { CompilationUnit cu{circ}; \ CompilationUnit copy = cu; \ PassPtr pp = pass; \ - nlohmann::json j_pp = pp; \ - PassPtr loaded = j_pp.get(); \ + nlohmann::json j_pp = serialise(pp); \ + PassPtr loaded = deserialise(j_pp); \ pp->apply(cu); \ loaded->apply(copy); \ REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); \ - nlohmann::json j_loaded = loaded; \ + nlohmann::json j_loaded = serialise(loaded); \ REQUIRE(j_pp == j_loaded); \ } COMPPASSJSONTEST(CommuteThroughMultis, CommuteThroughMultis()) @@ -986,14 +986,14 @@ SCENARIO("Test compiler pass serializations") { CompilationUnit copy = cu; PassPtr pp = gen_pauli_exponentials( Transforms::PauliSynthStrat::Sets, CXConfigType::Tree); - nlohmann::json j_pp = pp; - PassPtr loaded = j_pp.get(); + nlohmann::json j_pp = serialise(pp); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); DecomposeBoxes()->apply(cu); DecomposeBoxes()->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded = loaded; + nlohmann::json j_loaded = serialise(loaded); REQUIRE(j_pp == j_loaded); } GIVEN("RoutingPass") { @@ -1004,12 +1004,12 @@ SCENARIO("Test compiler pass serializations") { placement->apply(cu); CompilationUnit copy = cu; PassPtr pp = gen_routing_pass(arc, rcon); - nlohmann::json j_pp = pp; - PassPtr loaded = j_pp.get(); + nlohmann::json j_pp = serialise(pp); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded = loaded; + nlohmann::json j_loaded = serialise(loaded); REQUIRE(j_pp == j_loaded); } GIVEN("Routing with multiple routing methods") { @@ -1023,12 +1023,12 @@ SCENARIO("Test compiler pass serializations") { placement->apply(cu); CompilationUnit copy = cu; PassPtr pp = gen_routing_pass(arc, mrcon); - nlohmann::json j_pp = pp; - PassPtr loaded = j_pp.get(); + nlohmann::json j_pp = serialise(pp); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded = loaded; + nlohmann::json j_loaded = serialise(loaded); REQUIRE(j_pp == j_loaded); } GIVEN("FullMappingPass") { @@ -1049,7 +1049,7 @@ SCENARIO("Test compiler pass serializations") { } j_pp["StandardPass"]["routing_config"] = config_array; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1065,7 +1065,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["name"] = "DefaultMappingPass"; j_pp["StandardPass"]["architecture"] = arc; j_pp["StandardPass"]["delay_measures"] = true; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1088,7 +1088,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["routing_config"] = config_array; j_pp["StandardPass"]["directed"] = true; j_pp["StandardPass"]["delay_measures"] = false; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1106,7 +1106,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["pauli_synth_strat"] = Transforms::PauliSynthStrat::Sets; j_pp["StandardPass"]["cx_config"] = CXConfigType::Star; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1124,7 +1124,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["pauli_synth_strat"] = Transforms::PauliSynthStrat::Sets; j_pp["StandardPass"]["cx_config"] = CXConfigType::Star; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1143,7 +1143,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["name"] = "ContextSimp"; j_pp["StandardPass"]["allow_classical"] = true; j_pp["StandardPass"]["x_circuit"] = CircPool::X(); - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1158,12 +1158,12 @@ SCENARIO("Test compiler pass combinator serializations") { std::vector seq_vec = { gen_pauli_exponentials(), DecomposeBoxes(), gen_clifford_simp_pass()}; PassPtr seq = std::make_shared(seq_vec); - nlohmann::json j_seq = seq; - PassPtr loaded_seq = j_seq.get(); + nlohmann::json j_seq = serialise(seq); + PassPtr loaded_seq = deserialise(j_seq); seq->apply(cu); loaded_seq->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded_seq = loaded_seq; + nlohmann::json j_loaded_seq = serialise(loaded_seq); REQUIRE(j_seq == j_loaded_seq); } GIVEN("A complex pass with multiple combinators") { @@ -1186,12 +1186,12 @@ SCENARIO("Test compiler pass combinator serializations") { PassPtr rep = std::make_shared(seq, gate_set); PassPtr comb = std::make_shared(std::vector{rep, RebaseTket()}); - nlohmann::json j_comb = comb; - PassPtr loaded_comb = j_comb.get(); + nlohmann::json j_comb = serialise(comb); + PassPtr loaded_comb = deserialise(j_comb); comb->apply(cu); loaded_comb->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded_comb = loaded_comb; + nlohmann::json j_loaded_comb = serialise(loaded_comb); REQUIRE(j_comb == j_loaded_comb); } }