๐ 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
3or10), - 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_1represents the formula 2ยทx + 3.expr_2represents the formula yยฒ โ 4.expr_3represents 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
- Conciseness: Instead of building dozens of primitive constraints, you can write natural mathematical formulas.
- Flexibility: Expressions let you describe nonlinear, trigonometric, or exponential relationships.
- Reusability: An expression can be used in multiple constraints or as an optimization objective.
- 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.