| Crates.io | lynxql |
| lib.rs | lynxql |
| version | 0.1.3 |
| created_at | 2025-06-24 14:36:47.838637+00 |
| updated_at | 2025-07-23 08:27:10.715007+00 |
| description | A parser for the Lynx declarative modeling language - a statically typed language for expressing combinatorial optimization problems |
| homepage | https://github.com/lynx-lang/lynxql |
| repository | https://github.com/lynx-lang/lynxql |
| max_upload_size | |
| id | 1724406 |
| size | 106,983 |
Lynx lets you model MILP/SAT-style problems with just a few orthogonal
concepts: primitive values, type definitions, five logical combinators,
and a single optimisation call. Everything you write is guaranteed to
compile to a linear/MILP model; if you accidentally introduce
non-linear logic, Lynx aborts at compile-time.
// single-line comment
/*
multi
line
comment
*/
| Kind | Syntax | Notes |
|---|---|---|
| Boolean decision | Bool() |
Built‑in 0 / 1 variable; shorthand: x = 0..1 is equivalent to x = Bool() |
| Bounded integer | Integer(min, max) |
Decision ∈ [min,max]. |
| Named range | alias Week = Integer<1,52> |
Pure type synonym. |
| Sub-range variable | Week(lo, hi) |
Bounds tightened at declaration time. |
| Protected bounds | every var exposeslower,upper |
After solve:lower == upper. |
Before the syntax, here is what the example domain means:
tag so we can refer to colours symbolically.tag (e.g. front, back) and weight. Weight is numeric so we can penalise heavy wheels later.All; it is true only if both of its children are true (colors, wheel). It holds a single scalar attribute price. Modellers can hang an unbounded set of Price nodes under a bicycle, one per colour–wheel combination.All, meaning the bike exists iff its colors and wheels rules are satisfied. Extra attributes:
@totalPrice — linear aggregate of all active Price nodes; safe for the MILP objective.@heavyPen — linear penalty based on any wheel weighing > 3 kg.@nlMetric — a deliberately non‑linear attribute, computed after the solve for reporting.With the semantics clear, here’s how that maps to Lynx syntax:
// leaf types
type Color : Bool {
@tag : str,
}
type Wheel : Bool {
@tag : str,
@weight : float,
}
type Price : All {
colors : Any[Color],
wheel : Wheel,
@price : float,
}
// composite type
type Bicycle : All {
colors : ColorRule,
wheels : WheelRule,
prices : Free[Price],
@totalPrice : float = (Bicycle b) -> sum(b.prices.price),
@heavyPen : float = (Bicycle b) -> 50 * Any(filter(b.wheels, w -> w.weight > 3.0)),
@nlMetric : float = (Bicycle b) -> sum(b.prices.price) - 1.2 * b.heavyPen,
}
type declares either a leaf (Bool / Integer) or a composite(logical operator after the colon).@; youaccess them via normal dot notation (wheel.weight).a literal
a lambda that resolves to a constant at call-time (compile-time
for attributes, run-time for relationships).
| Name | Set form | Variadic form | Meaning |
|---|---|---|---|
All |
All[S] |
All(x1,…,xn) |
AND – true iffeverychild true |
Any |
Any[S] |
Any(x1,…,xn) |
OR – true iffat least onechild true |
Not |
Not[S] |
Not(x1,…,xn) |
NOR – true iffallchildren false |
Exactly<k> |
Exactly<k>[S] |
Exactly<k>(x)⇔x == k |
Cardinality / equality |
AtMost<k> |
AtMost<k>[S] |
AtMost<k>(x) |
≤ k |
AtLeast<k> |
AtLeast<k>[S] |
AtLeast<k>(x) |
≥ k |
Free |
Free[S] |
Free(x1,…,xn) |
Children areunconstrained; truth values propagate as-is |
Each call returns a Boolean variable. Inside suchThat you simply provide the expression; Lynx implicitly locks it to true (1). You never write == 1; inside
suchThat Lynx implicitly forces the given Boolean to true.
| Helper | Purpose | Allowed operators |
|---|---|---|
filter(context, λ) |
static subset;contextmay be*(root) or any set expression |
&&, ` |
firstTrue() |
on any set; returns the first member whose solved value is true | – |
A lambda is any (param) -> expr expression. Inside filter it must
be static (no decision values). Inside attributes it may reference
other attributes but must stay linear if the attribute itself is used
in objective or suchThat.
......expr unpacks a static set (or list) into positional arguments when calling a constructor. Common use-case: turning the result of filter into the child list expected by Any, All, Exactly, or a rule constructor.
reds = filter(*, c -> c.tag == "red")
rule = ColorRule(...reds) // same as ColorRule(red1, red2, …)
The operator works only on compile-time contexts (e.g., inside constructor calls or in default field values) and is a no-op after expansion: the compiler replaces it with the individual elements.
If an @attribute is referenced in the optimisation layer and the
compiler cannot linearise the expression, Lynx aborts with a clear error
message. Non-linear attributes are still computed post-solve for
reporting.
solution = minimize(
objective = <scalar expr>, // MILP-safe
suchThat = <Boolean expr>, // implicitly = true
options = { timeLimit = 300 } // optional
)
maximize mirrors minimize.Not( … ) to force false.bicycle = myBike) create local
references for readability (identical to using the expression
directly).// leaf types
type Color : Bool { @tag : str, }
type Wheel : Bool { @tag : str, @weight : float }
type Price : All {
colors : Any[Color],
wheel : Wheel,
@price : float,
}
alias ColorRule = Exactly<1>[Color]
alias WheelRule = Exactly<1>[Wheel]
// composite
type Bicycle : All {
colors : ColorRule,
wheels : WheelRule,
prices : Free[Price],
@totalPrice : float = (Bicycle b) -> sum(b.prices.price),
}
// data
red = Color(tag = "red")
front = Wheel(tag = "front", weight = 3.2)
price1 = Price(colors = Any(red), wheel = front, price = 150)
// compile-time sets
aRed = filter(*, c -> c.tag == "red")
frontW = filter(*, w -> w.tag == "front")
myBike = Bicycle(
colors = ColorRule(...aRed),
wheels = WheelRule(...frontW),
prices = Free(price1),
)
alias Week = Integer<1,52>
productionWeek = Week(4,32)
solution = minimize(
objective = myBike.totalPrice,
suchThat = All(
myBike,
Exactly<12>(productionWeek),
Has(frontW.tag, "front"),
),
)
chosenColour = solution.colors.firstTrue()
The solver returns a new, solved instance of Bicycle; you drill down
through dot access, firstTrue(), or filter(..., v -> v.lower == 1) to
inspect any decision.
Enjoy modelling with Lynx—minimal syntax, maximal clarity.