Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove dependance of BooleanExpression on tweedledum #13769

Merged
merged 15 commits into from
Feb 24, 2025

Conversation

gadial
Copy link
Contributor

@gadial gadial commented Jan 30, 2025

Summary

Rewrites the BooleanExpression class such that expression parsing and circuit synthesis does not rely on the tweedledum library, and moves it to the synthesis library. Adds a BitFlipOracle class to preserve the user-facing usage of BooleanExpression.

Partially addresses #13755.

Details and comments

BooleanExpression is used to describe classical boolean functions and synthesize phase/bit-flip oracles for them. Currently it is used directly within qiskit only for the PhaseOracle class.

The current implementation of BooleanExpression relies on the tweedledum library for parsing and synthesizing. Since qiskit 2.0 will stop using tweedledum, this PR makes the code of BooleanExpression independent, in the cost of lower capabilities.

These are the main changes:

  1. Parsing is now done using the ast library, using a custom boolean_expression_visitor. Parsing DIMACS files is done directly using regexp.
  2. Oracle synthesis is now performed directly (within the new boolean_expression_synth module) using a straightforward approach seemingly also employed by tweedledum, given a representation of the function as ESOP (Exclusive sum of squares).
  3. A rudimentary procedure for converting a BooleanExpression into ESOP representation was also added. While it features basic optimizations to reduce the size of the ESOP, it needs to generate the full truth table of the represented function, making it a viable approach only for functions on a relatively low number of variables. This can be improved upon in the future if required, using more sophisticated ESOP generation and minimization techniques (such as using BDDs or SAT-solvers).
  4. All the synthesis-related code was moved to a new directory, synthesis/boolean. A new class, BitFlipOracle was added to the circuit library. The old BooleanExpression code remains unchanged and will be deprecated in a separate PR along with the rest of the classicalfunction library.

Non comprehensive benchmarking indicates a factor-10 runtime blowup for the non-tweedledum version, but oracle generation for functions of around 10 variables is still viable.

╒═══════════════════════════════╤═════════════╤═════════════╤═══════════════════╕
│ Test Case                     │         new │         old │   Ratio (new/old) │
╞═══════════════════════════════╪═════════════╪═════════════╪═══════════════════╡
│ 3 vars, 9 clauses (seed 42)   │ 0.000578117 │ 0.00033474  │          1.72707  │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 2 vars, 6 clauses (seed 42)   │ 0.000580263 │ 0.0016449   │          0.352766 │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 4 vars, 12 clauses (seed 42)  │ 0.00112677  │ 0.000320101 │          3.52004  │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 5 vars, 15 clauses (seed 42)  │ 0.00388117  │ 0.00120397  │          3.22365  │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 6 vars, 18 clauses (seed 42)  │ 0.0112587   │ 0.00248165  │          4.5368   │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 7 vars, 21 clauses (seed 42)  │ 0.0248424   │ 0.0142735   │          1.74045  │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 8 vars, 24 clauses (seed 42)  │ 0.0663635   │ 0.0208194   │          3.18758  │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 9 vars, 27 clauses (seed 42)  │ 0.156358    │ 0.0419133   │          3.73051  │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 10 vars, 30 clauses (seed 42) │ 0.406802    │ 0.071683    │          5.67501  │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 11 vars, 33 clauses (seed 42) │ 0.863733    │ 0.156799    │          5.50854  │
├───────────────────────────────┼─────────────┼─────────────┼───────────────────┤
│ 12 vars, 36 clauses (seed 42) │ 2.25606     │ 0.234907    │          9.60408  │
╘═══════════════════════════════╧═════════════╧═════════════╧═══════════════════╛

Comparing the CX gate count in the result of transpiling the oracle for GenericBackendV2(num_qubits=27) with pass manager generate_preset_pass_manager(optimization_level=2, backend=backend) yields a slight advantage to the new implementation:

╒═══════════════════════════════╤═══════╤═══════╤═══════════════════╕
│ Test Case                     │   new │   old │   Ratio (new/old) │
╞═══════════════════════════════╪═══════╪═══════╪═══════════════════╡
│ 2 vars, 6 clauses (seed 42)   │     0 │     0 │        nan        │
├───────────────────────────────┼───────┼───────┼───────────────────┤
│ 3 vars, 9 clauses (seed 42)   │     0 │     0 │        nan        │
├───────────────────────────────┼───────┼───────┼───────────────────┤
│ 4 vars, 12 clauses (seed 42)  │     0 │     0 │        nan        │
├───────────────────────────────┼───────┼───────┼───────────────────┤
│ 5 vars, 15 clauses (seed 42)  │    36 │    36 │          1        │
├───────────────────────────────┼───────┼───────┼───────────────────┤
│ 6 vars, 18 clauses (seed 42)  │   218 │   244 │          0.893443 │
├───────────────────────────────┼───────┼───────┼───────────────────┤
│ 7 vars, 21 clauses (seed 42)  │   694 │  1088 │          0.637868 │
├───────────────────────────────┼───────┼───────┼───────────────────┤
│ 8 vars, 24 clauses (seed 42)  │  1842 │  3125 │          0.58944  │
├───────────────────────────────┼───────┼───────┼───────────────────┤
│ 9 vars, 27 clauses (seed 42)  │  6954 │  8238 │          0.844137 │
├───────────────────────────────┼───────┼───────┼───────────────────┤
│ 10 vars, 30 clauses (seed 42) │ 21927 │ 25955 │          0.844808 │
╘═══════════════════════════════╧═══════╧═══════╧═══════════════════╛

@gadial gadial requested a review from a team as a code owner January 30, 2025 19:14
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Cryoris
  • @Qiskit/terra-core
  • @ajavadia

@gadial gadial added this to the 2.0.0 milestone Jan 30, 2025
@ShellyGarion ShellyGarion self-assigned this Jan 31, 2025
@coveralls
Copy link

coveralls commented Feb 2, 2025

Pull Request Test Coverage Report for Build 13499061671

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 223 of 229 (97.38%) changed or added relevant lines in 6 files are covered.
  • 2272 unchanged lines in 84 files lost coverage.
  • Overall coverage decreased (-0.7%) to 88.15%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/synthesis/boolean/boolean_expression.py 89 90 98.89%
qiskit/synthesis/boolean/boolean_expression_visitor.py 38 39 97.44%
qiskit/circuit/library/bit_flip_oracle.py 14 16 87.5%
qiskit/circuit/library/phase_oracle.py 15 17 88.24%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/basis/basis_translator/basis_search.rs 1 99.31%
crates/accelerate/src/basis/basis_translator/compose_transforms.rs 1 99.16%
crates/accelerate/src/euler_one_qubit_decomposer.rs 1 91.5%
crates/accelerate/src/gate_direction.rs 1 97.34%
crates/accelerate/src/target_transpiler/nullable_index_map.rs 1 67.17%
crates/accelerate/src/twirling.rs 1 97.62%
qiskit/circuit/add_control.py 1 97.78%
qiskit/providers/basic_provider/basic_provider_tools.py 1 83.33%
qiskit/providers/fake_provider/fake_backend.py 1 85.51%
qiskit/providers/fake_provider/fake_openpulse_3q.py 1 92.86%
Totals Coverage Status
Change from base Build 13072040346: -0.7%
Covered Lines: 78607
Relevant Lines: 89174

💛 - Coveralls

@ShellyGarion ShellyGarion linked an issue Feb 2, 2025 that may be closed by this pull request
Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gadial for taking care of this - it looks great!

Note that there are some necessary API changes here, which makes sense and it's also allowed for Qiskit 2.0, but probably need better release notes, as well as raise deprecation warnings in Qiskit 1.4.

Did you make some experiments to check the performance compared to Tweedledum?



@HAS_TWEEDLEDUM.require_in_instance
class BooleanExpression(ClassicalElement):
class BooleanExpression(Gate):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The easiest deprecation path would be to just deprecate BooleanExpression in place and have the new version in the circuit library.

It might also be worth considering a renaming to avoid too much confusion with the new object. What does this circuit implement exactly? Does it flip a target bit if the boolean expression is satisfied or does it something in-place? If it's the first, maybe BitflipOracle could be a good name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a sound strategy - we already have a PhaseOracle so we can add BitFlipOracle which will be similar but with a different synthesis, and both gates would rely on a non-gate version of BooleanExpression (maybe with a different name) which handles parsing, simulating (to create the truth table) and synthesizing (since we might want to invest in optimized algorithms later.

The main question is where to put those files. We can put BitFlipOracle next to PhaseOracle in qiskit.circuit.library but we still need to put the NewBooleanExpression module (which would consist at least of boolean_expression, boolean_expression_visitor and boolean_expression_synth files) somewhere that makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e403028

Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gadial for taking care of this! I have a few comments and questions.

@@ -345,6 +345,7 @@
QuantumVolume
PhaseEstimation
GroverOperator
BitFlipOracle
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps this should be done in another PR, like @Cryoris's #13520, but I think that the 3 oracles (grover, phase and bitflip) deserve their own section in the circuit library docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a good idea but I agree it belongs in another PR.

Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @gadial !

Thanks for the comparison of synthesis time compared to tweedledum.
I also wonder what is the CX-cost comparison of the transpiled circuits.

@ShellyGarion ShellyGarion added the Changelog: New Feature Include in the "Added" section of the changelog label Feb 12, 2025
ShellyGarion
ShellyGarion previously approved these changes Feb 12, 2025
Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @Cryoris - do you have any further comments?

@gadial gadial requested a review from Cryoris February 12, 2025 11:39
Comment on lines 20 to 21
class BitFlipOracle(QuantumCircuit):
r"""Bit-flip Oracle.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the spirit of circuit library refactoring, do we want BitFlipOracle and PhaseOracle to also have Gate variants? @Cryoris, what are your thoughts on this? What about BooleanExpression?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'm againt BooleanExpression being a Gate (as is currently the situation) since it's "interface" is not clear, as is reflected in the difference between phase oracle (n qubits, applies Z conditioned on f(x)) and bit flip oracle (n+1 qubits, applies X on a specific qubit conditioned on f(x)). Currently the somewhat arbitrary choice was to have BooleanExpression as a gate act as a bit-flip oracle.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree that BooleanExpression should not be a Gate, thanks! I also think that we probably want to have the new BitFlipOracle available as a subclass of a quantum circuit, at the least to be consistent with PhaseOracle. However, for the sake of circuit library refactoring, in a follow-up we should also expose phase oracle and bit flip oracles as gates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to make a point for Gates 🙂 in the circuit library modernization (#13046) we define that all objects are either gates or functions -- gates, if their action is defined by a mathematical operation and we are free to synthesize with different algorithms, and functions, if they are defined by their structure.

To me, this is therefor a clear case of a Gate. We can discuss what interface makes most sense, if there are multiple options. What you suggested above makes sense to me:

  • a BitFlipOracle flips a target qubit if f(x) is satisfied
  • a PhaseOracle flips the phase of states satisfying f(x) -- note that it is fine if the actual implementation uses an auxiliary qubit.

I would prefer not introducing a BitFlipOracle(QuantumCircuit) if we add a gate. The PhaseOracle should also be updated to be a gate once we agree on the interface, to match the rest of the library and enable different synthesis algorithms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the point of interface to the future PhaseOracleGate and BitFlipOracleGate, do you think these gates should have the "evaluate" and "from_dimacs" methods?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the docstrings,

  • The phase oracle implements $|x\rangle \mapsto (-1)^{f(x)}|x\rangle$, and thus does not involve an additional qubit,
  • The bitflip oracle implements $|x\rangle|y\rangle \mapsto |x\rangle|f(x)\oplus y\rangle$, and thus involves an additional "target" qubit $y$.

@Cryoris, I think the questions are: would you like PhaseOracleGate and BitFlipOracleGate be a part of this PR and/or would you like to pending-deprecate PhaseOracle as a part of this PR?

Alternatively, maybe it's fine to introduce a BitFlipOracle quantum circuit (as this PR is doing), and add the two gates in a follow-up?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concrete question is whether I can simply change the line class BitFlipOracle(QuantumCircuit) into class BitFlipOracleGate(Gate) and be done with it, or whether that means I should implement additional methods currently not implemented.

As for evaluate_bitstring I don't mind removing it from bit flip oracle, and after deprecating PhaseOracle in favor of PhaseOracleGate we can also drop it from PhaseOracleGate. It's not a functionality that a gate should supply, and anyway the user can always do oracle.boolean_expression.simulate.

Copy link
Member

@ShellyGarion ShellyGarion Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are already introducing BitFlipOracleGate as part of this PR, should we also add PhaseOracleGate ?
(and PhaseOracle will be deprecated at Qiskit 3.0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 991ded1

I think we can merge.

Copy link
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really-really cool PR. Thanks for making the phase oracle (and its friend, bit-flip oracle) available to those who can no longer use tweedledum.

My only suggestions are around documentation: expanding the release notes and adding python type hints to various functions.

Comment on lines 20 to 21
class BitFlipOracle(QuantumCircuit):
r"""Bit-flip Oracle.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree that BooleanExpression should not be a Gate, thanks! I also think that we probably want to have the new BitFlipOracle available as a subclass of a quantum circuit, at the least to be consistent with PhaseOracle. However, for the sake of circuit library refactoring, in a follow-up we should also expose phase oracle and bit flip oracles as gates.

Comment on lines 52 to 59
var_order: list = None,
) -> None:
"""Creates a BitFlipOracle object

Args:
expression: A Python-like boolean expression.
var_order(list): A list with the order in which variables will be created.
(default: by appearance)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments on docstrings (which might be off in the original class already, not that you added this 🙂)

  • if None is supported we can annotate it with OtherType | None (this might require from __future__ import annotations)
  • you could also add the type of the list elements -- I'm not sure but this might be list[str]?
  • if the type is in the signature it can be skipped in the docstring
  • we typically skip the initial sentence in the initializer
Suggested change
var_order: list = None,
) -> None:
"""Creates a BitFlipOracle object
Args:
expression: A Python-like boolean expression.
var_order(list): A list with the order in which variables will be created.
(default: by appearance)
var_order: list[str] | None = None,
) -> None:
"""
Args:
expression: A Python-like boolean expression.
var_order: A list with the order in which variables will be created.
(default: by appearance)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d94f490

Comment on lines 20 to 21
class BitFlipOracle(QuantumCircuit):
r"""Bit-flip Oracle.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the point of interface to the future PhaseOracleGate and BitFlipOracleGate, do you think these gates should have the "evaluate" and "from_dimacs" methods?

Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some minor comments on the docs. it's worth to build it on the main branch after 1.4 release to see that there are no conflicts or other problems.


class TruthTable:
"""A simple implementation of a truth table for a boolean function"""

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you like to add some explanation or example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 454ecc5

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the effort, the result looks very nice! I have a few small comments, the main one about the fact that we don't synthesize Gate objects upon creation, but only when _define is called. Otherwise this LGTM 👍🏻

(default: by appearance)
"""
self.boolean_expression = BooleanExpression(expression, var_order=var_order)
self.oracle = self.boolean_expression.synth(circuit_type="bit")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs the synthesis, right? If yes, this should be moved into _define to avoid eagerly synthesizing the circuit upon gate creation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 454ecc5

def __init__(
self,
expression: str,
var_order: list[str] | None = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add an optional label: str | None = None here, which allows the user to overwrite the default label? All gates typically have this 🙂 (also for the BitFlipOracleGate)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 454ecc5



@ddt
class TestPhaseOracleGate(QiskitTestCase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you merge this test with the one for the circuit? They seem to be the same 🙂 You could e.g. do

@data( ... )
@unpack
def test_statevector(self, expression, truth_table):
    for use_gate in [True, False]:
        oracle = PhaseOracleGate(expression) if use_gate else PhaseOracle(expression)
        # ... rest as before ...
        
        with self.subTest(use_gate=use_gate):
            self.assertListEqual( ... )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually prefer to avoid this since we are deprecating PhaseOracle and I want to keep is separate from PhaseOracleGate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're only going to remove it for 3.0 though, so it would be nice to have them together since bugfixes and new checks would likely need to cover both. But we can also keep them separate and copy these, if they occur in the future, in case you prefer 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm convinced. Done in 454ecc5

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I won't put it on the merge queue yet in case @ShellyGarion has further comments 🙂

@ShellyGarion ShellyGarion added this pull request to the merge queue Feb 24, 2025
@mtreinish mtreinish removed this pull request from the merge queue due to a manual request Feb 24, 2025
@mtreinish mtreinish added this pull request to the merge queue Feb 24, 2025
Merged via the queue into Qiskit:main with commit d59e0c2 Feb 24, 2025
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove tweedledum from qiskit-sdk
7 participants