pyasyncbtrack


Namepyasyncbtrack JSON
Version 0.11.0 PyPI version JSON
download
home_pageNone
SummaryAsynchronous Backtracking (ABT) for Distributed CSPs
upload_time2025-09-06 01:56:06
maintainerNone
docs_urlNone
authorPieter Cawood
requires_python>=3.10
licenseMIT License Copyright (c) 2025 Pieter Cawood Permission is hereby granted, free of charge, to any person obtaining a copy ...
keywords dcsp distributed constraint satisfaction asynchronous backtracking abt csp constraint programming arc consistency ac-3 nogoods backjumping heuristics mrv lcv
VCS
bugtrack_url
requirements tqdm
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # PyAsyncBTrack

[![CI](https://github.com/Pieter-Cawood/PyAsyncBTrack/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Pieter-Cawood/PyAsyncBTrack/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/pyasyncbtrack.svg)](https://pypi.org/project/pyasyncbtrack/)
[![Python versions](https://img.shields.io/pypi/pyversions/pyasyncbtrack.svg)](https://pypi.org/project/pyasyncbtrack/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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[![CI](https://github.com/Pieter-Cawood/PyAsyncBTrack/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Pieter-Cawood/PyAsyncBTrack/actions/workflows/ci.yml)\n[![PyPI version](https://img.shields.io/pypi/v/pyasyncbtrack.svg)](https://pypi.org/project/pyasyncbtrack/)\n[![Python versions](https://img.shields.io/pypi/pyversions/pyasyncbtrack.svg)](https://pypi.org/project/pyasyncbtrack/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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"
}
        
Elapsed time: 0.67591s