Skip to content

Working with Expressions

Expressions are the mathematical building blocks of optimization models in LunaModel. They represent combinations of variables, constants, and mathematical operations that form objectives and constraints.

Overview

LunaModel supports expressions of arbitrary degree:

  • Linear: Sum of variables with coefficients (e.g., 3x + 2y + 5)
  • Quadratic: Products of two variables (e.g., x*y + 2x² + y²)
  • Higher-Order: Products of three or more variables (e.g., x*y*z)

All expression types can be mixed in a single expression, giving you complete flexibility in modeling.

Creating Expressions

From Variables

The most common way to create expressions is through mathematical operations on variables:

Python
from luna_model import Model, Vtype

model = Model()
x = model.add_variable("x", vtype=Vtype.BINARY)
y = model.add_variable("y", vtype=Vtype.BINARY)
z = model.add_variable("z", vtype=Vtype.BINARY)

# Linear expression
linear = 3 * x + 2 * y + 5

# Quadratic expression
quadratic = x * y + 2 * x * x

# Higher-order expression
higher = x * y * z

Using quicksum

For creating sums with many terms, use quicksum() for better performance:

Python
from luna_model.utils import quicksum

# Inefficient (for large n)
expr = sum(coeffs[i] * vars[i] for i in range(n))

# Efficient
expr = quicksum(coeffs[i] * vars[i] for i in range(n))

# With starting value
expr = quicksum(terms, start=100)

Performance Tip: quicksum() is significantly faster than Python's built-in sum() or repeated + operations, especially for large models.

Expression Types

Linear Expressions

Linear expressions contain only first-degree terms:

Python
# Simple linear
expr = 3 * x + 2 * y

# With constant
expr = 3 * x + 2 * y + 10

# Many terms
n = 100
variables = [model.add_variable(f"x_{i}") for i in range(n)]
expr = quicksum(i * variables[i] for i in range(n))

Properties: - Degree: 1 - Form: c₀ + c₁x₁ + c₂x₂ + ... + cₙxₙ - Supported by all solvers

Quadratic Expressions

Quadratic expressions contain products of two variables:

Python
# Product terms
expr = x * y + 2 * x * z

# Quadratic form (x² + 2xy + y²)
expr = x * x + 2 * x * y + y * y

# Mixed linear and quadratic
expr = 3 * x + x * y + 2 * y

Properties: - Degree: 2 - Form includes terms like: xᵢxⱼ, xᵢ² - Supported by QUBO, BQM, and many classical solvers

Higher-Order Expressions

LunaModel supports polynomial expressions of arbitrary degree:

Python
# Cubic expression
expr = x * y * z

# Mixed degrees
expr = x * y * z + x * y + x + 1

# Even higher order
expr = w * x * y * z + w * x * y

Properties: - Degree: 3 or higher - Requires transformation to be solved by most solvers - Use transformations to reduce degree when needed

Expression Operations

Arithmetic Operations

Expressions support standard arithmetic operations:

Python
# Addition
expr1 = 2 * x + 3 * y
expr2 = x + y
combined = expr1 + expr2  # 3x + 4y

# Subtraction
diff = expr1 - expr2  # x + 2y

# Multiplication by scalar
scaled = 2 * expr1  # 4x + 6y
scaled = expr1 * 2  # Same result

# Negation
negated = -expr1  # -2x - 3y

# Division by scalar (multiplication by reciprocal)
halved = expr1 / 2  # x + 1.5y

Powers and Products

Python
# Squaring a variable
x_squared = x * x
x_squared = x**2  # Alternative syntax

# Variable products
product = x * y * z

# Expression multiplication creates higher-order terms
expr1 = x + y
expr2 = y + z
product = expr1 * expr2  # xy + xz + y² + yz

Comparison Operations

Expressions can be compared to create constraints:

Python
# Less than or equal
constraint = 3 * x + 2 * y <= 10

# Greater than or equal
constraint = x + y >= 5

# Equality
constraint = 2 * x + y == 7

Expression Properties

Degree

Get the degree of an expression:

Python
linear = 3 * x + 2 * y
print(linear.degree())  # 1

quadratic = x * y + 2 * x * x
print(quadratic.degree())  # 2

higher = x * y * z
print(higher.degree())  # 3

Variables

Extract variables used in an expression:

Python
expr = 3 * x + 2 * y + x * z

# Get all variables
variables = expr.variables()
print(variables)  # [x, y, z]

# Check if variable is present
if x in expr.variables():
    print("x is in the expression")

Evaluation

Evaluate expressions with specific variable values:

Python
from luna_model import Expression

expr = 3 * x + 2 * y + x * y

# Evaluate with dictionary
value = expr.evaluate({x.name: 1, y.name: 2})
print(value)  # 3(1) + 2(2) + 1(2) = 9

Building Complex Expressions

Incrementally

For large models, build expressions using quicksum:

Python
from luna_model.utils import quicksum

# Efficient: use quicksum
terms = [coeffs[i] * variables[i] for i in range(n)]
expr = quicksum(terms)

With Conditionals

Build expressions based on conditions:

Python
expr = quicksum(
    coeff * var
    for coeff, var in zip(coeffs, variables)
    if coeff > 0  # Only include positive coefficients
)

Nested Structures

Create complex expressions from simpler ones:

Python
# Create sub-expressions
shipping_cost = quicksum(costs[i, j] * flow[i, j] 
                        for i in origins 
                        for j in destinations)

inventory_cost = quicksum(holding[i] * inventory[i] 
                          for i in warehouses)

# Combine
total_cost = shipping_cost + inventory_cost

Expression Types Reference

LunaModel creates appropriate expression types automatically based on operations performed on variables. The degree of an expression indicates its type:

  • Degree 0: Constant value
  • Degree 1: Linear expression
  • Degree 2: Quadratic expression
  • Degree 3+: Higher-order polynomial expression

You typically don't need to worry about specific expression classes - they are created automatically when you perform operations on variables.

Common Patterns

Objective Functions

Python
# Minimize distance
model.objective = quicksum(
    distances[i, j] * routes[i, j]
    for i in cities
    for j in cities
)

# Maximize profit
model.objective = quicksum(
    prices[i] * quantities[i] - costs[i] * quantities[i]
    for i in products
)

Constraint Expressions

Python
# Capacity constraint
model.constraints += quicksum(
    sizes[i] * items[i] for i in range(n)
) <= capacity

# Balance constraint
model.constraints += quicksum(
    incoming[i] for i in inputs
) == quicksum(
    outgoing[j] for j in outputs
)

Quadratic Objectives

Python
# Portfolio variance minimization
model.objective = quicksum(
    covariance[i, j] * allocations[i] * allocations[j]
    for i in assets
    for j in assets
)

Best Practices

Performance

  1. Use quicksum() for multiple terms:

    Python
    # Good
    expr = quicksum(coeffs[i] * vars[i] for i in range(n))
    
    # Avoid
    expr = sum(coeffs[i] * vars[i] for i in range(n))
    

  2. Build expressions once, reuse when possible:

    Python
    # Good: build once
    total = quicksum(weights[i] * items[i] for i in range(n))
    model.constraints += total <= capacity
    
    # Avoid: rebuilding same expression
    model.constraints += quicksum(...) <= capacity
    

  3. Avoid deep nesting in loops:

    Python
    # Better: collect terms first
    terms = [coeff * var for coeff, var in zip(coeffs, vars)]
    expr = quicksum(terms)
    

Clarity

  1. Name sub-expressions for readability:

    Python
    revenue = quicksum(prices[i] * sales[i] for i in products)
    costs = quicksum(production_costs[i] * sales[i] for i in products)
    profit = revenue - costs
    model.objective = profit
    

  2. Break complex expressions into parts:

    Python
    # Good
    fixed_costs = quicksum(setup[i] * is_open[i] for i in facilities)
    variable_costs = quicksum(unit_cost[i] * quantity[i] for i in facilities)
    total_costs = fixed_costs + variable_costs
    
    # Less clear
    total_costs = quicksum(setup[i] * is_open[i] + unit_cost[i] * quantity[i] 
                          for i in facilities)
    

Type Safety

  1. Use consistent types in expressions:
    Python
    # All floats
    expr = 2.0 * x + 3.0 * y
    
    # Mixed types work but be aware
    expr = 2 * x + 3.0 * y  # 2 is int, 3.0 is float
    

Next Steps