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

Qubo simulations #34

Open
wants to merge 3 commits into
base: qubo_simulations
Choose a base branch
from

Conversation

prasanthcakewalk
Copy link

@prasanthcakewalk prasanthcakewalk commented Aug 24, 2021

<Recreating an older PR. This time the source branch is directly based on the upstream branch (both qubo_simulations).>

This PR extends QTensor to perform QAOA for general Ising Hamiltonians, which can contain constant, linear, and quadratic terms.

The Ising Hamiltonian is to be encoded as a networkx graph.
For a Hamiltonian H = \sum_{i ≠ j} (A_ij/2) s_i s_j + \sum_i B_i s_i + C

The nodes of the graph correspond to the variables s_i = -1 or 1.
A_ij is given by the 'weight' attribute of the edge between i and j. If an edge is absent, or doesn't have a 'weight', the corresponding A_ij is assumed to be 0.
B_i is given by the 'weight' attribute of the node i. If the 'weight' attribute is absent for a node, the corresponding B_i is assumed to be 0.
C is specified by a graph attribute called 'offset'.

Also included are

  • Utilities to convent
    • a networkx graph representation to DOcplex model and back. The DOcplex model uses binary variables (x_i=0 or 1).
    • a networkx graph representation to qiskit QuadraticProgram and back. The qiskit QuadraticProgram uses binary variables (x_i=0 or 1).
  • Tests to verify the working of the new features
  • A few bugfixes

I've also added some package requirements: scipy, docplex, qiskit[optimization]. The first two are part of the dependency-tree of qtensor anyway. scipy is used to validate the working of the qubo simulations for small problems, and docplex and qiskit_optimization are used by the graph conversion utilities.

@prasanthcakewalk
Copy link
Author

@danlkv Sorry about the new PR. I'm copying your message from the other thread here.

Hi @prasanthcakewalk, thanks for the contribution! I added a qubo_simulations branch, which is a dev with merged changes from master.

TODO, as discussed:

  1. Add tests, compare to qiskit or a well-known result
  2. Better handling of single-node energy

Problems to think about:

  1. pynauty fails to install. @prasanthcakewalk Could you provide more info on the issue? @rsln-s did you face any problems with pynauty python package? Maybe we should consider removing from setup.py dependencies and make it an optional dependency.
  2. A lot of subclasses to support different builders and composers. Consider changing the architecture.

@danlkv danlkv mentioned this pull request Aug 24, 2021
@prasanthcakewalk
Copy link
Author

prasanthcakewalk commented Aug 24, 2021

Updates on the TODO items:

  1. I've added a test file which includes a naive scipy based QAOA simulator. The tests compare the results from qtensor and scipy for small problems.
  2. I've changed the handling of single-node energy by creating "node versions" of _edge_energy_circuit, energy_expectation_lightcone, get_edge_subgraph, and energy_expectation. This currently leads to some code repetition. It might be easier to modify the implementation of these functions to handle both nodes and edges. But that would be a major change to the current codebase, so we can leave that for after the approval of the new functionality.

@prasanthcakewalk
Copy link
Author

prasanthcakewalk commented Aug 24, 2021

Updates/thoughts on the other problems you mentioned:

  1. The reason pynauty doesn't work for me is because it's a Unix/Linux/Mac only package. I'm on windows (and resisting using WSL). For now, I'm getting around it by just commenting out that requirement when installing QTensor in my virtual env. If it comes down to it, I can install WSL and use pynauty.
  2. One way of getting around having different (sub)classes for the different builder-composer combinations would be have a metafunction/metaclass that takes the composer and builder as string valued inputs and returns the appropriate composer. This might be easier to use (and document). Here's ugly implementation that can be directly dropped into the current code:
def meta_composer(G, *args, composer='default', builder='qtree', **kwargs):
    composer_dict = {
        'default': QAOAComposer,
        'old': OldQAOAComposer,
        'zz': ZZQAOAComposer,
        'weightedzz': WeightedZZQAOAComposer
    }
    builder_dict = {
        'cirq': CirqBuilder,
        'qiskit': QiskitBuilder,
        'qtree': QtreeBuilder,
        'qtreefull': QtreeFullBuilder,
        'torch': TorchBuilder
    }
    unallowed_combinations = set()
    if (composer, builder) in unallowed_combinations:
        raise ValueError(f"The combination {composer = }, {builder = } is not allowed!")
    
    class MetaComposer(composer_dict[composer]):
        def _get_builder_class(self):
            return builder_dict[builder]
    
    return MetaComposer(G, *args, **kwargs)

A cleaner implementation would require some modifications, but might ultimately be worth it.

@danlkv
Copy link
Owner

danlkv commented Aug 26, 2021

I think what's better is that the builder should be an argument of composer. For example:

qtensor.DefaultQAOAComposer(G, gamma, beta, composer='qtree')

The composer type could be a string identifier, or a builder object. By default we should use qtree. If it's a string identifier, use the builder_dict you made in the above comment. I don't think there are any unallowed combinations.
This ensures there's a "has-a" relationship rather than a "is-a". I think from software architecture this is called a Bridge pattern (an interesting read).

So, in UML that would be

@@ -7,14 +7,16 @@
'matplotlib'
,'google-api-core[grpc]<=1.14.0'
,'cirq'
,'qiskit'
,'qiskit[optimization]'
Copy link
Owner

Choose a reason for hiding this comment

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

Why do you change this dependency?

Copy link
Author

Choose a reason for hiding this comment

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

qiskit.optimization, which used to be a part of qiskit aqua, is now deprecated and it's functionality has been moved into a package called qiskit-optimization.

So, while from qiskit.optimization import QuadraticProgram works for now, the recommended version is from qiskit_optimization import QuadraticProgram. This is needed for one of the converter functions, namely graph_to_qiskitqubo. Arguably though, that converter function can just be left out.

qiskit-optimization is only an optional dependency of the qiskit package. It can either be added either as qiskit[optimization], or separately as qiskit-optimization.

https://github.com/Qiskit/qiskit-aqua/blob/main/README.md#migration-guide

Copy link
Owner

Choose a reason for hiding this comment

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

Let's use the current version, from qiskit_optimization import QuadraticProgram then. Dependencies should have both qiskit and qiskit-optimization

@@ -69,7 +69,7 @@ def test_qtree_energy():
G=G, gamma=gamma, beta=beta)

print('Energy', E)
assert np.imag(E)<1e-6
assert np.abs(np.imag(E))<1e-6
Copy link
Owner

Choose a reason for hiding this comment

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

😅nice catch


return G

def docplexqubo_to_graph(mdl): # mdl: DOcplex model
Copy link
Owner

Choose a reason for hiding this comment

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

Hint: you can use python's built-in typing. It would look like this:

def docplexqubo_to_graph(mdl: Model)

Model is imported in this file from docplex. Static code analyzers will be able to automatically catch uses where types don't match

Copy link
Author

Choose a reason for hiding this comment

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

Thanks! I'll fix this today.


return _qubo_to_graph(n, qubo_constant, qubo_linear, qubo_quadratic)

def qiskitqubo_to_graph(qp): # qp: qiskit_optimization quadratic program
Copy link
Owner

Choose a reason for hiding this comment

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

Same here.

from qiskit_optimization import QuadraticProblem
def qiskitqubo_to_graph(qp: QuadraticProblem):

Copy link
Author

Choose a reason for hiding this comment

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

I'll fix this too


for node in G.nodes():
if G.nodes[node]['weight'] is not None:
total_E += self._get_node_energy(G, gamma, beta, node)
Copy link
Owner

Choose a reason for hiding this comment

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

_get_node_energy is undefined for this class. Just add a stub with a NonImplementedError raised.

Maybe _get_edge_energy should also raise and be implemented in QtreeSimulator, since currently it's defined on a superclass and would not work for CirqQAOASimulator...

Copy link
Author

Choose a reason for hiding this comment

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

Thanks a good point! I'll fix this.

trial_result = self.simulate(circuit)
weight = G.nodes[node]['weight']

return weight*np.sum(trial_result.state_vector())
Copy link
Owner

Choose a reason for hiding this comment

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

Did you actually make cirq simulation to work? I remember there were some problems last time I tried

Copy link
Author

Choose a reason for hiding this comment

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

I actually only tested the qtree versions. I just wrote the cirq version to just be consistent with the QAOACirqSimulator class, but didn't test it 😄. Should I just remove CirqIsingQAOASimulator?

Copy link
Owner

Choose a reason for hiding this comment

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

I think if we can't promise that this works, it shouldn't be there. But in case it works, it's fine.

print(f"Energy = {E}")
assert np.abs(np.imag(E))<1e-6

for (n, p, d) in [(6, 2, 0), (6, 2, 3), (6, 2, 5)]:
Copy link
Owner

Choose a reason for hiding this comment

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

For future note: there's such thing as test parametrization in pytest. Usage in qtensor: https://github.com/danlkv/QTensor/blob/dev/qtensor/tests/test_qaoa_energy.py#L32 and pytest docs

Copy link
Author

Choose a reason for hiding this comment

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

Thanks. I'll change the tests to be inline with this

@prasanthcakewalk
Copy link
Author

I agree that the builder would be better as a property of the composer, passed during initialization.

Regarding the composers themselves, there may be a better way to organize them. It seems like the differences between OldQAOAComposer, QAOAComposer, and QAOAComposerChords are relatively more fundamental level than the other composer classes. For example, the ZZ versions differ from the non-ZZ versions only in the implementation of append_zz_term. This can potentially be passed as a string argument which sets the append_zz_term method from a set of possible functions.

Likewise, WeightedZZQAOAComposer differs from the unweighted composer in the problem that it solves. I wonder if it would make sense to start decouple the particular problem being solved from the QAOAsimulation. For example, you can start with a base class that solves a general Ising hamiltonian (with linear and quadratic terms) and returns the energy (with a dummy _post_process_energy that simply passes along the energy). Unweighted MaxCut can then implemented as a subclass of this base, with default edge weights set to 1, and an appropriately overridden _post_process_energy. I don't think the performance will be affected appreciably by this.

I'll see if I can put together a diagram (or a skeleton code) for these things

@danlkv
Copy link
Owner

danlkv commented Aug 29, 2021

There are 3 tasks here:

  1. Complete the current implementation of generic ising, fast and well-defined, just some code-style fixes
  2. Refactor Composers to contain a Builder, using bridge pattern. Slower and requires some research.
  3. Refactor QAOA simulators and composers, decouple Simulation from the flavour of QAOA. I think this has several sub-tasks and requires even more research and planning.

I think 1 should be completed first, then 2. If you are interested in doing so we can go for 3, and the practice from doing 2 will help.

For diagrams, I recommend plantuml. There are several types of UML diagrams and the relevant for our case is Class diagram.

For example, this is the picture that I had in mind for solving 3.
XOz1ImD138Nl-HMX9ptC7n0FhPwbBg9NIRjfDv2Pb4oMglZZtIquBdZeC8UPFDxtyjOro6FC2ig7m31qEGw1lJZlGwp0NgbMV2CNcl-apsuxvGp_HtY7TWh9yiNl9tBAzUOM07xL--zqWQTq5TSCgLcwpNRJlJvieLWmNhQrGviYh6AGYjCz3-

You can edit it here http://www.plantuml.com/plantuml/uml/XP11ImCn48Nlyok6FUaXVw1uQFKiTH6zohGxxGuacJAPfIh-U3U5fWiUX1oIzvZlvSswE9bFAKBaWwChcXn7nq6CzowQOH-f57md4wflYY-ckyW9_aguGZsv9FdcZnCPfN9t3m1Nz-4dNE0X_8jh0w6Dz9ljfdzxt49MPxpyLWzDHUXyb8BpEOziZ42s4pUoht3cYk01SAGaqJKBPJmgbmuu3rY1q40rT9dvJ9zFazzQX5b_bJH5ShKLTbTzAbnkHWVjkkpTkp4t1zPpVDtzBm00

@prasanthcakewalk
Copy link
Author

prasanthcakewalk commented Oct 5, 2021

I came across the concept of multiple dispatch recently. It's when different versions of a function can be defined for different types of arguments. It's kinda similar to function overloading in C++, except overloading is based on static types. It's more similar to Python's class-based oop, where depending on the type of the object, you can have different definitions for the same operation. Except, instead of doing this kind of dispatch based only on the first argument (self), in multiple dispatch you can do it for all arguments.

I bring this up because, I think multiple dispatch can come in handy for refactoring QTensor. Apparently python has a few different packages which provide multiple dispatch support (eg: https://github.com/wesselb/plum). Maybe worth looking into?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants