# PyAsyncBTrack
[](https://github.com/Pieter-Cawood/PyAsyncBTrack/actions/workflows/ci.yml)
[](https://pypi.org/project/pyasyncbtrack/)
[](https://pypi.org/project/pyasyncbtrack/)
[](LICENSE)
Asynchronous Backtracking (ABT) — implemented as a fast, centralized solver for **Distributed Constraint Satisfaction Problems (DCSPs)**.
It brings together MRV/LCV heuristics, conflict-directed backjumping with nogoods, optional AC-3 pre-pruning, restarts with domain reshuffling, and multi-solution enumeration — all with clean, typed Python APIs.
> **Install**
> ```bash
> pip install pypyasyncbtrack
> ```
> **Import**
> ```python
> from pyasyncbtrack import DCSPProblem, solve, Verbosity
> ```
---
## Highlights
- **ABT-style search** (centralized): nogood learning + conflict-directed backjumping
- **Heuristics**: MRV (minimum remaining values), degree tie-break, and LCV
- **Consistency**: optional **AC-3** arc consistency pre-pass
- **Restarts**: per-run iteration caps, domain reshuffling, and diversified RNG
- **Enumeration**: collect **unique** solutions with canonical deduping
- **Progress**: `Verbosity.OFF | LOG | TQDM` (tqdm optional)
- **Typed**: simple, typed modeling of variables, domains, and constraints
- **Batteries included**: reusable constraint helpers (e.g., `not_equal`, `alldifferent`, ranges)
---
## N Queens Example
N-Queens with a 2D domain (rows, cols, diagonals)
Below is a compact demo that models **N-Queens** where each queen’s domain is the full grid `(row, col)`, and pairwise constraints rule out shared rows, columns, and diagonals.
```
from __future__ import annotations
import argparse
import random
from typing import List, Dict, Optional, Tuple
from pyasyncbtrack import DCSPProblem, solve, Verbosity
from pyasyncbtrack.types import BinaryConstraint
# ---------------------------------------------------------------------------
# Constraints (2D domain)
# ---------------------------------------------------------------------------
def pred(u_var: str, u_val: Tuple[int, int], v_var: str, v_val: Tuple[int, int]) -> bool:
if (not isinstance(u_val, tuple) or len(u_val) != 2 or
not isinstance(v_val, tuple) or len(v_val) != 2):
return False
r1, c1 = u_val
r2, c2 = v_val
return (r1 != r2) and (c1 != c2) and (abs(r1 - r2) != abs(c1 - c2))
def rows_cols_diags_constraint(u: str, v: str) -> BinaryConstraint:
"""
Enforce: different rows, different columns, not on a diagonal.
Values are tuples (row, col).
"""
return BinaryConstraint(u, v, pred)
# ---------------------------------------------------------------------------
# Main demo
# ---------------------------------------------------------------------------
def main(N: int = 8, timeout_s: Optional[float] = 10.0) -> None:
# Variables (queens)
variables = [f"Q{i}" for i in range(N)]
# 2D domain: every queen can pick any (row, col)
all_cells: List[Tuple[int, int]] = [(r, c) for r in range(N) for c in range(N)]
domains: Dict[str, List[Tuple[int, int]]] = {q: list(all_cells) for q in variables}
# Pairwise constraints for all pairs (different rows, cols, diagonals)
constraints: List[BinaryConstraint] = []
for i in range(N):
for j in range(i + 1, N):
constraints.append(rows_cols_diags_constraint(variables[i], variables[j]))
rng = random.Random(42) # optional for reproducibility
# Build and solve
problem = DCSPProblem(variables, domains, constraints)
sol = solve(
problem,
timeout_s=timeout_s,
domain_reshuffling=True,
rng=rng,
reshuffle_iterations=150, # single knob; <=0 means no per-run cap
prefilter_domain=True, # enable AC-3 pruning before each run
verbosity=Verbosity.TQDM # tqdm desc-only (if available), or quiet
)
if sol is None:
print("No solution (or timeout).")
return
# Pretty-print a board
grid = [["." for _ in range(N)] for _ in range(N)]
for q, (r, c) in sol.items():
grid[int(r)][int(c)] = "Q"
print("\n".join(" ".join(row) for row in grid))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="N-Queens (2D-domain) with PyAsyncBTrack (ABT)")
parser.add_argument("-n", "--size", type=int, default=8, help="Board size N")
parser.add_argument("--timeout", type=float, default=120.0, help="Timeout seconds (<=0 for unlimited)")
args = parser.parse_args()
main(N=args.size, timeout_s=args.timeout)
```
---
## Why “Asynchronous Backtracking”?
This package implements **ABT semantics** (nogoods, backjumping, asynchronous “agent” view) in a **single-process, centralized** solver that’s easy to embed. You get ABT’s powerful conflict learning without having to stand up a distributed system or message bus.
---
## Examples
This repo ships with two practical demos:
### 1) Latin Square (N × N)
```bash
python examples/latin_square_demo.py --n 4 --verbosity TQDM
python examples/latin_square_demo.py --n 5 --k 3 --solutions-timeout 5 --verbosity LOG
python examples/latin_square_demo.py --n 4 --givens "0,0=1; 1,1=2" --verbosity OFF
```
What it shows:
- Variables = grid cells, domains = symbols (e.g. `1..N` or `A..D`)
- Row/column **AllDifferent** via pairwise `!=`
- Optional **givens** as unary constraints
- Single solution or **multi-solution** enumeration
### 2) N-Queens (2D domain)
Values are `(row, col)` tuples; constraints enforce no shared rows/cols/diagonals.
```bash
python examples/example_NQueens.py -n 10 --timeout 120
python examples/example_NQueens_multiple_solutions.py -n 8 --timeout 120
```
What it shows:
- **2D domains** (any queen can occupy any cell)
- Pairwise constraints using a custom predicate
- Optional AC-3 pre-filtering and progress reporting
- Collect several **distinct** solutions
---
## Modeling DCSPs
### Concepts
- **Variables**: identifiers like `"X"`, `"Q0"`, `"X_0_1"`
- **Domains**: lists of values (ints, strings, tuples, frozensets)
- **Binary constraints**: relations over pairs `(u, v)` via fast, pure predicates
### Building a problem
```python
from pyasyncbtrack import DCSPProblem
from pyasyncbtrack.constraints import not_equal, alldifferent
variables = ["A", "B", "C"]
domains = {"A": [1,2], "B": [1,2], "C": [1,2]}
constraints = []
constraints += alldifferent(variables) # expands to pairwise !=
problem = DCSPProblem(variables, domains, constraints)
```
### Common constraints
```python
from pyasyncbtrack.constraints import (
eq, ne, lt, le, gt, ge,
equals_offset, difference_ge,
in_collection, not_in_collection, in_range,
str_equals, str_not_equals, str_contains,
alldifferent, allequal, monotone_increasing
)
# u != v
ne("X", "Y")
# |u - v| >= k
difference_ge("X", "Y", 2)
# X in {1,3,5} (paired against any neighbor)
in_collection("X", {1,3,5})("Y")
```
### Unary constraints (domain filters)
```python
from pyasyncbtrack.types import UnaryConstraint, apply_unary
domains = {"X": list(range(10))}
unaries = [UnaryConstraint("X", allowed=lambda v: v % 2 == 0)]
domains = apply_unary(domains, unaries) # keeps only even values
```
---
## Solving
```python
from pyasyncbtrack import solve, Verbosity
result = solve(
problem,
timeout_s=20.0, # None or <=0 means unlimited
reshuffle_iterations=50_000,# per-run iteration cap (enables restarts)
prefilter_domain=True, # AC-3 before each run
verbosity=Verbosity.TQDM, # OFF | LOG | TQDM
seed=7, # or pass rng=Random(...)
# Enumeration (optional):
nr_of_solutions=10, # collect up to k distinct solutions
solutions_timeout_s=60.0, # enumeration time budget (seconds)
)
```
### Return shape
- **Single-solution mode**: returns `Assignment` (`dict[var] = value`) or `None`.
- **Enumeration mode** (`nr_of_solutions` set or `solutions_timeout_s` set): returns `List[Assignment]` (possibly empty).
---
## Configuration Reference
| Argument | Type | Default | Description |
|---|---|---:|---|
| `timeout_s` | `float \| None` | `10.0` | Global wall-clock budget for the whole call. |
| `use_mrv` | `bool` | `True` | **Minimum Remaining Values** variable selection. |
| `use_lcv` | `bool` | `True` | **Least Constraining Value** ordering. |
| `domain_reshuffling` | `bool` | `True` | Shuffle domains per run to diversify search. |
| `random_tiebreak` | `bool` | `True` | Jitter to break ties in selection/ordering. |
| `rng` | `random.Random \| None` | `None` | Provide your RNG (overrides `seed`). |
| `seed` | `int \| None` | `None` | Seed for deterministic runs (when `rng` not provided). |
| `reshuffle_iterations` | `int \| None` | `None` | Per-run iteration cap; triggers **restarts** when hit. |
| `prefilter_domain` | `bool` | `False` | Run **AC-3** before each run. |
| `verbosity` | `Verbosity` | `OFF` | `OFF`, `LOG`, or `TQDM` (desc-only). |
| `nr_of_solutions` | `int \| None` | `None` | Enumerate up to **k** unique solutions. |
| `solutions_timeout_s` | `float \| None` | `None` | Enumeration time budget (wall-clock). |
| `progress_log_every` | `int` | `5000` | LOG cadence (iterations). |
| `diversify_restarts` | `bool` | `True` | Per-run RNG diversification for broader exploration. |
---
## Tips & Best Practices
- **Domains matter**: narrow them early with unary constraints or AC-3 (`prefilter_domain=True`).
- **Heuristics**: keep MRV & LCV on for most problems.
- **Restarts**: for tough instances, set a per-run cap (`reshuffle_iterations`) and a sensible `timeout_s`.
- **Determinism**: pass a fixed `seed` (or an explicit `random.Random`) to reproduce results.
- **Enumeration**: use `nr_of_solutions` and/or `solutions_timeout_s`; solutions are **canonicalized** to avoid duplicates.
---
## API Surface (import paths)
```python
# Core
from pyasyncbtrack import DCSPProblem, solve, Verbosity
# Types & utilities
from pyasyncbtrack.types import (
BinaryConstraint, UnaryConstraint, TableConstraint,
apply_unary, Assignment, Variable, Value
)
# Reusable constraints
from pyasyncbtrack.constraints import (
not_equal, equals, less_than, less_equal, greater_than, greater_equal,
equals_offset, difference_ge, difference_gt, difference_le, difference_lt,
in_collection, not_in_collection, in_range,
str_equals, str_not_equals, str_has_prefix, str_has_suffix, str_contains,
alldifferent, allequal, monotone_increasing, monotone_non_decreasing,
equals_with_offset_chain, no_overlap, precedes, follows,
pair, # wrap custom (value,value) predicate quickly
)
# Consistency (optional)
from pyasyncbtrack.consistency import ac3
```
---
## CLI Demos
Run from the repository root:
```bash
# Latin squares
python examples/latin_square_demo.py --n 4 --verbosity TQDM
# N-Queens (2D domain)
python examples/example_NQueens.py -n 10 --timeout 120 TQDM
# N-Queens (2D domain) multiple solutions
python examples/example_NQueens_multiple_solutions.py -n 8 --timeout 120 TQDM
```
---
## Performance Notes
- Constraint predicates are in hot loops. Keep them **pure** and **fast**.
- If you write custom constraints, avoid expensive Python objects in inner calls.
- AC-3 can dramatically shrink domains for tight relations; for loose `!=` on large domains, its effect may be modest — test both ways.
---
## Python & Typing
- **Python**: 3.9+ recommended
- **Typing**: The public API is type-annotated and works well with Pyright/MyPy.
---
## License
This project is open source. See `LICENSE` in the repository for details.
---
## Acknowledgements
Inspired by the Asynchronous Backtracking literature and classic CSP propagation techniques (AC-3, MRV/LCV, nogoods, backjumping).
---
Raw data
{
"_id": null,
"home_page": null,
"name": "pyasyncbtrack",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "DCSP, Distributed Constraint Satisfaction, Asynchronous Backtracking, ABT, CSP, Constraint Programming, Arc Consistency, AC-3, Nogoods, Backjumping, Heuristics, MRV, LCV",
"author": "Pieter Cawood",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/b9/8f/e0e41f193260ab2b32404b547cdcfa86ac4f9205ab6dd2806906215ad1cc/pyasyncbtrack-0.11.0.tar.gz",
"platform": null,
"description": "# PyAsyncBTrack\n\n[](https://github.com/Pieter-Cawood/PyAsyncBTrack/actions/workflows/ci.yml)\n[](https://pypi.org/project/pyasyncbtrack/)\n[](https://pypi.org/project/pyasyncbtrack/)\n[](LICENSE)\n\n\nAsynchronous Backtracking (ABT) \u2014 implemented as a fast, centralized solver for **Distributed Constraint Satisfaction Problems (DCSPs)**. \nIt brings together MRV/LCV heuristics, conflict-directed backjumping with nogoods, optional AC-3 pre-pruning, restarts with domain reshuffling, and multi-solution enumeration \u2014 all with clean, typed Python APIs.\n\n> **Install** \n> ```bash\n> pip install pypyasyncbtrack\n> ```\n> **Import** \n> ```python\n> from pyasyncbtrack import DCSPProblem, solve, Verbosity\n> ```\n\n---\n\n## Highlights\n\n- **ABT-style search** (centralized): nogood learning + conflict-directed backjumping\n- **Heuristics**: MRV (minimum remaining values), degree tie-break, and LCV\n- **Consistency**: optional **AC-3** arc consistency pre-pass\n- **Restarts**: per-run iteration caps, domain reshuffling, and diversified RNG\n- **Enumeration**: collect **unique** solutions with canonical deduping\n- **Progress**: `Verbosity.OFF | LOG | TQDM` (tqdm optional)\n- **Typed**: simple, typed modeling of variables, domains, and constraints\n- **Batteries included**: reusable constraint helpers (e.g., `not_equal`, `alldifferent`, ranges)\n\n---\n\n## N Queens Example \n\nN-Queens with a 2D domain (rows, cols, diagonals)\n\nBelow is a compact demo that models **N-Queens** where each queen\u2019s domain is the full grid `(row, col)`, and pairwise constraints rule out shared rows, columns, and diagonals.\n\n```\nfrom __future__ import annotations\nimport argparse\nimport random\nfrom typing import List, Dict, Optional, Tuple\n\nfrom pyasyncbtrack import DCSPProblem, solve, Verbosity\nfrom pyasyncbtrack.types import BinaryConstraint\n\n# ---------------------------------------------------------------------------\n# Constraints (2D domain)\n# ---------------------------------------------------------------------------\ndef pred(u_var: str, u_val: Tuple[int, int], v_var: str, v_val: Tuple[int, int]) -> bool:\n if (not isinstance(u_val, tuple) or len(u_val) != 2 or\n not isinstance(v_val, tuple) or len(v_val) != 2):\n return False\n r1, c1 = u_val\n r2, c2 = v_val\n return (r1 != r2) and (c1 != c2) and (abs(r1 - r2) != abs(c1 - c2))\n\ndef rows_cols_diags_constraint(u: str, v: str) -> BinaryConstraint:\n \"\"\"\n Enforce: different rows, different columns, not on a diagonal.\n\n Values are tuples (row, col).\n \"\"\"\n return BinaryConstraint(u, v, pred)\n\n# ---------------------------------------------------------------------------\n# Main demo\n# ---------------------------------------------------------------------------\n\ndef main(N: int = 8, timeout_s: Optional[float] = 10.0) -> None:\n # Variables (queens)\n variables = [f\"Q{i}\" for i in range(N)]\n\n # 2D domain: every queen can pick any (row, col)\n all_cells: List[Tuple[int, int]] = [(r, c) for r in range(N) for c in range(N)]\n domains: Dict[str, List[Tuple[int, int]]] = {q: list(all_cells) for q in variables}\n\n # Pairwise constraints for all pairs (different rows, cols, diagonals)\n constraints: List[BinaryConstraint] = []\n for i in range(N):\n for j in range(i + 1, N):\n constraints.append(rows_cols_diags_constraint(variables[i], variables[j]))\n\n rng = random.Random(42) # optional for reproducibility\n\n # Build and solve\n problem = DCSPProblem(variables, domains, constraints)\n sol = solve(\n problem,\n timeout_s=timeout_s,\n domain_reshuffling=True,\n rng=rng,\n reshuffle_iterations=150, # single knob; <=0 means no per-run cap\n prefilter_domain=True, # enable AC-3 pruning before each run\n verbosity=Verbosity.TQDM # tqdm desc-only (if available), or quiet\n )\n\n if sol is None:\n print(\"No solution (or timeout).\")\n return\n\n # Pretty-print a board\n grid = [[\".\" for _ in range(N)] for _ in range(N)]\n for q, (r, c) in sol.items():\n grid[int(r)][int(c)] = \"Q\"\n print(\"\\n\".join(\" \".join(row) for row in grid))\n\nif __name__ == \"__main__\":\n parser = argparse.ArgumentParser(description=\"N-Queens (2D-domain) with PyAsyncBTrack (ABT)\")\n parser.add_argument(\"-n\", \"--size\", type=int, default=8, help=\"Board size N\")\n parser.add_argument(\"--timeout\", type=float, default=120.0, help=\"Timeout seconds (<=0 for unlimited)\")\n args = parser.parse_args()\n main(N=args.size, timeout_s=args.timeout)\n```\n\n---\n\n## Why \u201cAsynchronous Backtracking\u201d?\n\nThis package implements **ABT semantics** (nogoods, backjumping, asynchronous \u201cagent\u201d view) in a **single-process, centralized** solver that\u2019s easy to embed. You get ABT\u2019s powerful conflict learning without having to stand up a distributed system or message bus.\n\n---\n\n## Examples\n\nThis repo ships with two practical demos:\n\n### 1) Latin Square (N \u00d7 N)\n\n```bash\npython examples/latin_square_demo.py --n 4 --verbosity TQDM\npython examples/latin_square_demo.py --n 5 --k 3 --solutions-timeout 5 --verbosity LOG\npython examples/latin_square_demo.py --n 4 --givens \"0,0=1; 1,1=2\" --verbosity OFF\n```\n\nWhat it shows:\n- Variables = grid cells, domains = symbols (e.g. `1..N` or `A..D`)\n- Row/column **AllDifferent** via pairwise `!=`\n- Optional **givens** as unary constraints\n- Single solution or **multi-solution** enumeration\n\n### 2) N-Queens (2D domain)\n\nValues are `(row, col)` tuples; constraints enforce no shared rows/cols/diagonals.\n\n```bash\npython examples/example_NQueens.py -n 10 --timeout 120\npython examples/example_NQueens_multiple_solutions.py -n 8 --timeout 120\n```\n\nWhat it shows:\n- **2D domains** (any queen can occupy any cell)\n- Pairwise constraints using a custom predicate\n- Optional AC-3 pre-filtering and progress reporting\n- Collect several **distinct** solutions\n\n---\n\n## Modeling DCSPs\n\n### Concepts\n\n- **Variables**: identifiers like `\"X\"`, `\"Q0\"`, `\"X_0_1\"`\n- **Domains**: lists of values (ints, strings, tuples, frozensets)\n- **Binary constraints**: relations over pairs `(u, v)` via fast, pure predicates\n\n### Building a problem\n\n```python\nfrom pyasyncbtrack import DCSPProblem\nfrom pyasyncbtrack.constraints import not_equal, alldifferent\n\nvariables = [\"A\", \"B\", \"C\"]\ndomains = {\"A\": [1,2], \"B\": [1,2], \"C\": [1,2]}\n\nconstraints = []\nconstraints += alldifferent(variables) # expands to pairwise !=\n\nproblem = DCSPProblem(variables, domains, constraints)\n```\n\n### Common constraints\n\n```python\nfrom pyasyncbtrack.constraints import (\n eq, ne, lt, le, gt, ge,\n equals_offset, difference_ge,\n in_collection, not_in_collection, in_range,\n str_equals, str_not_equals, str_contains,\n alldifferent, allequal, monotone_increasing\n)\n\n# u != v\nne(\"X\", \"Y\")\n\n# |u - v| >= k\ndifference_ge(\"X\", \"Y\", 2)\n\n# X in {1,3,5} (paired against any neighbor)\nin_collection(\"X\", {1,3,5})(\"Y\")\n```\n\n### Unary constraints (domain filters)\n\n```python\nfrom pyasyncbtrack.types import UnaryConstraint, apply_unary\n\ndomains = {\"X\": list(range(10))}\nunaries = [UnaryConstraint(\"X\", allowed=lambda v: v % 2 == 0)]\ndomains = apply_unary(domains, unaries) # keeps only even values\n```\n\n---\n\n## Solving\n\n```python\nfrom pyasyncbtrack import solve, Verbosity\n\nresult = solve(\n problem,\n timeout_s=20.0, # None or <=0 means unlimited\n reshuffle_iterations=50_000,# per-run iteration cap (enables restarts)\n prefilter_domain=True, # AC-3 before each run\n verbosity=Verbosity.TQDM, # OFF | LOG | TQDM\n seed=7, # or pass rng=Random(...)\n # Enumeration (optional):\n nr_of_solutions=10, # collect up to k distinct solutions\n solutions_timeout_s=60.0, # enumeration time budget (seconds)\n)\n```\n\n### Return shape\n\n- **Single-solution mode**: returns `Assignment` (`dict[var] = value`) or `None`.\n- **Enumeration mode** (`nr_of_solutions` set or `solutions_timeout_s` set): returns `List[Assignment]` (possibly empty).\n\n---\n\n## Configuration Reference\n\n| Argument | Type | Default | Description |\n|---|---|---:|---|\n| `timeout_s` | `float \\| None` | `10.0` | Global wall-clock budget for the whole call. |\n| `use_mrv` | `bool` | `True` | **Minimum Remaining Values** variable selection. |\n| `use_lcv` | `bool` | `True` | **Least Constraining Value** ordering. |\n| `domain_reshuffling` | `bool` | `True` | Shuffle domains per run to diversify search. |\n| `random_tiebreak` | `bool` | `True` | Jitter to break ties in selection/ordering. |\n| `rng` | `random.Random \\| None` | `None` | Provide your RNG (overrides `seed`). |\n| `seed` | `int \\| None` | `None` | Seed for deterministic runs (when `rng` not provided). |\n| `reshuffle_iterations` | `int \\| None` | `None` | Per-run iteration cap; triggers **restarts** when hit. |\n| `prefilter_domain` | `bool` | `False` | Run **AC-3** before each run. |\n| `verbosity` | `Verbosity` | `OFF` | `OFF`, `LOG`, or `TQDM` (desc-only). |\n| `nr_of_solutions` | `int \\| None` | `None` | Enumerate up to **k** unique solutions. |\n| `solutions_timeout_s` | `float \\| None` | `None` | Enumeration time budget (wall-clock). |\n| `progress_log_every` | `int` | `5000` | LOG cadence (iterations). |\n| `diversify_restarts` | `bool` | `True` | Per-run RNG diversification for broader exploration. |\n\n---\n\n## Tips & Best Practices\n\n- **Domains matter**: narrow them early with unary constraints or AC-3 (`prefilter_domain=True`).\n- **Heuristics**: keep MRV & LCV on for most problems.\n- **Restarts**: for tough instances, set a per-run cap (`reshuffle_iterations`) and a sensible `timeout_s`.\n- **Determinism**: pass a fixed `seed` (or an explicit `random.Random`) to reproduce results.\n- **Enumeration**: use `nr_of_solutions` and/or `solutions_timeout_s`; solutions are **canonicalized** to avoid duplicates.\n\n---\n\n## API Surface (import paths)\n\n```python\n# Core\nfrom pyasyncbtrack import DCSPProblem, solve, Verbosity\n\n# Types & utilities\nfrom pyasyncbtrack.types import (\n BinaryConstraint, UnaryConstraint, TableConstraint,\n apply_unary, Assignment, Variable, Value\n)\n\n# Reusable constraints\nfrom pyasyncbtrack.constraints import (\n not_equal, equals, less_than, less_equal, greater_than, greater_equal,\n equals_offset, difference_ge, difference_gt, difference_le, difference_lt,\n in_collection, not_in_collection, in_range,\n str_equals, str_not_equals, str_has_prefix, str_has_suffix, str_contains,\n alldifferent, allequal, monotone_increasing, monotone_non_decreasing,\n equals_with_offset_chain, no_overlap, precedes, follows,\n pair, # wrap custom (value,value) predicate quickly\n)\n\n# Consistency (optional)\nfrom pyasyncbtrack.consistency import ac3\n```\n\n---\n\n## CLI Demos\n\nRun from the repository root:\n\n```bash\n# Latin squares\npython examples/latin_square_demo.py --n 4 --verbosity TQDM\n\n# N-Queens (2D domain)\npython examples/example_NQueens.py -n 10 --timeout 120 TQDM\n\n# N-Queens (2D domain) multiple solutions\npython examples/example_NQueens_multiple_solutions.py -n 8 --timeout 120 TQDM\n```\n\n---\n\n## Performance Notes\n\n- Constraint predicates are in hot loops. Keep them **pure** and **fast**.\n- If you write custom constraints, avoid expensive Python objects in inner calls.\n- AC-3 can dramatically shrink domains for tight relations; for loose `!=` on large domains, its effect may be modest \u2014 test both ways.\n\n---\n\n## Python & Typing\n\n- **Python**: 3.9+ recommended\n- **Typing**: The public API is type-annotated and works well with Pyright/MyPy.\n\n---\n\n## License\n\nThis project is open source. See `LICENSE` in the repository for details.\n\n---\n\n## Acknowledgements\n\nInspired by the Asynchronous Backtracking literature and classic CSP propagation techniques (AC-3, MRV/LCV, nogoods, backjumping).\n\n---\n\n",
"bugtrack_url": null,
"license": "MIT License\n \n Copyright (c) 2025 Pieter Cawood\n \n Permission is hereby granted, free of charge, to any person obtaining a copy\n ...",
"summary": "Asynchronous Backtracking (ABT) for Distributed CSPs",
"version": "0.11.0",
"project_urls": {
"Homepage": "https://github.com/Pieter-Cawood/PyAsyncBTrack",
"Issues": "https://github.com/Pieter-Cawood/PyAsyncBTrack/issues",
"Source": "https://github.com/Pieter-Cawood/PyAsyncBTrack"
},
"split_keywords": [
"dcsp",
" distributed constraint satisfaction",
" asynchronous backtracking",
" abt",
" csp",
" constraint programming",
" arc consistency",
" ac-3",
" nogoods",
" backjumping",
" heuristics",
" mrv",
" lcv"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "84af9aca0a82d0e110791d31cc45795229bcdd0bd6c0396a747d82b3db5922f3",
"md5": "d017b90073a3718294f033a1e790ac27",
"sha256": "5ec13da56ebaa625742b489c1c5257daffda4c7f3df32cb6a284e2d05d74217c"
},
"downloads": -1,
"filename": "pyasyncbtrack-0.11.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "d017b90073a3718294f033a1e790ac27",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 29657,
"upload_time": "2025-09-06T01:56:04",
"upload_time_iso_8601": "2025-09-06T01:56:04.612977Z",
"url": "https://files.pythonhosted.org/packages/84/af/9aca0a82d0e110791d31cc45795229bcdd0bd6c0396a747d82b3db5922f3/pyasyncbtrack-0.11.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b98fe0e41f193260ab2b32404b547cdcfa86ac4f9205ab6dd2806906215ad1cc",
"md5": "23685a25d208f897aeb2b96b49e33102",
"sha256": "b7102da6592f13061e7225eed9de24453d625cb1b9028b2c49405019a165c9b5"
},
"downloads": -1,
"filename": "pyasyncbtrack-0.11.0.tar.gz",
"has_sig": false,
"md5_digest": "23685a25d208f897aeb2b96b49e33102",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 31525,
"upload_time": "2025-09-06T01:56:06",
"upload_time_iso_8601": "2025-09-06T01:56:06.123551Z",
"url": "https://files.pythonhosted.org/packages/b9/8f/e0e41f193260ab2b32404b547cdcfa86ac4f9205ab6dd2806906215ad1cc/pyasyncbtrack-0.11.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-06 01:56:06",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Pieter-Cawood",
"github_project": "PyAsyncBTrack",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "tqdm",
"specs": [
[
"==",
"4.67.1"
]
]
}
],
"lcname": "pyasyncbtrack"
}