diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb242ba8..da9c3545 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: run: | python -m pip install -U pip setuptools wheel pybind11 cat requirements.txt - python -m pip install -r requirements.txt --prefer-binary + python -m pip install -r requirements.txt --prefer-binary --no-build-isolation python -m pip install coveralls - name: Setup annotations on Linux @@ -254,11 +254,12 @@ jobs: /var/cache/dnf/ key: ${{ runner.os }}-centos${{ matrix.centos }}-yum-${{ secrets.yum_cache }} - - name: Fix repository URLs (CentOS 8 only) - if: matrix.centos == 8 + - name: Fix repository URLs run: | sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* + sed -i 's|mirror.centos.org|vault.centos.org|g' /etc/yum.repos.d/CentOS-* + sed -i 's|^#baseurl|baseurl|g' /etc/yum.repos.d/CentOS-* - name: Update YUM/DNF run: yum update -y @@ -267,6 +268,9 @@ jobs: if: matrix.centos == 7 run: | yum install -y centos-release-scl-rh + sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* + sed -i 's|mirror.centos.org|vault.centos.org|g' /etc/yum.repos.d/CentOS-* + sed -i 's|^#baseurl|baseurl|g' /etc/yum.repos.d/CentOS-* yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm echo '/opt/rh/rh-python38/root/usr/bin' >> $GITHUB_PATH diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 18a5a10c..5f462e8a 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -30,9 +30,10 @@ jobs: - uses: actions/checkout@v3 - name: Prepare env - run: > - apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel - --no-install-recommends + run: | + sed -i 's|deb.debian.org/debian-security|archive.debian.org/debian-security|g' /etc/apt/sources.list + apt-get update -o Acquire::Retries=3 + apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel --no-install-recommends - name: Upgrade pybind11 and setuptools run: python3 -m pip install --upgrade pybind11 "setuptools>=61.0.0" --prefer-binary diff --git a/CHANGELOG.md b/CHANGELOG.md index e58e1202..c80cf3ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed some typos (thanks to @eltociear, @Darkdragon84) - Fixed support for Python 3.12 +- Fixed `ComparatorQuantum` decomposition so input registers are properly restored (#491) ### Removed @@ -23,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - Update GitHub workflows to work with merge queues +- Fix Clang-Tidy Debian security package source URL in GitHub workflow +- Fix setuptools >= 70 compatibility in compiler configuration and build commands - Update GitHub workflow action versions: `actions/cache@v3`, `actions/checkout@v3`, `actions/setup-python@v4`, `thomaseizinger/create-pull-request` - Update pre-commit hook versions - Fix failing GitHub workflows (Clang-10) diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py index 59aa7cf9..8a984951 100644 --- a/projectq/libs/math/_quantummath.py +++ b/projectq/libs/math/_quantummath.py @@ -218,11 +218,11 @@ def comparator(eng, quint_a, quint_b, comp): with Control(eng, [quint_a[n_qubits - 2], quint_b[n_qubits - 2]]): X | comp - for k in range(0, n_qubits - 2): + for k in range(n_qubits - 3, -1, -1): with Control(eng, [quint_a[k], quint_b[k]]): X | (quint_a[k + 1]) - for j in range(n_qubits - 3, 0, -1): + for j in range(1, n_qubits - 2): CNOT | (quint_a[j], quint_a[j + 1]) for i in range(1, n_qubits - 1): diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py index d32715ca..810b54a3 100644 --- a/projectq/libs/math/_quantummath_test.py +++ b/projectq/libs/math/_quantummath_test.py @@ -17,7 +17,12 @@ import projectq.libs.math from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import AutoReplacer, DecompositionRuleSet, InstructionFilter +from projectq.cengines import ( + AutoReplacer, + BasicEngine, + DecompositionRuleSet, + InstructionFilter, +) from projectq.libs.math import ( AddQuantum, ComparatorQuantum, @@ -249,6 +254,8 @@ def test_comparator(eng): ComparatorQuantum | (qureg_a, qureg_b, compare_qubit) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0], qureg_b)) assert 1.0 == pytest.approx(eng.backend.get_probability([1], compare_qubit)) # Size mismatch in qubit registers @@ -264,6 +271,85 @@ def test_comparator(eng): Measure | compare_qubit +class _BasisBackend(BasicEngine): + """Exact basis-state backend accepting only multi-controlled X and lifecycle gates.""" + + def __init__(self): + super().__init__() + self.state = {} + + def is_available(self, cmd): + return cmd.gate.__class__.__name__ in ( + 'XGate', + 'AllocateQubitGate', + 'DeallocateQubitGate', + 'FlushGate', + 'MeasureGate', + ) + + def receive(self, commands): + for cmd in commands: + name = cmd.gate.__class__.__name__ + if name == 'AllocateQubitGate': + self.state[cmd.qubits[0][0].id] = 0 + elif name == 'XGate' and all(self.state[c.id] for c in cmd.control_qubits): + self.state[cmd.qubits[0][0].id] ^= 1 + + +def _run_comparator_decomposition(a_val, b_val, c_val, n_bits=3): + backend = _BasisBackend() + rules = DecompositionRuleSet(modules=[projectq.libs.math]) + engine = MainEngine(backend=backend, engine_list=[AutoReplacer(rules)]) + reg_a = engine.allocate_qureg(n_bits) + reg_b = engine.allocate_qureg(n_bits) + reg_c = engine.allocate_qureg(1) + for i, qubit in enumerate(reg_a): + if (a_val >> i) & 1: + X | qubit + for i, qubit in enumerate(reg_b): + if (b_val >> i) & 1: + X | qubit + if c_val: + X | reg_c[0] + ComparatorQuantum | (reg_a, reg_b, reg_c) + All(Measure) | (reg_a + reg_b + reg_c) + engine.flush() + res_a = sum(backend.state[q.id] << i for i, q in enumerate(reg_a)) + res_b = sum(backend.state[q.id] << i for i, q in enumerate(reg_b)) + res_c = backend.state[reg_c[0].id] + return res_a, res_b, res_c + + +@pytest.mark.parametrize( + 'a, b, c_in, expected_c', + [ + (1, 0, 0, 1), + (0, 1, 0, 0), + (2, 2, 0, 0), + (1, 0, 1, 0), + (0, 1, 1, 1), + (7, 0, 0, 1), + (0, 7, 0, 0), + (7, 7, 1, 1), + ], +) +def test_comparator_decomposition_restore_registers(a, b, c_in, expected_c): + res_a, res_b, res_c = _run_comparator_decomposition(a, b, c_in, n_bits=3) + assert res_a == a + assert res_b == b + assert res_c == expected_c + + +def test_comparator_decomposition_all_3bit_cases(): + for a in range(8): + for b in range(8): + for c_in in (0, 1): + res_a, res_b, res_c = _run_comparator_decomposition(a, b, c_in, n_bits=3) + assert res_a == a + assert res_b == b + assert res_c == c_in ^ int(a > b) + + @pytest.mark.parametrize('qubit_idx', (0, 1, 2)) def test_quantumdivision_size_mismatch(eng, qubit_idx): qureg_a = eng.allocate_qureg(4 if qubit_idx != 0 else 3) diff --git a/pyproject.toml b/pyproject.toml index 6e4686e2..19d426db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dynamic = ["version"] dependencies = [ 'matplotlib >= 2.2.3', 'networkx >= 2.4', - 'numpy>=1.21.5', + 'numpy>=1.21.5, <2', 'requests>=2.25.1', 'scipy>=1.8.0' ] @@ -44,7 +44,7 @@ dependencies = [ [project.optional-dependencies] azure-quantum = [ - 'azure-quantum' + 'azure-quantum < 3.10' ] braket = [ diff --git a/setup.py b/setup.py index 24a647bd..74104658 100755 --- a/setup.py +++ b/setup.py @@ -429,8 +429,9 @@ def _get_compilation_commands(self, ext): def _configure_compiler(self): # Force dry_run = False to allow for compiler feature testing - dry_run_old = self.compiler.dry_run - self.compiler.dry_run = False + dry_run_old = getattr(self.compiler, 'dry_run', False) + if hasattr(self.compiler, 'dry_run'): + self.compiler.dry_run = False if ( int(os.environ.get('PROJECTQ_CLEANUP_COMPILER_FLAGS', 0)) @@ -472,7 +473,8 @@ def _configure_compiler(self): if compiler_type == 'unix' and compiler_test(self.compiler, '-fvisibility=hidden'): self.opts.append('-fvisibility=hidden') - self.compiler.dry_run = dry_run_old + if hasattr(self.compiler, 'dry_run'): + self.compiler.dry_run = dry_run_old status_msgs('Finished configuring compiler!') def _configure_openmp(self): @@ -680,7 +682,8 @@ def run(self): command.append('--warnings-as-errors=*') for ext in self.distribution.ext_modules: command.extend(os.path.abspath(p) for p in ext.sources) - spawn(command, dry_run=self.dry_run) + if not self.dry_run: + spawn(command) # ------------------------------------------------------------------------------