| name | add-propagator |
| description | Scaffold a new constraint propagator in nucs/propagators/, register it, and add its test. Use when the user asks to add a propagator, implement a new constraint, or extend the propagator library. Use when this capability is needed. |
| metadata | {"author":"yangeorget"} |
Add a propagator
A propagator is a constraint enforced by domain filtering. Adding one means: writing a module with three Numba-jitted
functions, registering it with an ALG_* id, and adding a parameterized test.
1. Pick a name and signature
<name> is snake_case, derived from the constraint (e.g. abs_eq, sum_leq_c). Suffix _c means a constant
parameter is involved.
- Decide what
domains and parameters carry:
domains is an NDArray of shape (n, 2) — one (MIN, MAX) row per variable, in a fixed order chosen by you.
parameters is a 1-D NDArray of ints (may be empty). Use it for constants, coefficients, or table data.
- Document the variable order in the
compute_domains_<name> docstring — callers rely on it.
2. Create nucs/propagators/<name>_propagator.py
The file must contain three functions and the standard copyright header (copy from header.txt — every source file in
nucs/ and tests/ starts with the ASCII-art banner).
Reference: nucs/propagators/abs_eq_propagator.py is the minimal template.
def get_complexity_<
name > (n: int, parameters: NDArray) -> int:
...
@njit(cache=True, fastmath=True)
def get_triggers_<
name > (n: int, variable: int, parameters: NDArray) -> int:
...
@njit(cache=True, fastmath=True)
def compute_domains_<
name > (domains: NDArray, parameters: NDArray) -> int:
...
Rules for the jitted functions (see [[numba-rules]] in CLAUDE.md):
- No Python objects, no exceptions, no list/dict comprehensions over heterogeneous types.
- Mutate
domains in place. After each tightening, check
if domains[i][MIN] > domains[i][MAX]: return PROP_INCONSISTENCY.
- Return
PROP_ENTAILMENT only when the constraint can never be violated again (rare; safe to return PROP_CONSISTENCY
if unsure).
3. Register in nucs/propagators/propagators.py
Add the import alongside the others, then append a registration line. The ALG_* lines are ordered alphabetically by
id — keep that.
from nucs.propagators. < name > _propagator
import
(
compute_domains_ < name >,
get_complexity_ < name >,
get_triggers_ < name >,
)
...
ALG_ < NAME > = register_propagator(get_triggers_ < name >, get_complexity_ < name >, compute_domains_ < name >)
The returned id is the propagator's index; never hardcode it.
4. Add tests/propagators/test_<name>.py
Follow the PropagatorTest pattern (see tests/propagators/test_abs_eq.py):
class Test< Name > (PropagatorTest):
@pytest.mark.parametrize(
"domains,parameters,consistency_result,expected_domains",
[
([(lo, hi), ...], [param, ...], PROP_CONSISTENCY, [[lo, hi], ...]),
],
)
def test_compute_domains(self, domains, parameters, consistency_result, expected_domains) -> None:
self.assert_compute_domains(
compute_domains_ < name >, domains, parameters, consistency_result, expected_domains
)
Cover at minimum: a pruning case, an inconsistency case, and a no-op case where the input is already tight.
5. Verify
./scripts/bash/style.sh
NUMBA_CACHE_DIR=.numba/cache PYTHONPATH=. pytest tests/propagators/test_<name>.py
For debugging the propagator logic interactively, run with NUMBA_DISABLE_JIT=1 so tracebacks land in your Python
source.
6. Document
Add the propagator to docs/source/reference/reference_propagators.rst (the .. autofunction:: list is ordered
alphabetically by module name — keep that).
7. Wire it into the FlatZinc adapter (only if it backs a FlatZinc builtin)
If the propagator exists to support a MiniZinc/FlatZinc constraint, also connect it in nucs/fzn/:
- Add one entry to the
BUILTINS dict in nucs/fzn/builtins.py, keyed by the FlatZinc builtin name. Its handler
resolves the args (model.var_index_of / var_list_of / int_list_of / const_of) and calls
model.problem.add_propagator(ALG_<NAME>, variables, parameters).
- If it is a global you want MiniZinc to keep native rather than decompose, add a body-less predicate file under
nucs/fzn/share/minizinc/nucs/ (see fzn_all_different_int.mzn), and key the dispatch entry on the fzn_* (or
custom)
predicate name that file produces.
Verify end-to-end with tests/fzn/ and, when minizinc is installed, minizinc --solver nucs.
Source: yangeorget/nucs — distributed by TomeVault.