Relational Constraints

๐ŸŽ“ Relational Constraints

We often want to write formulas โ€” sums, products, powers, or trigonometric functions โ€” to enforce them over variables, to find the right assignments. In Qaekwy, the building block for this is the Expression class. Expressions let you treat mathematical formulas as first-class constraints of your model: you can combine them, compare them, turn them into constraints, or even promote them to variables.


๐Ÿงฑ What Is an Expression?

An Expression is a symbolic mathematical formula built from:

  • Variables (integer or array variables),
  • Constants (like 3 or 10),
  • Mathematical / Boolean operators (+, -, *, /, &, |, etc.),
  • Expression functions (like sqr, sqrt, exp, sin, max, sum_of, โ€ฆ).

You can think of an Expression as a โ€œrecipeโ€ for computing a value that depends on the solverโ€™s decisions.

For example:

expr_1 = 2 * x + 3
expr_2 = power(y, 2) - 4
expr_3 = expr_1 - sum_of(arr)

Here:

  • expr_1 represents the formula 2ยทx + 3.
  • expr_2 represents the formula yยฒ โˆ’ 4.
  • expr_3 represents the formula (2ยทx + 3) โˆ’ โˆ‘ arr.

At solving time, whenever x and y are assigned, the solver knows what expr_1, expr_2, and expr_3 evaluate to.


๐Ÿงฎ Arithmetic with Expressions

Expressions are composable. You can add, subtract, multiply, or combine them in more complex ways:

result_expr = expr_1 + expr_2
result_expr = expr_1 * 2 - expr_2

Conceptually, this works like symbolic algebra: Qaekwy tracks the operations to build a structured formula tree.


๐Ÿ”Ž Relational Expressions: Turning Formulas into Constraints

On their own, expressions are just formulas. To constrain them, you must compare them. This is done using Pythonโ€™s comparison operators:

bool_expr = expr_1 >= expr_2
bool_expr = expr_1 == 0

Each comparison produces a relational expression (a Boolean formula). To actually enforce it in the model, you wrap it into a RelationalExpression constraint:

my_model.constraint(expr_1 >= expr_2)

This way, expr_1 โ‰ฅ expr_2 becomes a logical rule that the solver must respect during search.


๐Ÿ“š Expression Functions

Qaekwy provides a collection of mathematical functions that operate on expressions. They extend the expressiveness of your models beyond simple linear formulas.

Aggregation

  • maximum(expr_array) โ†’ maximum of an array of expressions.
  • minimum(expr_array) โ†’ minimum of an array of expressions.
  • sum_of(expr_array) โ†’ sum of an array of expressions.

Arithmetic transformations

  • absolute(expr) โ†’ |expr|.
  • sqr(expr) โ†’ exprยฒ.
  • power(expr, val) โ†’ expr^val.
  • nroot(expr, val) โ†’ val-th root of expr.
  • sqrt(expr) โ†’ โˆšexpr.

Trigonometry

  • sin(expr), cos(expr), tan(expr) โ†’ trigonometric functions.
  • asin(expr), acos(expr), atan(expr) โ†’ inverse trigonometric functions.

Exponential & logarithm

  • exp(expr) โ†’ e^expr.
  • log(expr) โ†’ ln(expr).

These allow you to model highly nonlinear phenomena โ€” growth, oscillations, angles, penalties โ€” directly in your CSP or optimization problem.


๐ŸŽจ Example 1: Nonlinear Constraints with Expressions

import qaekwy as qw

# Instantiate the model
m = qw.Model()

# Create variables
# Domain is now passed as a simple tuple (low, high)
x = m.integer_variable(name="x", domain=(0, 100))
y = m.integer_variable(name="y", domain=(0, 100))
z = m.integer_variable(name="z", domain=(0, 100))

# Create an array of variables
# The new API handles arrays cleanly via list comprehensions or built-in helpers
arr = [
    m.integer_variable(name=f"arr_{i}", domain=(0, 100)) 
    for i in range(3)
]

# Add constraints using direct mathematical expressions
# Functions like square, exp, and power are now part of the model/expression logic
m.constraint(arr[0] > x + 1)

# Power and squaring
m.constraint(qw.math.power(y, 2) < arr[1] * z)

# Complex expressions remain readable and don't require manual variable registration
m.constraint(qw.math.exp(arr[2]) + qw.math.power(z, 3) >= arr[0] + arr[1])

Here:

  • The first constraint ensures arr[0] > x + 1.
  • The second constraint encodes yยฒ < arr[1]ยทz.
  • The third constraint mixes exponential and polynomial terms.

๐ŸŽฏ Example 2: Expressions as Variables (Objectives)

Expressions can also be turned into variables via IntegerExpressionVariable. This is useful when:

  • You want to monitor a complex formula,
  • Or you want to optimize it (maximize/minimize).
import qaekwy as qw

# Instantiate the model
m = qw.Model()

# Variables
# Simplified variable creation with tuple-based domains
x = m.integer_variable(name="x", domain=(0, 100))
y = m.integer_variable(name="y", domain=(0, 100))
z = m.integer_variable(name="z", domain=(0, 100))

# Complex nonlinear expression promoted to a variable in one step
objective_var = m.integer_variable(
    name="objective_variable",
    expression=(qw.math.power(x, 2) + qw.math.power(y, 3)) * z + x * y - 10
)

# Adding a constraint directly using the variable
m.constraint(objective_var < 100)

# Set the objective: Maximize the variable
m.maximize(objective_var)

Here:

  • The nonlinear expression becomes a variable that the solver tracks.
  • We add a constraint (objective_variable < 100).
  • We set an objective: maximize objective_variable.

โšก Why Expressions Are Powerful

  1. Conciseness: Instead of building dozens of primitive constraints, you can write natural mathematical formulas.
  2. Flexibility: Expressions let you describe nonlinear, trigonometric, or exponential relationships.
  3. Reusability: An expression can be used in multiple constraints or as an optimization objective.
  4. Integration: Expressions play well with reification, branching, and global constraints.

๐Ÿ“Œ Takeaway

  • In Qaekwy, Expressions are symbolic formulas that extend the modeling language.
  • They can be combined arithmetically, compared relationally, and transformed with a wide range of functions.
  • Relational expressions turn formulas into constraints.
  • Expression variables let you optimize formulas directly.
  • Together, they make complex constraint models natural to write and efficient to solve.
  • important: Expressions must contain variables of the same type (e.g., all integer). Mixing types (integer with float) is not supported.