recruit-communications / pyqubo

Python DSL for constructing QUBOs from mathematical expressions.
https://pyqubo.readthedocs.io/
Apache License 2.0
176 stars 46 forks source link

Automatically handle strength for dimension reduction #182

Open haoyudoingthings opened 2 years ago

haoyudoingthings commented 2 years ago

Is your feature request related to a problem? Please describe. When compiling a high order polynomial expression into a model, the strength for dimension reduction has to be specified beforehand. Not only is this annoying because sometimes estimating what the strength should be is non-trivial, but it sometimes leads to unnecessarily large constraints.

For example, if we want to reduce 100*a*b*c + x*y*z to quadratic, the strength has to be at least 100, which is unnecessarily large for the constraint for x*y*z.

Describe the solution you'd like

  1. The compile() function can determine the strength automatically. It already reads through every term of the polynomial anyways, it should have all the information it needs.
  2. The strengths for each constraint should be determined separately so as to minimize unnecessary energy barriers in the model.

Describe alternatives you've considered I have actually put together a fix that does what I suggested. The fix was based on pyqubo == 0.4.0, however, because I am not fluent in c++ enough to contribute to the current version.

All the modified files are in my repo for your reference if needed.

kotarotanahashi commented 2 years ago

@haoyudoingthings compile method has the argument strength, so you can specify the value of the strength.

import pyqubo as pq

a,b,c = pq.Binary("a"), pq.Binary("b"), pq.Binary("c")

H = a*b*c + a*b
model = H.compile(strength=10)
model.to_qubo()

output will be

({('b', 'a * b'): -20.0,
  ('a * b', 'a * b'): 31.0,
  ('c', 'a * b'): 1.0,
  ('a', 'a * b'): -20.0,
  ('a', 'b'): 10.0},
 0.0)
haoyudoingthings commented 2 years ago

Yes I know. What I meant was that the strength argument is applied to every pair of reduced variables, when different strengths for different pairs are sometimes more preferable.

For example,

import pyqubo as pq

a, b, c, x, y, z = pq.Binary('a'), pq.Binary('b'), pq.Binary('c'), pq.Binary('x'), pq.Binary('y'), pq.Binary('z')
H = 100*a*b*c + x*y*z

The minimum strength that preserve the ground state is strength=100. The output of H.compile(strength=100).to_qubo() is

({('x', 'y'): 100.0,
  ('x', 'x*y'): -200.0,
  ('x*y', 'y'): -200.0,
  ('a', 'b'): 100.0,
  ('a', 'a*b'): -200.0,
  ('a*b', 'b'): -200.0,
  ('x*y', 'z'): 1.0,
  ('a*b', 'c'): 100.0,
  ('x', 'x'): 0.0,
  ('y', 'y'): 0.0,
  ('x*y', 'x*y'): 300.0,
  ('a', 'a'): 0.0,
  ('b', 'b'): 0.0,
  ('a*b', 'a*b'): 300.0,
  ('z', 'z'): 0.0,
  ('c', 'c'): 0.0},
 0.0)

However, for the term x*y*z, strength=1 is actually enough. So, if our goal is to minimize the qubo coefficients while preserving the ground state, the ideal output should be

({('x', 'y'): 1.0,
  ('x', 'x*y'): -1.0,
  ('x*y', 'y'): -1.0,
  ('a', 'b'): 100.0,
  ('a', 'a*b'): -100.0,
  ('a*b', 'b'): -100.0,
  ('x*y', 'z'): 1.0,
  ('a*b', 'c'): 100.0,
  ('x', 'x'): 0.0,
  ('y', 'y'): 0.0,
  ('x*y', 'x*y'): 1.0,
  ('a', 'a'): 0.0,
  ('b', 'b'): 0.0,
  ('a*b', 'a*b'): 100.0,
  ('z', 'z'): 0.0,
  ('c', 'c'): 0.0},
 0.0)

Also sorry for closing and reopening the issue. I misclicked the button.