Nota
Esta página fue generada a partir de docs/tutorials/02_converters_for_quadratic_programs.ipynb.
Conversores para Programas Cuadráticos#
Los problemas de optimización en el módulo de optimización de Qiskit se representan con la clase QuadraticProgram
, que es una representación genérica y poderosa de los problemas de optimización. En general, los algoritmos de optimización se definen para una determinada formulación de un programa cuadrático y necesitamos convertir nuestro problema al tipo correcto.
Por ejemplo, Qiskit Optimization proporciona varios algoritmos de optimización que pueden manejar problemas de Optimización Binaria Cuadrática Sin Restricciones (Quadratic Unconstrained Binary Optimization, QUBO). Estos se mapean a Hamiltonianos Ising, para los cuales Qiskit Optimization usa el objeto qiskit.quantum_info.SparsePauliOp
, y luego se aproxima su estado fundamental. Para esta optimización, se pueden utilizar algoritmos comúnmente conocidos como VQE o QAOA como rutina subyacente. Consulta el siguiente tutorial sobre el Optimizador Propio Mínimo para obtener más detalles. Ten en cuenta que también existen otros algoritmos que funcionan de manera diferente, como por ejemplo el GroverOptimizer
.
Para mapear un problema al formato de entrada correcto, el módulo de optimización de Qiskit Optimization ofrece una variedad de convertidores. En este tutorial proporcionamos una descripción general de esta funcionalidad. Actualmente, Qiskit Optimization contiene los siguientes convertidores.
InequalityToEquality
: convertir restricciones de desigualdad en restricciones de igualdad con variables de holgura (slack) adicionales.IntegerToBinary
: convertir variables enteras en variables binarias y los coeficientes correspondientes.LinearEqualityToPenalty
: convertir las restricciones de igualdad en términos adicionales de la función objetivo.LinearInequalityToPenalty
: convertir las restricciones de desigualdad en términos adicionales de la función objetivo.MaximizeToMinimize
: convertir al problema de minimización equivalente.MinimizeToMaximize
: convertir al problema de maximización equivalente.QuadraticProgramToQubo
: un contenedor que incluyeInequalityToEquality
,IntegerToBinary
,LinearEqualityToPenalty
,LinearInequalityToPenalty
yMaximizeToMinimize
por conveniencia.
InequalityToEquality#
InequalityToEqualityConverter
convierte las restricciones de desigualdad en restricciones de igualdad con variables de holgura adicionales para eliminar las restricciones de desigualdad del QuadraticProgram
. Los límites superiores y los límites inferiores de las variables de holgura se calcularán a partir de la diferencia entre el lado izquierdo y el lado derecho de las restricciones. Los signos de las variables de holgura dependen de los símbolos en restricciones como \(\leq\) y \(\geq\).
A continuación se muestra un ejemplo de un problema de maximización con dos restricciones de desigualdad. Las variables \(x\) y \(y\) son variables binarias y la variable \(z\) es una variable entera.
Con QuadraticProgram
, un modelo de optimización del problema se escribe de la siguiente manera.
[1]:
from qiskit_optimization import QuadraticProgram
from qiskit_optimization.translators.docplex_mp import to_docplex_mp
[2]:
qp = QuadraticProgram()
qp.binary_var("x")
qp.binary_var("y")
qp.integer_var(lowerbound=0, upperbound=7, name="z")
qp.maximize(linear={"x": 3, "y": 2, "z": 1})
qp.linear_constraint(linear={"x": 1, "y": 1, "z": 1}, sense="LE", rhs=5.5, name="xyz_leq")
qp.linear_constraint(linear={"x": 1, "y": 1, "z": 1}, sense="GE", rhs=2.5, name="xyz_geq")
print(qp.prettyprint())
Problem name:
Maximize
3*x + 2*y + z
Subject to
Linear constraints (2)
x + y + z <= 5.5 'xyz_leq'
x + y + z >= 2.5 'xyz_geq'
Integer variables (1)
0 <= z <= 7
Binary variables (2)
x y
Llama al método convert
de InequalityToEquality
para convertirlo.
[3]:
from qiskit_optimization.converters import InequalityToEquality
[4]:
ineq2eq = InequalityToEquality()
qp_eq = ineq2eq.convert(qp)
print(qp_eq.prettyprint())
Problem name:
Maximize
3*x + 2*y + z
Subject to
Linear constraints (2)
x + xyz_leq@int_slack + y + z == 5 'xyz_leq'
x - xyz_geq@int_slack + y + z == 3 'xyz_geq'
Integer variables (3)
0 <= z <= 7
0 <= xyz_leq@int_slack <= 5
0 <= xyz_geq@int_slack <= 6
Binary variables (2)
x y
Después de la conversión, la formulación del problema se parece al resultado anterior. Como podemos ver, las restricciones de desigualdad se reemplazan con restricciones de igualdad con variables de holgura (variables slack) enteras adicionales, \(xyz\_leg\text{@}int\_slack\) y \(xyz\_geq\text{@}int\_slack\).
Expliquemos cómo funciona la conversión. Por ejemplo, el límite inferior del lado izquierdo de la primera restricción es \(0\) que es el caso de \(x=0\), \(y=0\), y \(z=0\). Por lo tanto, el límite superior de la variable entera adicional debe ser \(5\) para poder satisfacer incluso el caso de \(x=0\), \(y=0\), y \(z=0\). Ten en cuenta que cortamos la parte después del punto decimal en la formulación convertida, ya que el lado izquierdo de la primera restricción en la formulación original puede ser solo valores enteros. Para la segunda restricción, básicamente aplicamos el mismo enfoque. Sin embargo, el símbolo en la segunda restricción es \(\geq\), por lo que agregamos un signo menos antes de \(xyz\_geq\text{@}int\_slack\) para poder satisfacer incluso el caso de \(x=1, y=1\), y \(z=7\).
Veamos cómo funciona el método interpret
. El propósito de este método es convertir la solución del problema convertido a la del problema original. Para utilizar este método, primero necesitaríamos resolver el problema. Para los fines de este tutorial, usaremos docplex para resolverlo. Primero traduciremos el problema cuadrático a un modelo docplex.mp.
[5]:
from qiskit_optimization.algorithms import CplexOptimizer
cplex_optimizer = CplexOptimizer()
[6]:
result_orig = cplex_optimizer.solve(qp)
print(result_orig)
fval=8.0, x=1.0, y=1.0, z=3.0, status=SUCCESS
[7]:
result_eq = cplex_optimizer.solve(qp_eq)
print(result_eq)
fval=8.0, x=1.0, y=1.0, z=3.0, xyz_leq@int_slack=0.0, xyz_geq@int_slack=2.0, status=SUCCESS
El resultado result_eq
de qp_eq
tiene 5 valores variables (x=1.0, y=1.0, z=3.0, xyz_leq@int_slack=0.0, xyz_geq@int_slack=2.0
) mientras que el resultado result_orig
del qp
original tiene tres valores (x=1.0, y=1.0, z=3.0
). Podemos llamar al método InequalityToEquality.interpret
pasando una lista o una matriz al método que tiene valores de qp_eq.variables
de la siguiente manera. result_eq.x
tiene la lista de valores que toma cada variable en la solución en correspondencia con su posición en la lista de variables qp_eq.variables
.
[8]:
print("interpreting values of result_eq:", ineq2eq.interpret(result_eq.x))
print("values of result_orig:", result_orig.x)
interpreting values of result_eq: [1. 1. 3.]
values of result_orig: [1. 1. 3.]
Notamos que \([1., 1., 3.]\) son los valores tomados en la solución del problema convertido para las variables comunes entre los problemas convertidos y originales (variables: \(x\), \(y\), \(z\)). El método de interpretación muestra que los mismos valores son la solución del problema original. Esto se debe a que la función objetivo para los problemas originales y convertidos es exactamente la misma. Las variables de holgura (slack) solo garantizan la igualdad en las restricciones del problema convertido, donde las restricciones también son exactamente iguales entre los problemas original y convertido, excepto que el problema original tiene restricciones de desigualdad.
IntegerToBinary#
IntegerToBinary
convierte variables enteras en variables binarias y coeficientes para eliminar las variables enteras del QuadraticProgram
. Para convertir, se usa la codificación de coeficiente acotado propuesta en arxiv:1706.01945 (Eq. (5)). Para obtener más detalles sobre el método de codificación, consulta el documento dado.
Usamos el resultado de InequalityToEquality
como punto de partida. Las variables \(x\) e \(y\) son variables binarias, mientras que la variable \(z\) y las variables de holgura \(xyz\_leq\text{@}int\_slack\) y \(xyz\_geq\text{@}int\_slack\) son variables enteras. Imprimimos el problema nuevamente como referencia.
[9]:
print(qp_eq.prettyprint())
Problem name:
Maximize
3*x + 2*y + z
Subject to
Linear constraints (2)
x + xyz_leq@int_slack + y + z == 5 'xyz_leq'
x - xyz_geq@int_slack + y + z == 3 'xyz_geq'
Integer variables (3)
0 <= z <= 7
0 <= xyz_leq@int_slack <= 5
0 <= xyz_geq@int_slack <= 6
Binary variables (2)
x y
Llama al método convert
de IntegerToBinary
para convertirlo.
[10]:
from qiskit_optimization.converters import IntegerToBinary
[11]:
int2bin = IntegerToBinary()
qp_eq_bin = int2bin.convert(qp_eq)
print(qp_eq_bin.prettyprint())
Problem name:
Maximize
3*x + 2*y + z@0 + 2*z@1 + 4*z@2
Subject to
Linear constraints (2)
x + xyz_leq@int_slack@0 + 2*xyz_leq@int_slack@1 + 2*xyz_leq@int_slack@2 + y
+ z@0 + 2*z@1 + 4*z@2 == 5 'xyz_leq'
x - xyz_geq@int_slack@0 - 2*xyz_geq@int_slack@1 - 3*xyz_geq@int_slack@2 + y
+ z@0 + 2*z@1 + 4*z@2 == 3 'xyz_geq'
Binary variables (11)
x y z@0 z@1 z@2 xyz_leq@int_slack@0 xyz_leq@int_slack@1 xyz_leq@int_slack@2
xyz_geq@int_slack@0 xyz_geq@int_slack@1 xyz_geq@int_slack@2
Después de la conversión, la variable entera \(z\) se reemplaza con tres variables binarias \(z\text{@}0\), \(z\text{@}1\) y \(z\text{@}2\) con los coeficientes 1, 2 y 4, respectivamente, como los anteriores. Las variables de holgura \(xyz\_leq\text{@}int\_slack\) y \(xyz\_geq\text{@}int\_slack\) que fueron introducidas por InequalityToEquality
también se reemplazan por tres variables binarias con los coeficientes 1, 2, 2 y 1, 2, 3, respectivamente.
Nota: En esencia, los coeficientes significan que la suma de estas variables binarias con coeficientes puede ser la suma de un subconjunto de \(\{1, 2, 4\}\), \(\{1, 2, 2\}\), y \(\{1, 2, 3\}\) para representar los valores aceptables \(\{0, \ldots, 7\}\), \(\{0, \ldots, 5\}\), y \(\{0, \ldots, 6\}\), que respeta correctamente el límite inferior y el límite superior de las variables enteras originales.
IntegerToBinary
también provee el método interpret
que es la funcionalidad para traducir un resultado binario dado de nuevo a la representación original. Veamos cómo funciona el método interpret
.
[12]:
result_eq = cplex_optimizer.solve(qp_eq)
print(result_eq)
fval=8.0, x=1.0, y=1.0, z=3.0, xyz_leq@int_slack=0.0, xyz_geq@int_slack=2.0, status=SUCCESS
[13]:
result_eq_bin = cplex_optimizer.solve(qp_eq_bin)
print(result_eq_bin)
fval=8.0, x=1.0, y=1.0, z@0=1.0, z@1=1.0, z@2=0.0, xyz_leq@int_slack@0=0.0, xyz_leq@int_slack@1=0.0, xyz_leq@int_slack@2=0.0, xyz_geq@int_slack@0=0.0, xyz_geq@int_slack@1=1.0, xyz_geq@int_slack@2=0.0, status=SUCCESS
result_eq_bin
tiene más variables binarias debido a la conversión mediante IntegerToBinary.convert
. IntegerToBinary.interpret
los traduce nuevamente a valores enteros agregando valores de variables binarias asociadas con las variables enteras originales de qp_eq
.
[14]:
print("interpreting values of result_eq_bin:", int2bin.interpret(result_eq_bin.x))
print("values of result_eq:", result_eq.x)
interpreting values of result_eq_bin: [1. 1. 3. 0. 2.]
values of result_eq: [1. 1. 3. 0. 2.]
LineEqualityToPenalty#
LineeEqualityToPenalty
convierte las restricciones de igualdad lineal en términos de penalización cuadráticos adicionales de la función objetivo para mapear el QuadraticProgram
a una forma sin restricciones. Una entrada al convertidor tiene que ser un QuadraticProgram
con sólo restricciones lineales de igualdad. Estas restricciones de igualdad, por ejemplo, \(\sum_i a_i x_i = b\) donde \(a_i\) y \(b\) son números y \(x_i\) es una variable, se agregará a la función objetivo en la forma de \(M(b - \sum_i a_i x_i)^2\) donde \(M\) es un número grande como factor de penalización. Por defecto, \(M= 1e5\). El signo del término depende de si el tipo de problema es de maximización o minimización.
Utilizamos la salida de IntegerToBinary
como punto de partida, donde todas las variables son variables binarias y todas las restricciones de desigualdad se han mapeado con restricciones de igualdad. Volvemos a imprimir el problema como referencia.
[15]:
print(qp_eq_bin.prettyprint())
Problem name:
Maximize
3*x + 2*y + z@0 + 2*z@1 + 4*z@2
Subject to
Linear constraints (2)
x + xyz_leq@int_slack@0 + 2*xyz_leq@int_slack@1 + 2*xyz_leq@int_slack@2 + y
+ z@0 + 2*z@1 + 4*z@2 == 5 'xyz_leq'
x - xyz_geq@int_slack@0 - 2*xyz_geq@int_slack@1 - 3*xyz_geq@int_slack@2 + y
+ z@0 + 2*z@1 + 4*z@2 == 3 'xyz_geq'
Binary variables (11)
x y z@0 z@1 z@2 xyz_leq@int_slack@0 xyz_leq@int_slack@1 xyz_leq@int_slack@2
xyz_geq@int_slack@0 xyz_geq@int_slack@1 xyz_geq@int_slack@2
Llama al método convert
de LinearEqualityToPenalty
para convertirlo.
[16]:
from qiskit_optimization.converters import LinearEqualityToPenalty
[17]:
lineq2penalty = LinearEqualityToPenalty()
qubo = lineq2penalty.convert(qp_eq_bin)
print(qubo.prettyprint())
Problem name:
Maximize
-26*x^2 + 26*x*xyz_geq@int_slack@0 + 52*x*xyz_geq@int_slack@1
+ 78*x*xyz_geq@int_slack@2 - 26*x*xyz_leq@int_slack@0
- 52*x*xyz_leq@int_slack@1 - 52*x*xyz_leq@int_slack@2 - 52*x*y - 52*x*z@0
- 104*x*z@1 - 208*x*z@2 - 13*xyz_geq@int_slack@0^2
- 52*xyz_geq@int_slack@0*xyz_geq@int_slack@1
- 78*xyz_geq@int_slack@0*xyz_geq@int_slack@2 - 52*xyz_geq@int_slack@1^2
- 156*xyz_geq@int_slack@1*xyz_geq@int_slack@2 - 117*xyz_geq@int_slack@2^2
- 13*xyz_leq@int_slack@0^2 - 52*xyz_leq@int_slack@0*xyz_leq@int_slack@1
- 52*xyz_leq@int_slack@0*xyz_leq@int_slack@2 - 52*xyz_leq@int_slack@1^2
- 104*xyz_leq@int_slack@1*xyz_leq@int_slack@2 - 52*xyz_leq@int_slack@2^2
+ 26*y*xyz_geq@int_slack@0 + 52*y*xyz_geq@int_slack@1
+ 78*y*xyz_geq@int_slack@2 - 26*y*xyz_leq@int_slack@0
- 52*y*xyz_leq@int_slack@1 - 52*y*xyz_leq@int_slack@2 - 26*y^2 - 52*y*z@0
- 104*y*z@1 - 208*y*z@2 + 26*z@0*xyz_geq@int_slack@0
+ 52*z@0*xyz_geq@int_slack@1 + 78*z@0*xyz_geq@int_slack@2
- 26*z@0*xyz_leq@int_slack@0 - 52*z@0*xyz_leq@int_slack@1
- 52*z@0*xyz_leq@int_slack@2 - 26*z@0^2 - 104*z@0*z@1 - 208*z@0*z@2
+ 52*z@1*xyz_geq@int_slack@0 + 104*z@1*xyz_geq@int_slack@1
+ 156*z@1*xyz_geq@int_slack@2 - 52*z@1*xyz_leq@int_slack@0
- 104*z@1*xyz_leq@int_slack@1 - 104*z@1*xyz_leq@int_slack@2 - 104*z@1^2
- 416*z@1*z@2 + 104*z@2*xyz_geq@int_slack@0 + 208*z@2*xyz_geq@int_slack@1
+ 312*z@2*xyz_geq@int_slack@2 - 104*z@2*xyz_leq@int_slack@0
- 208*z@2*xyz_leq@int_slack@1 - 208*z@2*xyz_leq@int_slack@2 - 416*z@2^2
+ 211*x - 78*xyz_geq@int_slack@0 - 156*xyz_geq@int_slack@1
- 234*xyz_geq@int_slack@2 + 130*xyz_leq@int_slack@0 + 260*xyz_leq@int_slack@1
+ 260*xyz_leq@int_slack@2 + 210*y + 209*z@0 + 418*z@1 + 836*z@2 - 442
Subject to
No constraints
Binary variables (11)
x y z@0 z@1 z@2 xyz_leq@int_slack@0 xyz_leq@int_slack@1 xyz_leq@int_slack@2
xyz_geq@int_slack@0 xyz_geq@int_slack@1 xyz_geq@int_slack@2
Después de la conversión, las restricciones de igualdad se añaden a la función objetivo como términos adicionales con el factor de penalización predeterminado provisto por Qiskit Optimization. El problema resultante es ahora un QUBO y es compatible con muchos algoritmos de optimización cuántica como VQE, QAOA y otros.
Esto da el mismo resultado que antes.
Como hicimos con los otros convertidores, veamos cómo funciona el método interpret
en este caso.
[18]:
result_eq_bin = cplex_optimizer.solve(qp_eq_bin)
print(result_eq_bin)
fval=8.0, x=1.0, y=1.0, z@0=1.0, z@1=1.0, z@2=0.0, xyz_leq@int_slack@0=0.0, xyz_leq@int_slack@1=0.0, xyz_leq@int_slack@2=0.0, xyz_geq@int_slack@0=0.0, xyz_geq@int_slack@1=1.0, xyz_geq@int_slack@2=0.0, status=SUCCESS
[19]:
result_qubo = cplex_optimizer.solve(qubo)
print(result_qubo)
fval=8.0, x=1.0, y=1.0, z@0=1.0, z@1=1.0, z@2=0.0, xyz_leq@int_slack@0=0.0, xyz_leq@int_slack@1=0.0, xyz_leq@int_slack@2=0.0, xyz_geq@int_slack@0=0.0, xyz_geq@int_slack@1=1.0, xyz_geq@int_slack@2=0.0, status=SUCCESS
[20]:
print("interpreting values of result_eq_bin:", lineq2penalty.interpret(result_qubo.x))
print("values of result_eq_bin:", result_eq_bin.x)
interpreting values of result_eq_bin: [1. 1. 1. 1. 0. 0. 0. 0. 0. 1. 0.]
values of result_eq_bin: [1. 1. 1. 1. 0. 0. 0. 0. 0. 1. 0.]
Podemos ver que el resultado del método interpret
implica que tanto el problema original como el convertido tienen exactamente la misma solución. Esto es de esperarse porque el problema convertido tiene exactamente las mismas variables que el problema original, el objetivo se ha modificado de tal manera que ya no tenemos las restricciones en el problema convertido.
Finalmente, veamos cómo interpretamos el resultado de QUBO hasta la solución del problema original qp
. El siguiente código muestra que los valores interpretados son equivalentes al resultado del problema original qp
.
[21]:
print("result_orig.x", result_orig.x)
x = result_qubo.x
for conv in [lineq2penalty, int2bin, ineq2eq]:
x = conv.interpret(x)
print("interpreting result_qubo.x", x)
result_orig.x [1. 1. 3.]
interpreting result_qubo.x [1. 1. 3.]
[22]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Software | Version |
---|---|
qiskit | 0.44.1 |
qiskit-terra | 0.45.0.dev0+dcec79e |
qiskit_optimization | 0.6.0 |
qiskit_algorithms | 0.3.0 |
System information | |
Python version | 3.10.13 |
Python compiler | Clang 14.0.3 (clang-1403.0.22.14.1) |
Python build | main, Aug 24 2023 22:36:46 |
OS | Darwin |
CPUs | 10 |
Memory (Gb) | 64.0 |
Thu Sep 07 12:33:31 2023 JST |
This code is a part of Qiskit
© Copyright IBM 2017, 2023.
This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.
[ ]: