Expresiones, funciones y comparaciones

Expresiones

Las variables se pueden combinar algebraicamente con los operadores habituales de Python (+, -, *, /, **) para construir una Expression simbólica. Cada hoja (una variable, un parámetro o una constante numérica) y cada nodo operador (Add, Mul, Pow) es en sí mismo una Expression, de modo que las expresiones se componen libremente. Por ejemplo:

from qilisdk.core.variables import BinaryVariable, Bitwise, Domain, SpinVariable, Variable
x = Variable("x", domain=Domain.REAL, bounds=(1, 2), encoding=Bitwise, precision=1e-1)
s = SpinVariable("s")
b = BinaryVariable("b")

e1 = 2 * x + 3
print("e1:", e1)
e2 = 3 * x**2 + 2 * x + 4
print("e2:", e2)
e3 = 2 * x + b - 1
print("e3:", e3)
e4 = e1 - e2
print("e4:", e4)

Salida:

e1: 3 + 2 * x
e2: 4 + 2 * x + 3 * x**2
e3: -1 + b + 2 * x
e4: 3 - (4 + 2 * x + 3 * x**2) + 2 * x

En el momento de la construcción canonizamos la expresión (aplanando sumas y productos anidados, combinando términos semejantes, ordenando los operandos, etc.), de modo que x + y + 1 es igual a y + 1 + x. Los productos no se distribuyen sobre las sumas, así que e4 conserva la subexpresión factorizada - (4 + 2 * x + 3 * x**2). Use expand() para distribuir:

print(e4.expand())          # distribute the product over the sum

Salida:

-1 - 3 * x**2

Las expresiones se pueden evaluar proporcionando valores para las variables implicadas mediante evaluate():

e3.evaluate({
    x: 1.5,
    b: 0
})

Salida:

2.0

Advertencia

Para evaluar una expresión, a todas las variables participantes se les deben asignar valores válidos dentro de sus dominios y límites respectivos.

Inspeccionar y derivar expresiones

Una Expression expone una serie de funciones auxiliares. Puede listar las hojas con nombre de las que depende, aislar únicamente las hojas Parameter libres, leer su degree polinómico o calcular una derivada simbólica con derivative():

from qilisdk.core.variables import Parameter, Variable, Domain

a = Parameter("a", value=2.0)
y = Variable("y", domain=Domain.REAL, bounds=(0, 5))

expr = a * y**2 + 3 * y

print(expr.variables())          # named leaves, sorted by label
print(expr.free_parameters())    # only the Parameter leaves
print(expr.degree)               # highest polynomial degree (a and y both count)
print(expr.derivative(y))              # symbolic d/dy

Salida:

[a, y]
{a}
3
3 + 2 * a * y

Funciones matemáticas

Las operaciones no polinómicas se representan con Function, la base abstracta de las funciones matemáticas unarias. Cada una de sus subclases concretas (listadas más abajo) envuelve un único operando Expression (un Parameter, cualquier otra variable o una expresión compuesta) y aplaza la evaluación numérica hasta que se proporcionan valores.

from qilisdk.core.expression import Cos, Sin
from qilisdk.core.variables import Parameter

theta = Parameter("theta", 0.5)
expr = Sin(theta) + Cos(2 * theta)

print(expr)                # Cos(2 * theta) + Sin(theta)
print(expr.evaluate({}))   # uses theta.value automatically

# You can also supply a different value at evaluation time:
print(expr.evaluate({theta: 1.0}))

Salida:

Cos(2 * theta) + Sin(theta)
1.0197278444723428
0.4253241482607541

Como cada función es un nodo Expression normal, participa en la misma álgebra: se puede sumar o multiplicar con otras expresiones, derivar simbólicamente (la regla de la cadena se aplica automáticamente) y evaluar. Envolver una constante numérica se pliega de inmediato a una Constant:

from qilisdk.core.expression import Cos, Exp, Sin
from qilisdk.core.variables import Parameter

theta = Parameter("theta", 0.5)

print(Sin(theta).derivative(theta))   # d/dtheta Sin(theta) == Cos(theta)
print(Exp(theta).derivative(theta))   # d/dtheta Exp(theta) == Exp(theta)
print(Cos(0))                   # folds to a numeric constant

Salida:

Cos(theta)
Exp(theta)
1.0

Estas funciones se componen de forma natural con el resto del árbol de expresiones, por lo que se pueden incluir en restricciones, objetivos o coeficientes de schedules y se rigen por las mismas reglas de evaluación y codificación que cualquier otra expresión simbólica.

Los nodos de función disponibles son:

  • Sin para el seno

  • Cos para el coseno

  • Tan para la tangente

  • Exp para la exponencial

  • Log para el logaritmo

  • Sqrt para la raíz cuadrada

  • Abs para el valor absoluto

Abs es la única función sin derivada: no es derivable en cero y no existe un nodo sign con el que escribir su derivada, de modo que derivative() lanza un error sobre ella.

Para las potencias, use el operador **, que construye un nodo Pow y admite un exponente fraccionario o simbólico. Inv() es una abreviatura de x ** -1, así que Inv(x), 1 / x y x ** -1 son todas la misma expresión:

from qilisdk.core.expression import Inv
from qilisdk.core.variables import Parameter

x, y = Parameter("x", 4.0), Parameter("y", 0.5)

print(x**y)                       # symbolic exponent
print((x**y).evaluate({}))
print(Inv(x) == 1 / x == x**-1)

Salida:

x**y
2.0
True

Para escribir su propia función, cree una subclase de Function con un NAME, un núcleo numérico y una derivada. Todo lo demás (canonización, igualdad, derivative, expand, substitute, serialización) lo proporciona la clase base.

Comparaciones

Una Comparison relaciona dos expresiones y es aquello a partir de lo cual se construye una Constraint. No es una Expression en sí misma. Use los siguientes operadores para construir una:

Operación de comparación

Método QiliSDK

Alias

Igualdad

Equal(lhs, rhs)

EQ(lhs, rhs)

Distinto de

NotEqual(lhs, rhs)

NEQ(lhs, rhs)

Menor que

LessThan(lhs, rhs)

LT(lhs, rhs)

Menor o igual que

LessThanOrEqual(lhs, rhs)

LEQ(lhs, rhs)

Mayor que

GreaterThan(lhs, rhs)

GT(lhs, rhs)

Mayor o igual que

GreaterThanOrEqual(lhs, rhs)

GEQ(lhs, rhs)

Nota: lhs y rhs hacen referencia a las expresiones del lado izquierdo y del lado derecho, respectivamente.

Ejemplo:

from qilisdk.core.comparison import LT
from qilisdk.core.variables import BinaryVariable
x = BinaryVariable("x")
print(LT(2 * x - 1, 1))

Salida:

2 * x < 2

Cuando se crea un término de comparación, las constantes se mueven automáticamente al lado derecho y los términos con variables al lado izquierdo.