diff --git a/.github/problem-matchers/black.json b/.github/problem-matchers/black.json deleted file mode 100644 index 639f1b40b..000000000 --- a/.github/problem-matchers/black.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "problemMatcher": [ - { - "owner": "black", - "severity": "warning", - "pattern": [ - { - "regexp": "^(would reformat\\s(.*))$", - "message": 1, - "file": 2 - } - ] - } - ] -} diff --git a/.github/problem-matchers/ruff.json b/.github/problem-matchers/ruff.json new file mode 100644 index 000000000..e36efc324 --- /dev/null +++ b/.github/problem-matchers/ruff.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "ruff", + "pattern": [ + { + "regexp": "^(.+?):(\\d+):(\\d+): (?:([A-Z0-9]+) )?(.+)$", + "file": 1, + "line": 2, + "column": 3, + "code": 4, + "message": 5 + } + ] + } + ] +} \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 53b1894f8..f8bccc6d9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -84,7 +84,7 @@ jobs: - name: Check format continue-on-error: ${{inputs.soft-linting == 'true'}} run: | - echo '::add-matcher::.github/problem-matchers/black.json' + echo '::add-matcher::.github/problem-matchers/ruff.json' check/format-incremental - name: Check lint diff --git a/check/format-incremental b/check/format-incremental index d927a255e..4f0d825bd 100755 --- a/check/format-incremental +++ b/check/format-incremental @@ -112,48 +112,49 @@ if (( ${#format_files[@]} == 0 )); then exit 0 fi -# Apply isort only on Python files with the exception of __init__.py files. -declare -a isort_files=() -for f in "${format_files[@]}"; do - if [[ "${f}" == *.py && "${f##*/}" != __init__.py ]]; then - isort_files+=("${f}") - fi -done - # Color the output if it goes to a terminal or GitHub Actions log. arg_color=() if [[ -t 1 || "${CI}" == true ]]; then - arg_color=("--color") + # Check if ruff supports --color (it might be missing in older/some versions). + if ruff --help 2>&1 | grep -q -- '--color'; then + arg_color=("--color" "always") + fi fi -ISORTVERSION=$(isort --version-number) - -echo "Sorting imports with isort... (version: $ISORTVERSION)" - -args=("${arg_color[@]}") -if (( only_print == 1 )); then - args+=("--check" "--diff") +# Run ruff for import sorting. +# We do this first so that if it changes files, the formatting step will pick them up. +echo "Running ruff check (isort)..." +check_args=("check" "--select" "I" "${arg_color[@]}") +if (( only_print == 0 )); then + check_args+=("--fix") +else + # In check mode, we want to see diffs if possible, but ruff check usually just prints violations. + # To match previous behavior of "checking" without modifying, we don't add --fix. + # If we want diffs for imports, --diff can be used. + check_args+=("--diff") fi -ISORTSTATUS=0 -if (( "${#isort_files[@]}" )); then - isort "${args[@]}" "${isort_files[@]}" - ISORTSTATUS=$? +RUFF_CHECK_STATUS=0 +if (( "${#format_files[@]}" )); then + # We pass all files to ruff; it handles excluding those that shouldn't be touched if configured, + # but here we are passing a specific list of files. + ruff "${check_args[@]}" "${format_files[@]}" + RUFF_CHECK_STATUS=$? fi -BLACKVERSION=$(black --version | head -1) - -echo "Running the black formatter... (version: $BLACKVERSION)" - -args=("${arg_color[@]}") +echo "Running ruff format..." +format_args=("format" "${arg_color[@]}") if (( only_print == 1 )); then - args+=("--check" "--diff") + format_args+=("--check" "--diff") fi -black "${args[@]}" "${format_files[@]}" -BLACKSTATUS=$? +RUFF_FORMAT_STATUS=0 +if (( "${#format_files[@]}" )); then + ruff "${format_args[@]}" "${format_files[@]}" + RUFF_FORMAT_STATUS=$? +fi -if (( BLACKSTATUS || ISORTSTATUS )); then +if (( RUFF_CHECK_STATUS || RUFF_FORMAT_STATUS )); then exit 1 fi exit 0 diff --git a/docs/_scripts/build_api_docs.py b/docs/_scripts/build_api_docs.py index f0b2bce72..53027acc6 100644 --- a/docs/_scripts/build_api_docs.py +++ b/docs/_scripts/build_api_docs.py @@ -13,6 +13,7 @@ # limitations under the License. # ============================================================================== """Tool to generate external api_docs for qsim shameless copy from TFQ.""" + import os from absl import app, flags @@ -24,7 +25,7 @@ flags.DEFINE_string( "code_url_prefix", - ("https://github.com/quantumlib/qsim/tree/main/" "qsimcirq"), + ("https://github.com/quantumlib/qsim/tree/main/qsimcirq"), "The url prefix for links to code.", ) @@ -40,7 +41,6 @@ def main(unused_argv): - doc_generator = generate_lib.DocGenerator( root_title="qsim", py_modules=[("qsimcirq", qs)], diff --git a/docs/tutorials/noisy_qsimcirq.ipynb b/docs/tutorials/noisy_qsimcirq.ipynb index c1466facf..ae98d09a0 100644 --- a/docs/tutorials/noisy_qsimcirq.ipynb +++ b/docs/tutorials/noisy_qsimcirq.ipynb @@ -1,30 +1,4 @@ { - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.2" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "python392jvsc74a57bd0916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1", - "display_name": "Python 3.9.2 64-bit" - }, - "metadata": { - "interpreter": { - "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2, "cells": [ { "cell_type": "code", @@ -32,7 +6,7 @@ "metadata": {}, "outputs": [], "source": [ - "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# @title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", @@ -46,42 +20,42 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "# Noise simulation in qsimcirq" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "\n", " \n", " \n", " \n", " \n", "
\n", " View on QuantumAI\n", " \n", - " Run in Google Colab\n", + " Run in Google Colab\n", " \n", - " View source on GitHub\n", + " View source on GitHub\n", " \n", " Download notebook\n", "
" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "Noisy gates in Cirq are represented by `Channel`s, which can act as one of a set of gates depending on the state of the circuit. The [Cirq tutorial on noise](https://quantumai.google/cirq/noise) explains how to construct these objects and add them to your circuits.\n", "\n", "## Setup\n", "\n", "Install the Cirq and qsimcirq packages:" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -103,6 +77,8 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "It is possible to simulate channels with density matrices, which combine all possible channel behaviors, but the overhead is steep: a density matrix requires O(4^N) storage for N qubits.\n", "\n", @@ -113,18 +89,16 @@ "Noisy circuits tend to be more expensive to simulate than their noiseless equivalents, but qsim is optimized to avoid these overheads when possible. In particular, the less incoherent noise (i.e. non-unitary effects) that a `Channel` has, the closer its performance will be to the noiseless case for a single repetition.\n", "\n", "Simulating many repetitions of a noisy circuit requires executing the entire circuit once for each repetition due to the nondeterministic nature of noisy operations." - ], - "cell_type": "markdown", - "metadata": {} + ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "## Constructing noisy circuits\n", "\n", "Cirq provides a number of tools for constructing noisy circuits. For the purpose of this tutorial, we will focus on two common types of noise: T1 (\"amplitude damping\") and T2 (\"phase damping\"). These can be created in Cirq with `cirq.amplitude_damp` and `cirq.phase_damp`, as shown below:" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -136,7 +110,8 @@ "\n", "circuit = cirq.Circuit(\n", " # Perform a Hadamard on both qubits\n", - " cirq.H(q0), cirq.H(q1),\n", + " cirq.H(q0),\n", + " cirq.H(q1),\n", " # Apply amplitude damping to q0 with probability 0.1\n", " cirq.amplitude_damp(gamma=0.1).on(q0),\n", " # Apply phase damping to q1 with probability 0.1\n", @@ -145,13 +120,13 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "## Simulating noisy circuits\n", "\n", "Simulating this circuit works exactly the same as simulating a noiseless circuit: simply construct a simulator object and simulate. `QSimSimulator` will automatically switch over to the noisy simulator if it detects noise (i.e. `Channel`s) in your circuit." - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -159,8 +134,8 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "[0.52631575+0.j 0.49930704+0.j 0.49930704+0.j 0.47368425+0.j]\n" ] @@ -173,49 +148,49 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "It's important to note that unlike density-matrix simulations, this result (from a single repetition) is stochastic in nature. Running the circuit multiple times may yield different results, but each result generated is a possible outcome of the provided circuit." - ], - "cell_type": "markdown", - "metadata": {} + ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "## Other simulation modes\n", "\n", "Noisy circuit simulation in qsimcirq supports all of the same simulation modes as the noiseless simulator, including:\n", "\n", "### Measurement Sampling" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { - "source": [ - "# Simulate measuring at the end of the circuit.\n", - "measured_circuit = circuit + cirq.measure(q0, q1, key='m')\n", - "measure_results = qsim_simulator.run(measured_circuit, repetitions=5)\n", - "print(measure_results)" - ], "cell_type": "code", - "metadata": {}, "execution_count": 5, + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "m=01101, 00100\n" ] } + ], + "source": [ + "# Simulate measuring at the end of the circuit.\n", + "measured_circuit = circuit + cirq.measure(q0, q1, key=\"m\")\n", + "measure_results = qsim_simulator.run(measured_circuit, repetitions=5)\n", + "print(measure_results)" ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "### Amplitude evaluation" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -223,8 +198,8 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "[(0.5263157486915588+0j), (0.4993070363998413+0j)]\n" ] @@ -232,19 +207,18 @@ ], "source": [ "# Calculate only the amplitudes of the |00) and |01) states.\n", - "amp_results = qsim_simulator.compute_amplitudes(\n", - " circuit, bitstrings=[0b00, 0b01])\n", + "amp_results = qsim_simulator.compute_amplitudes(circuit, bitstrings=[0b00, 0b01])\n", "print(amp_results)" ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "### Expectation values\n", "\n", "Expectation values can only be estimated from trajectories, but the accuracy of these estimates can be increased by simulating the circuit additional times. This is demonstrated below." - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -252,8 +226,8 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "[(0.13789467364549637+0j), (0.9386972016096116+0j)]\n" ] @@ -262,7 +236,7 @@ "source": [ "# Set the \"noisy repetitions\" to 100.\n", "# This parameter only affects expectation value calculations.\n", - "options = {'r': 100}\n", + "options = {\"r\": 100}\n", "# Also set the random seed to get reproducible results.\n", "ev_simulator = qsimcirq.QSimSimulator(qsim_options=options, seed=1)\n", "# Define observables to measure: for q0 and for q1.\n", @@ -277,11 +251,37 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "The output is a list of expectation values, one for each observable." - ], - "cell_type": "markdown", - "metadata": {} + ] } - ] + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.9.2 64-bit", + "name": "python392jvsc74a57bd0916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + }, + "metadata": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + }, + "orig_nbformat": 2 + }, + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/pyproject.toml b/pyproject.toml index c1f85c3ed..f8067be1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,8 +103,7 @@ dev = [ "cibuildwheel", # Linters, formatters, and test utilities. - "black~=25.9.0", - "isort[colors]~=6.0.1", + "ruff~=0.9.4", "py-cpuinfo", "pylint~=4.0.2", "pytest", @@ -152,14 +151,18 @@ manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" skip = "*musllinux*" -[tool.black] -target-version = ['py310', 'py311', 'py312', 'py313'] -extend-exclude = 'third_party' - -[tool.isort] -profile = 'black' -order_by_type = false # Sort alphabetically, irrespective of case. -skip_gitignore = true -combine_as_imports = true -known_first_party = ["qsimcirq*"] -extend_skip = ["__init__.py"] +[tool.ruff] +target-version = "py310" +extend-exclude = ["third_party"] + +[tool.ruff.lint] +select = [ + "I", # isort +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["I"] + +[tool.ruff.lint.isort] +combine-as-imports = true +known-first-party = ["qsimcirq", "qsimcirq_tests"] diff --git a/qsimcirq/qsimh_simulator.py b/qsimcirq/qsimh_simulator.py index 8d3f1202c..9216a49aa 100644 --- a/qsimcirq/qsimh_simulator.py +++ b/qsimcirq/qsimh_simulator.py @@ -49,7 +49,6 @@ def compute_amplitudes_sweep( params: cirq.Sweepable, qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT, ) -> Sequence[Sequence[complex]]: - if not isinstance(program, qsimc.QSimCircuit): program = qsimc.QSimCircuit(program) @@ -65,7 +64,6 @@ def compute_amplitudes_sweep( trials_results = [] for prs in param_resolvers: - solved_circuit = cirq.resolve_parameters(program, prs) options["c"], _ = solved_circuit.translate_cirq_to_qsim(qubit_order) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 0b5cdbc8e..d6a6d53b0 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1036,7 +1036,7 @@ def _num_qubits_(self): return self._num_qubits def _kraus_(self): - return [cirq.unitary(op) for _, op, in self._prob_op_pairs] + return [cirq.unitary(op) for _, op in self._prob_op_pairs] def steps(self): return [m for _, m in self._prob_op_pairs] @@ -1064,7 +1064,7 @@ def _mixture_(self): # unitary() on those NoiseStep objects, the values unitary() returns # will not actually be unitary. This is done knowingly. The nonunitary # values are eventually normalized in test_multi_qubit_noise(). - return [(prob, op) for prob, op, in self._prob_op_pairs] + return [(prob, op) for prob, op in self._prob_op_pairs] @pytest.mark.parametrize( @@ -2144,7 +2144,9 @@ def test_qsimcirq_identity_expectation_value(): ( cirq.I(cirq.LineQubit(i)) if p == "I" - else cirq.Z(cirq.LineQubit(i)) if p == "Z" else None + else cirq.Z(cirq.LineQubit(i)) + if p == "Z" + else None ) for i, p in enumerate(pauli) ) diff --git a/setup.py b/setup.py index b9a9b7b36..81e66af63 100644 --- a/setup.py +++ b/setup.py @@ -55,8 +55,7 @@ def run(self): ) except subprocess.CalledProcessError as e: raise RuntimeError( - f"Command '{e.cmd}' returned status {e.returncode}. " - f"Output: {e.output}" + f"Command '{e.cmd}' returned status {e.returncode}. Output: {e.output}" ) except subprocess.TimeoutExpired as e: raise RuntimeError(f"Command timed out: {e}")