A fast implementation of the Cassowary constraint solver
npx @tessl/cli install tessl/pypi-kiwisolver@1.4.00
# Kiwisolver
1
2
A fast implementation of the Cassowary constraint solver providing an efficient C++ implementation with hand-rolled Python bindings. Kiwisolver enables developers to solve linear constraint systems for GUI layout management, mathematical optimization problems, and user interface positioning with performance improvements of 10x to 500x over the original Cassowary solver.
3
4
## Package Information
5
6
- **Package Name**: kiwisolver
7
- **Language**: Python
8
- **Installation**: `pip install kiwisolver`
9
- **Python Version**: 3.10+
10
11
## Core Imports
12
13
```python
14
import kiwisolver
15
```
16
17
Common imports for constraint solving:
18
19
```python
20
from kiwisolver import Variable, Solver, Constraint, Expression, Term, strength
21
```
22
23
Exception handling imports:
24
25
```python
26
from kiwisolver import (
27
UnsatisfiableConstraint,
28
DuplicateConstraint,
29
UnknownConstraint,
30
DuplicateEditVariable,
31
UnknownEditVariable,
32
BadRequiredStrength
33
)
34
```
35
36
Type annotation imports:
37
38
```python
39
from typing import Any, Iterable, Tuple, Literal
40
```
41
42
## Basic Usage
43
44
```python
45
from kiwisolver import Variable, Solver, strength
46
47
# Create variables for a simple layout problem
48
left = Variable("left")
49
width = Variable("width")
50
right = Variable("right")
51
52
# Create solver and add constraints
53
solver = Solver()
54
55
# Basic relationship: left + width = right
56
solver.addConstraint(left + width == right)
57
58
# Set some values with different strengths
59
solver.addConstraint(left >= 0) # Required strength (default)
60
solver.addConstraint(width >= 100 | strength.strong) # Strong preference
61
solver.addConstraint(right <= 500 | strength.medium) # Medium preference
62
63
# Solve the system
64
solver.updateVariables()
65
66
print(f"Left: {left.value()}, Width: {width.value()}, Right: {right.value()}")
67
```
68
69
## Architecture
70
71
Kiwisolver implements the Cassowary constraint solving algorithm through a hierarchy of mathematical components:
72
73
- **Variables**: Named unknowns that can be solved for
74
- **Terms**: Products of variables and coefficients (`coefficient * variable`)
75
- **Expressions**: Sums of terms plus constants (`term1 + term2 + ... + constant`)
76
- **Constraints**: Relationships between expressions with operators (`==`, `>=`, `<=`) and strengths
77
- **Solver**: The main engine that maintains and solves constraint systems
78
- **Strength System**: Hierarchical priority system for conflicting constraints (required > strong > medium > weak)
79
80
This design enables natural mathematical syntax through operator overloading while providing fine-grained control over constraint priorities and conflict resolution.
81
82
## Capabilities
83
84
### Variable Management
85
86
Create and manage constraint variables with optional naming and context for debugging and identification.
87
88
```python { .api }
89
class Variable:
90
def __init__(self, name: str = "", context: Any = None) -> None:
91
"""Create a new constraint variable.
92
93
Args:
94
name: Optional name for debugging
95
context: Optional context object for application use
96
"""
97
98
def name(self) -> str:
99
"""Get the variable name."""
100
101
def setName(self, name: str) -> None:
102
"""Set the variable name."""
103
104
def value(self) -> float:
105
"""Get the current solved value of the variable."""
106
107
def context(self) -> Any:
108
"""Get the context object associated with the variable."""
109
110
def setContext(self, context: Any) -> None:
111
"""Set the context object associated with the variable."""
112
```
113
114
### Mathematical Expression Building
115
116
Build mathematical expressions using terms (coefficient * variable combinations) and expressions (sums of terms).
117
118
```python { .api }
119
class Term:
120
def __init__(self, variable: Variable, coefficient: int | float = 1.0) -> None:
121
"""Create a term representing coefficient * variable.
122
123
Args:
124
variable: The variable to multiply
125
coefficient: Multiplication factor (default 1.0)
126
"""
127
128
def coefficient(self) -> float:
129
"""Get the coefficient for the term."""
130
131
def variable(self) -> Variable:
132
"""Get the variable for the term."""
133
134
def value(self) -> float:
135
"""Get the current evaluated value (coefficient * variable.value())."""
136
137
class Expression:
138
def __init__(self, terms: Iterable[Term], constant: int | float = 0.0) -> None:
139
"""Create an expression as sum of terms plus constant.
140
141
Args:
142
terms: Collection of Term objects to sum
143
constant: Constant value to add (default 0.0)
144
"""
145
146
def constant(self) -> float:
147
"""Get the constant term."""
148
149
def terms(self) -> Tuple[Term, ...]:
150
"""Get the tuple of terms in the expression."""
151
152
def value(self) -> float:
153
"""Get the current evaluated value of the entire expression."""
154
```
155
156
### Constraint Creation and Management
157
158
Create constraints from expressions using comparison operators and manage constraint strengths for conflict resolution.
159
160
```python { .api }
161
class Constraint:
162
def __init__(
163
self,
164
expression: Expression,
165
op: Literal["==", "<=", ">="],
166
strength: float | Literal["weak", "medium", "strong", "required"] = "required"
167
) -> None:
168
"""Create a constraint from an expression.
169
170
Args:
171
expression: Mathematical expression for the constraint
172
op: Comparison operator ("==", "<=", or ">=")
173
strength: Constraint strength (default "required")
174
"""
175
176
def expression(self) -> Expression:
177
"""Get the expression object for the constraint."""
178
179
def op(self) -> Literal["==", "<=", ">="]:
180
"""Get the relational operator for the constraint."""
181
182
def strength(self) -> float:
183
"""Get the numeric strength value."""
184
185
def violated(self) -> bool:
186
"""Indicate if the constraint is violated in the current state of the solver."""
187
```
188
189
### Constraint Solving
190
191
Main solver engine for managing and solving constraint systems with support for dynamic constraint modification.
192
193
```python { .api }
194
class Solver:
195
def __init__(self) -> None:
196
"""Create a new constraint solver."""
197
198
def addConstraint(self, constraint: Constraint) -> None:
199
"""Add a constraint to the solver.
200
201
Raises:
202
DuplicateConstraint: If constraint already exists
203
UnsatisfiableConstraint: If constraint cannot be satisfied
204
"""
205
206
def removeConstraint(self, constraint: Constraint) -> None:
207
"""Remove a constraint from the solver.
208
209
Raises:
210
UnknownConstraint: If constraint doesn't exist
211
"""
212
213
def hasConstraint(self, constraint: Constraint) -> bool:
214
"""Check if solver contains a specific constraint."""
215
216
def addEditVariable(
217
self,
218
variable: Variable,
219
strength: float | Literal["weak", "medium", "strong", "required"]
220
) -> None:
221
"""Add a variable that can be dynamically modified.
222
223
Args:
224
variable: Variable to make editable
225
strength: Strength for edit operations
226
227
Raises:
228
DuplicateEditVariable: If variable already added as edit variable
229
BadRequiredStrength: If required strength used for edit variable
230
"""
231
232
def removeEditVariable(self, variable: Variable) -> None:
233
"""Remove an edit variable from the solver.
234
235
Raises:
236
UnknownEditVariable: If variable is not an edit variable
237
"""
238
239
def hasEditVariable(self, variable: Variable) -> bool:
240
"""Check if variable is registered as an edit variable."""
241
242
def suggestValue(self, variable: Variable, value: int | float) -> None:
243
"""Suggest a desired value for an edit variable.
244
245
Args:
246
variable: Edit variable to modify
247
value: Suggested value
248
249
Raises:
250
UnknownEditVariable: If variable is not an edit variable
251
"""
252
253
def updateVariables(self) -> None:
254
"""Solve the constraint system and update all variable values."""
255
256
def reset(self) -> None:
257
"""Reset solver to empty state, removing all constraints and edit variables."""
258
259
def dump(self) -> None:
260
"""Print solver internal state to stdout for debugging."""
261
262
def dumps(self) -> str:
263
"""Return solver internal state as string for debugging."""
264
```
265
266
### Strength Management
267
268
Hierarchical constraint strength system for resolving conflicts between competing constraints. The `strength` object is a singleton instance providing predefined strength values and custom strength creation.
269
270
```python { .api }
271
# Singleton strength instance - do not instantiate directly
272
strength: Strength
273
274
# Predefined strength values (properties)
275
strength.weak: float # Weakest constraint strength
276
strength.medium: float # Medium constraint strength
277
strength.strong: float # Strong constraint strength
278
strength.required: float # Required constraint strength (highest priority)
279
280
def strength.create(
281
a: int | float,
282
b: int | float,
283
c: int | float,
284
weight: int | float = 1.0
285
) -> float:
286
"""Create custom constraint strength.
287
288
Args:
289
a, b, c: Strength hierarchy components
290
weight: Additional weight factor (default: 1.0)
291
292
Returns:
293
Custom strength value
294
"""
295
```
296
297
### Version Information
298
299
Package and library version information for compatibility checking.
300
301
```python { .api }
302
__version__: str # Python package version
303
__kiwi_version__: str # Underlying C++ library version
304
```
305
306
## Exception Handling
307
308
Kiwisolver provides specific exceptions for different constraint solving errors:
309
310
```python { .api }
311
class BadRequiredStrength(Exception):
312
"""Raised when required strength is used inappropriately."""
313
314
class DuplicateConstraint(Exception):
315
"""Raised when adding a constraint that already exists."""
316
constraint: Constraint # The duplicate constraint
317
318
class DuplicateEditVariable(Exception):
319
"""Raised when adding an edit variable that already exists."""
320
edit_variable: Variable # The duplicate edit variable
321
322
class UnknownConstraint(Exception):
323
"""Raised when removing a constraint that doesn't exist."""
324
constraint: Constraint # The unknown constraint
325
326
class UnknownEditVariable(Exception):
327
"""Raised when operating on an edit variable that doesn't exist."""
328
edit_variable: Variable # The unknown edit variable
329
330
class UnsatisfiableConstraint(Exception):
331
"""Raised when constraints cannot be satisfied."""
332
constraint: Constraint # The constraint that caused the conflict
333
```
334
335
## Mathematical Operators
336
337
Variables, Terms, and Expressions support rich mathematical syntax through operator overloading:
338
339
**Arithmetic Operations:**
340
- `+`, `-`: Addition and subtraction between Variables, Terms, Expressions, and numbers (returns Expression)
341
- `*`, `/`: Multiplication and division with numeric coefficients (returns Term or Expression)
342
- `-x`: Negation (unary minus) - returns Term for Variable, Term/Expression for others
343
344
**Constraint Creation:**
345
- `==`: Equality constraint (`expression == value`) - returns Constraint
346
- `>=`: Greater-than-or-equal constraint (`expression >= value`) - returns Constraint
347
- `<=`: Less-than-or-equal constraint (`expression <= value`) - returns Constraint
348
- **Unsupported**: `!=`, `>`, `<` operators will raise exceptions
349
350
**Strength Modification:**
351
- `|`: Apply strength to constraint (`constraint | strength.medium`)
352
353
## Usage Examples
354
355
### Simple Layout Problem
356
357
```python
358
from kiwisolver import Variable, Solver, strength
359
360
# Create variables for a horizontal layout
361
x1, w1, x2, w2 = Variable("x1"), Variable("w1"), Variable("x2"), Variable("w2")
362
363
solver = Solver()
364
365
# Define relationships
366
solver.addConstraint(x1 + w1 <= x2) # Widget 1 before widget 2
367
solver.addConstraint(x1 >= 0) # Left boundary
368
solver.addConstraint(x2 + w2 <= 800) # Right boundary
369
solver.addConstraint(w1 >= 100 | strength.strong) # Preferred width
370
solver.addConstraint(w2 >= 100 | strength.strong) # Preferred width
371
372
solver.updateVariables()
373
print(f"Widget 1: x={x1.value()}, w={w1.value()}")
374
print(f"Widget 2: x={x2.value()}, w={w2.value()}")
375
```
376
377
### Dynamic Value Suggestions
378
379
```python
380
from kiwisolver import Variable, Solver
381
382
# Create a resizable element
383
x, width = Variable("x"), Variable("width")
384
solver = Solver()
385
386
# Basic constraints
387
solver.addConstraint(x >= 0)
388
solver.addConstraint(x + width <= 1000)
389
solver.addConstraint(width >= 50)
390
391
# Make position editable
392
solver.addEditVariable(x, "medium")
393
solver.addEditVariable(width, "medium")
394
395
# Suggest new values dynamically
396
solver.suggestValue(x, 100)
397
solver.suggestValue(width, 200)
398
solver.updateVariables()
399
400
print(f"Position: {x.value()}, Width: {width.value()}")
401
402
# Update suggestions
403
solver.suggestValue(x, 300)
404
solver.updateVariables()
405
print(f"New position: {x.value()}, Width: {width.value()}")
406
```
407
408
### Complex Expression Building
409
410
```python
411
from kiwisolver import Variable, Term, Expression, Solver
412
413
# Create variables
414
a, b, c = Variable("a"), Variable("b"), Variable("c")
415
416
# Build complex expressions manually
417
term1 = Term(a, 2.0) # 2*a
418
term2 = Term(b, -1.5) # -1.5*b
419
expr = Expression([term1, term2], 10) # 2*a - 1.5*b + 10
420
421
# Or use operator overloading (equivalent)
422
expr2 = 2*a - 1.5*b + 10
423
424
solver = Solver()
425
solver.addConstraint(expr == c) # 2*a - 1.5*b + 10 = c
426
solver.addConstraint(a == 5)
427
solver.addConstraint(b == 2)
428
429
solver.updateVariables()
430
print(f"a={a.value()}, b={b.value()}, c={c.value()}")
431
print(f"Expression value: {expr.value()}")
432
```