or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/pypi-kiwisolver

A fast implementation of the Cassowary constraint solver

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/kiwisolver@1.4.x

To install, run

npx @tessl/cli install tessl/pypi-kiwisolver@1.4.0

index.mddocs/

Kiwisolver

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.

Package Information

  • Package Name: kiwisolver
  • Language: Python
  • Installation: pip install kiwisolver
  • Python Version: 3.10+

Core Imports

import kiwisolver

Common imports for constraint solving:

from kiwisolver import Variable, Solver, Constraint, Expression, Term, strength

Exception handling imports:

from kiwisolver import (
    UnsatisfiableConstraint,
    DuplicateConstraint,
    UnknownConstraint,
    DuplicateEditVariable,
    UnknownEditVariable,
    BadRequiredStrength
)

Type annotation imports:

from typing import Any, Iterable, Tuple, Literal

Basic Usage

from kiwisolver import Variable, Solver, strength

# Create variables for a simple layout problem
left = Variable("left")
width = Variable("width") 
right = Variable("right")

# Create solver and add constraints
solver = Solver()

# Basic relationship: left + width = right
solver.addConstraint(left + width == right)

# Set some values with different strengths
solver.addConstraint(left >= 0)  # Required strength (default)
solver.addConstraint(width >= 100 | strength.strong)  # Strong preference
solver.addConstraint(right <= 500 | strength.medium)  # Medium preference

# Solve the system
solver.updateVariables()

print(f"Left: {left.value()}, Width: {width.value()}, Right: {right.value()}")

Architecture

Kiwisolver implements the Cassowary constraint solving algorithm through a hierarchy of mathematical components:

  • Variables: Named unknowns that can be solved for
  • Terms: Products of variables and coefficients (coefficient * variable)
  • Expressions: Sums of terms plus constants (term1 + term2 + ... + constant)
  • Constraints: Relationships between expressions with operators (==, >=, <=) and strengths
  • Solver: The main engine that maintains and solves constraint systems
  • Strength System: Hierarchical priority system for conflicting constraints (required > strong > medium > weak)

This design enables natural mathematical syntax through operator overloading while providing fine-grained control over constraint priorities and conflict resolution.

Capabilities

Variable Management

Create and manage constraint variables with optional naming and context for debugging and identification.

class Variable:
    def __init__(self, name: str = "", context: Any = None) -> None:
        """Create a new constraint variable.
        
        Args:
            name: Optional name for debugging
            context: Optional context object for application use
        """
    
    def name(self) -> str:
        """Get the variable name."""
    
    def setName(self, name: str) -> None:
        """Set the variable name."""
    
    def value(self) -> float:
        """Get the current solved value of the variable."""
    
    def context(self) -> Any:
        """Get the context object associated with the variable."""
    
    def setContext(self, context: Any) -> None:
        """Set the context object associated with the variable."""

Mathematical Expression Building

Build mathematical expressions using terms (coefficient * variable combinations) and expressions (sums of terms).

class Term:
    def __init__(self, variable: Variable, coefficient: int | float = 1.0) -> None:
        """Create a term representing coefficient * variable.
        
        Args:
            variable: The variable to multiply
            coefficient: Multiplication factor (default 1.0)
        """
    
    def coefficient(self) -> float:
        """Get the coefficient for the term."""
    
    def variable(self) -> Variable:
        """Get the variable for the term."""
    
    def value(self) -> float:
        """Get the current evaluated value (coefficient * variable.value())."""

class Expression:
    def __init__(self, terms: Iterable[Term], constant: int | float = 0.0) -> None:
        """Create an expression as sum of terms plus constant.
        
        Args:
            terms: Collection of Term objects to sum
            constant: Constant value to add (default 0.0)
        """
    
    def constant(self) -> float:
        """Get the constant term."""
    
    def terms(self) -> Tuple[Term, ...]:
        """Get the tuple of terms in the expression."""
    
    def value(self) -> float:
        """Get the current evaluated value of the entire expression."""

Constraint Creation and Management

Create constraints from expressions using comparison operators and manage constraint strengths for conflict resolution.

class Constraint:
    def __init__(
        self,
        expression: Expression,
        op: Literal["==", "<=", ">="],
        strength: float | Literal["weak", "medium", "strong", "required"] = "required"
    ) -> None:
        """Create a constraint from an expression.
        
        Args:
            expression: Mathematical expression for the constraint
            op: Comparison operator ("==", "<=", or ">=")
            strength: Constraint strength (default "required")
        """
    
    def expression(self) -> Expression:
        """Get the expression object for the constraint."""
    
    def op(self) -> Literal["==", "<=", ">="]:
        """Get the relational operator for the constraint."""
    
    def strength(self) -> float:
        """Get the numeric strength value."""
    
    def violated(self) -> bool:
        """Indicate if the constraint is violated in the current state of the solver."""

Constraint Solving

Main solver engine for managing and solving constraint systems with support for dynamic constraint modification.

class Solver:
    def __init__(self) -> None:
        """Create a new constraint solver."""
    
    def addConstraint(self, constraint: Constraint) -> None:
        """Add a constraint to the solver.
        
        Raises:
            DuplicateConstraint: If constraint already exists
            UnsatisfiableConstraint: If constraint cannot be satisfied
        """
    
    def removeConstraint(self, constraint: Constraint) -> None:
        """Remove a constraint from the solver.
        
        Raises:
            UnknownConstraint: If constraint doesn't exist
        """
    
    def hasConstraint(self, constraint: Constraint) -> bool:
        """Check if solver contains a specific constraint."""
    
    def addEditVariable(
        self, 
        variable: Variable, 
        strength: float | Literal["weak", "medium", "strong", "required"]
    ) -> None:
        """Add a variable that can be dynamically modified.
        
        Args:
            variable: Variable to make editable
            strength: Strength for edit operations
            
        Raises:
            DuplicateEditVariable: If variable already added as edit variable
            BadRequiredStrength: If required strength used for edit variable
        """
    
    def removeEditVariable(self, variable: Variable) -> None:
        """Remove an edit variable from the solver.
        
        Raises:
            UnknownEditVariable: If variable is not an edit variable
        """
    
    def hasEditVariable(self, variable: Variable) -> bool:
        """Check if variable is registered as an edit variable."""
    
    def suggestValue(self, variable: Variable, value: int | float) -> None:
        """Suggest a desired value for an edit variable.
        
        Args:
            variable: Edit variable to modify
            value: Suggested value
            
        Raises:
            UnknownEditVariable: If variable is not an edit variable
        """
    
    def updateVariables(self) -> None:
        """Solve the constraint system and update all variable values."""
    
    def reset(self) -> None:
        """Reset solver to empty state, removing all constraints and edit variables."""
    
    def dump(self) -> None:
        """Print solver internal state to stdout for debugging."""
    
    def dumps(self) -> str:
        """Return solver internal state as string for debugging."""

Strength Management

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.

# Singleton strength instance - do not instantiate directly
strength: Strength

# Predefined strength values (properties)
strength.weak: float          # Weakest constraint strength
strength.medium: float        # Medium constraint strength  
strength.strong: float        # Strong constraint strength
strength.required: float      # Required constraint strength (highest priority)

def strength.create(
    a: int | float, 
    b: int | float, 
    c: int | float, 
    weight: int | float = 1.0
) -> float:
    """Create custom constraint strength.
    
    Args:
        a, b, c: Strength hierarchy components
        weight: Additional weight factor (default: 1.0)
        
    Returns:
        Custom strength value
    """

Version Information

Package and library version information for compatibility checking.

__version__: str  # Python package version
__kiwi_version__: str  # Underlying C++ library version

Exception Handling

Kiwisolver provides specific exceptions for different constraint solving errors:

class BadRequiredStrength(Exception):
    """Raised when required strength is used inappropriately."""

class DuplicateConstraint(Exception):
    """Raised when adding a constraint that already exists."""
    constraint: Constraint  # The duplicate constraint

class DuplicateEditVariable(Exception):
    """Raised when adding an edit variable that already exists."""
    edit_variable: Variable  # The duplicate edit variable

class UnknownConstraint(Exception):
    """Raised when removing a constraint that doesn't exist."""
    constraint: Constraint  # The unknown constraint

class UnknownEditVariable(Exception):
    """Raised when operating on an edit variable that doesn't exist."""
    edit_variable: Variable  # The unknown edit variable

class UnsatisfiableConstraint(Exception):
    """Raised when constraints cannot be satisfied."""
    constraint: Constraint  # The constraint that caused the conflict

Mathematical Operators

Variables, Terms, and Expressions support rich mathematical syntax through operator overloading:

Arithmetic Operations:

  • +, -: Addition and subtraction between Variables, Terms, Expressions, and numbers (returns Expression)
  • *, /: Multiplication and division with numeric coefficients (returns Term or Expression)
  • -x: Negation (unary minus) - returns Term for Variable, Term/Expression for others

Constraint Creation:

  • ==: Equality constraint (expression == value) - returns Constraint
  • >=: Greater-than-or-equal constraint (expression >= value) - returns Constraint
  • <=: Less-than-or-equal constraint (expression <= value) - returns Constraint
  • Unsupported: !=, >, < operators will raise exceptions

Strength Modification:

  • |: Apply strength to constraint (constraint | strength.medium)

Usage Examples

Simple Layout Problem

from kiwisolver import Variable, Solver, strength

# Create variables for a horizontal layout
x1, w1, x2, w2 = Variable("x1"), Variable("w1"), Variable("x2"), Variable("w2")

solver = Solver()

# Define relationships
solver.addConstraint(x1 + w1 <= x2)  # Widget 1 before widget 2  
solver.addConstraint(x1 >= 0)  # Left boundary
solver.addConstraint(x2 + w2 <= 800)  # Right boundary
solver.addConstraint(w1 >= 100 | strength.strong)  # Preferred width
solver.addConstraint(w2 >= 100 | strength.strong)  # Preferred width

solver.updateVariables()
print(f"Widget 1: x={x1.value()}, w={w1.value()}")
print(f"Widget 2: x={x2.value()}, w={w2.value()}")

Dynamic Value Suggestions

from kiwisolver import Variable, Solver

# Create a resizable element
x, width = Variable("x"), Variable("width")
solver = Solver()

# Basic constraints
solver.addConstraint(x >= 0)
solver.addConstraint(x + width <= 1000)
solver.addConstraint(width >= 50)

# Make position editable
solver.addEditVariable(x, "medium")
solver.addEditVariable(width, "medium")

# Suggest new values dynamically
solver.suggestValue(x, 100)
solver.suggestValue(width, 200)
solver.updateVariables()

print(f"Position: {x.value()}, Width: {width.value()}")

# Update suggestions
solver.suggestValue(x, 300)
solver.updateVariables()
print(f"New position: {x.value()}, Width: {width.value()}")

Complex Expression Building

from kiwisolver import Variable, Term, Expression, Solver

# Create variables
a, b, c = Variable("a"), Variable("b"), Variable("c")

# Build complex expressions manually
term1 = Term(a, 2.0)  # 2*a
term2 = Term(b, -1.5)  # -1.5*b
expr = Expression([term1, term2], 10)  # 2*a - 1.5*b + 10

# Or use operator overloading (equivalent)
expr2 = 2*a - 1.5*b + 10

solver = Solver()
solver.addConstraint(expr == c)  # 2*a - 1.5*b + 10 = c
solver.addConstraint(a == 5)
solver.addConstraint(b == 2)

solver.updateVariables()
print(f"a={a.value()}, b={b.value()}, c={c.value()}")
print(f"Expression value: {expr.value()}")