elenctic 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- elenctic-1.0.0/.dockerignore +15 -0
- elenctic-1.0.0/.github/workflows/ci.yml +42 -0
- elenctic-1.0.0/.github/workflows/release.yml +66 -0
- elenctic-1.0.0/.gitignore +10 -0
- elenctic-1.0.0/CHANGELOG.md +193 -0
- elenctic-1.0.0/Containerfile +24 -0
- elenctic-1.0.0/LICENSE +21 -0
- elenctic-1.0.0/PKG-INFO +165 -0
- elenctic-1.0.0/README.md +148 -0
- elenctic-1.0.0/check.sh +5 -0
- elenctic-1.0.0/docs/examples/clingcon.lp +11 -0
- elenctic-1.0.0/docs/examples/consequences.lp +6 -0
- elenctic-1.0.0/docs/examples/hidden-contrary.lp +6 -0
- elenctic-1.0.0/docs/examples/no-objective.lp +5 -0
- elenctic-1.0.0/docs/examples/optimum-wrong.lp +8 -0
- elenctic-1.0.0/docs/examples/optimum.lp +11 -0
- elenctic-1.0.0/docs/examples/theory-atoms.lp +4 -0
- elenctic-1.0.0/docs/examples/why-not.lp +4 -0
- elenctic-1.0.0/docs/explanation/semantics.md +63 -0
- elenctic-1.0.0/docs/how-to/guides.md +75 -0
- elenctic-1.0.0/docs/index.md +8 -0
- elenctic-1.0.0/docs/reference/examples.md +213 -0
- elenctic-1.0.0/docs/reference/output-v1.schema.json +749 -0
- elenctic-1.0.0/docs/reference/reference.md +334 -0
- elenctic-1.0.0/docs/tutorial/first-contract.md +33 -0
- elenctic-1.0.0/pixi.lock +4846 -0
- elenctic-1.0.0/pyproject.toml +189 -0
- elenctic-1.0.0/recipe/recipe.yaml +50 -0
- elenctic-1.0.0/scripts/capture_examples.py +45 -0
- elenctic-1.0.0/scripts/differential.py +159 -0
- elenctic-1.0.0/scripts/rename.sh +14 -0
- elenctic-1.0.0/scripts/write_output_schema.py +18 -0
- elenctic-1.0.0/src/elenctic/__init__.py +169 -0
- elenctic-1.0.0/src/elenctic/__main__.py +8 -0
- elenctic-1.0.0/src/elenctic/about.py +53 -0
- elenctic-1.0.0/src/elenctic/body.py +70 -0
- elenctic-1.0.0/src/elenctic/braces.py +37 -0
- elenctic-1.0.0/src/elenctic/by_answer_sets.py +144 -0
- elenctic-1.0.0/src/elenctic/check.py +220 -0
- elenctic-1.0.0/src/elenctic/classes.py +217 -0
- elenctic-1.0.0/src/elenctic/cli.py +419 -0
- elenctic-1.0.0/src/elenctic/contract.py +130 -0
- elenctic-1.0.0/src/elenctic/determination.py +213 -0
- elenctic-1.0.0/src/elenctic/directives.py +72 -0
- elenctic-1.0.0/src/elenctic/display.py +22 -0
- elenctic-1.0.0/src/elenctic/engine.py +255 -0
- elenctic-1.0.0/src/elenctic/extension.py +34 -0
- elenctic-1.0.0/src/elenctic/facts.py +165 -0
- elenctic-1.0.0/src/elenctic/gates.py +141 -0
- elenctic-1.0.0/src/elenctic/glyphs.py +41 -0
- elenctic-1.0.0/src/elenctic/grounded.py +259 -0
- elenctic-1.0.0/src/elenctic/human.py +309 -0
- elenctic-1.0.0/src/elenctic/json_view.py +212 -0
- elenctic-1.0.0/src/elenctic/judge.py +101 -0
- elenctic-1.0.0/src/elenctic/literals.py +85 -0
- elenctic-1.0.0/src/elenctic/normalize.py +102 -0
- elenctic-1.0.0/src/elenctic/operands.py +123 -0
- elenctic-1.0.0/src/elenctic/paint.py +31 -0
- elenctic-1.0.0/src/elenctic/partition.py +42 -0
- elenctic-1.0.0/src/elenctic/patterns.py +72 -0
- elenctic-1.0.0/src/elenctic/presentation.py +99 -0
- elenctic-1.0.0/src/elenctic/program.py +38 -0
- elenctic-1.0.0/src/elenctic/py.typed +0 -0
- elenctic-1.0.0/src/elenctic/queries.py +113 -0
- elenctic-1.0.0/src/elenctic/query.py +157 -0
- elenctic-1.0.0/src/elenctic/reader.py +94 -0
- elenctic-1.0.0/src/elenctic/readings.py +107 -0
- elenctic-1.0.0/src/elenctic/relation.py +62 -0
- elenctic-1.0.0/src/elenctic/report.py +64 -0
- elenctic-1.0.0/src/elenctic/rows.py +54 -0
- elenctic-1.0.0/src/elenctic/search.py +279 -0
- elenctic-1.0.0/src/elenctic/spans.py +277 -0
- elenctic-1.0.0/src/elenctic/substitution.py +32 -0
- elenctic-1.0.0/src/elenctic/syntax.py +118 -0
- elenctic-1.0.0/src/elenctic/tags.py +147 -0
- elenctic-1.0.0/src/elenctic/terms.py +162 -0
- elenctic-1.0.0/src/elenctic/theory.py +115 -0
- elenctic-1.0.0/src/elenctic/traversal.py +28 -0
- elenctic-1.0.0/src/elenctic/tree.py +89 -0
- elenctic-1.0.0/src/elenctic/unify.py +93 -0
- elenctic-1.0.0/src/elenctic/verdicts.py +68 -0
- elenctic-1.0.0/src/elenctic/wellformed.py +115 -0
- elenctic-1.0.0/src/elenctic/writer.py +87 -0
- elenctic-1.0.0/tests/conftest.py +10 -0
- elenctic-1.0.0/tests/corpus/cowardly.lp +53 -0
- elenctic-1.0.0/tests/corpus/orphans.lp +64 -0
- elenctic-1.0.0/tests/corpus/tweety.lp +82 -0
- elenctic-1.0.0/tests/corpus/uncaring.lp +67 -0
- elenctic-1.0.0/tests/interrupting.py +20 -0
- elenctic-1.0.0/tests/nesting.py +8 -0
- elenctic-1.0.0/tests/reference.py +83 -0
- elenctic-1.0.0/tests/runs.py +19 -0
- elenctic-1.0.0/tests/test_about.py +39 -0
- elenctic-1.0.0/tests/test_by_answer_sets.py +68 -0
- elenctic-1.0.0/tests/test_check.py +612 -0
- elenctic-1.0.0/tests/test_cli.py +692 -0
- elenctic-1.0.0/tests/test_cli_output.py +363 -0
- elenctic-1.0.0/tests/test_cli_version.py +250 -0
- elenctic-1.0.0/tests/test_clingcon.py +179 -0
- elenctic-1.0.0/tests/test_consistency.py +100 -0
- elenctic-1.0.0/tests/test_corpus.py +16 -0
- elenctic-1.0.0/tests/test_determination.py +60 -0
- elenctic-1.0.0/tests/test_diagnostic_wording.py +193 -0
- elenctic-1.0.0/tests/test_differential.py +80 -0
- elenctic-1.0.0/tests/test_display.py +22 -0
- elenctic-1.0.0/tests/test_engine.py +610 -0
- elenctic-1.0.0/tests/test_examples.py +31 -0
- elenctic-1.0.0/tests/test_extension.py +25 -0
- elenctic-1.0.0/tests/test_facts.py +45 -0
- elenctic-1.0.0/tests/test_glyphs.py +49 -0
- elenctic-1.0.0/tests/test_human.py +333 -0
- elenctic-1.0.0/tests/test_json_view.py +146 -0
- elenctic-1.0.0/tests/test_lexable.py +128 -0
- elenctic-1.0.0/tests/test_literals.py +71 -0
- elenctic-1.0.0/tests/test_normalize.py +91 -0
- elenctic-1.0.0/tests/test_paint.py +41 -0
- elenctic-1.0.0/tests/test_partition.py +41 -0
- elenctic-1.0.0/tests/test_presentation.py +186 -0
- elenctic-1.0.0/tests/test_public.py +150 -0
- elenctic-1.0.0/tests/test_query.py +271 -0
- elenctic-1.0.0/tests/test_reader.py +511 -0
- elenctic-1.0.0/tests/test_relation.py +41 -0
- elenctic-1.0.0/tests/test_report.py +36 -0
- elenctic-1.0.0/tests/test_source_boundaries.py +140 -0
- elenctic-1.0.0/tests/test_spans.py +81 -0
- elenctic-1.0.0/tests/test_syntax.py +225 -0
- elenctic-1.0.0/tests/test_tags.py +35 -0
- elenctic-1.0.0/tests/test_terms.py +126 -0
- elenctic-1.0.0/tests/test_theory.py +84 -0
- elenctic-1.0.0/tests/test_traversal.py +35 -0
- elenctic-1.0.0/tests/test_tree.py +49 -0
- elenctic-1.0.0/tests/test_unify.py +315 -0
- elenctic-1.0.0/tests/test_world_view_defaults.py +36 -0
- elenctic-1.0.0/uv.lock +684 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
concurrency:
|
|
7
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
8
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
9
|
+
jobs:
|
|
10
|
+
# A secondary check that the package installs and its tests pass under uv, on Python 3.14 and 3.15,
|
|
11
|
+
# from the tracked uv.lock. The gate itself is the pixi job below.
|
|
12
|
+
uv-install:
|
|
13
|
+
runs-on: ${{ matrix.os }}
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
os: [ubuntu-latest, macos-latest]
|
|
18
|
+
python: ["3.14", "3.15"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v6
|
|
22
|
+
- run: uv sync --locked --python ${{ matrix.python }} --extra theory
|
|
23
|
+
- run: uv run pytest
|
|
24
|
+
pixi:
|
|
25
|
+
# The gate: format, lint, strict types, import contracts, tests.
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: prefix-dev/setup-pixi@v0.8.10
|
|
30
|
+
- run: pixi run -e theory check
|
|
31
|
+
recipe:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: prefix-dev/setup-pixi@v0.8.10
|
|
36
|
+
- run: pixi global install rattler-build
|
|
37
|
+
- name: Build the recipe from this checkout
|
|
38
|
+
run: |
|
|
39
|
+
mkdir -p "$RUNNER_TEMP/recipe"
|
|
40
|
+
sed "s| url: .*| path: $GITHUB_WORKSPACE|; / sha256:/d" \
|
|
41
|
+
recipe/recipe.yaml > "$RUNNER_TEMP/recipe/recipe.yaml"
|
|
42
|
+
rattler-build build --recipe "$RUNNER_TEMP/recipe/recipe.yaml"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags: ["v*"]
|
|
5
|
+
permissions:
|
|
6
|
+
contents: read
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
12
|
+
with:
|
|
13
|
+
persist-credentials: false
|
|
14
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.14"
|
|
17
|
+
- run: uv sync --locked --no-dev
|
|
18
|
+
- name: Check the tag and schema
|
|
19
|
+
env:
|
|
20
|
+
RELEASE_TAG: ${{ github.ref_name }}
|
|
21
|
+
run: |
|
|
22
|
+
uv run --locked --no-dev python - <<'PY'
|
|
23
|
+
import os
|
|
24
|
+
import runpy
|
|
25
|
+
import tomllib
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
|
|
29
|
+
if os.environ["RELEASE_TAG"] != f"v{version}":
|
|
30
|
+
raise SystemExit("tag must match the package version")
|
|
31
|
+
schema = runpy.run_path("scripts/write_output_schema.py")
|
|
32
|
+
if schema["TARGET"].read_text() != schema["render"]():
|
|
33
|
+
raise SystemExit("regenerate the output schema")
|
|
34
|
+
PY
|
|
35
|
+
- run: uv build --python 3.14
|
|
36
|
+
- name: Check the shipped schema
|
|
37
|
+
run: |
|
|
38
|
+
uv run --locked --no-dev python - <<'PYTHON'
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from zipfile import ZipFile
|
|
41
|
+
|
|
42
|
+
with ZipFile(next(Path("dist").glob("*.whl"))) as wheel:
|
|
43
|
+
if wheel.read("elenctic/output-v1.schema.json") != Path(
|
|
44
|
+
"docs/reference/output-v1.schema.json"
|
|
45
|
+
).read_bytes():
|
|
46
|
+
raise SystemExit("wheel must ship the output schema")
|
|
47
|
+
PYTHON
|
|
48
|
+
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
49
|
+
with:
|
|
50
|
+
name: distributions
|
|
51
|
+
path: dist/
|
|
52
|
+
if-no-files-found: error
|
|
53
|
+
publish:
|
|
54
|
+
needs: build
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
environment:
|
|
57
|
+
name: pypi
|
|
58
|
+
url: https://pypi.org/p/elenctic
|
|
59
|
+
permissions:
|
|
60
|
+
id-token: write
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
63
|
+
with:
|
|
64
|
+
name: distributions
|
|
65
|
+
path: dist/
|
|
66
|
+
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are recorded here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-11-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `--root DIR`: one root for every path and `#include` of a run, so a subtree whose cases include
|
|
11
|
+
files from a sibling directory can be tested on its own.
|
|
12
|
+
- `Containerfile`: a local multi-stage recipe for the CLI (uv sync from the lockfile with the
|
|
13
|
+
theory extra). Build it yourself; there is no published image and no CI image build.
|
|
14
|
+
- Pixi environments `py314` and `py315` for the test suite under those Pythons (`pixi run -e py314 test`).
|
|
15
|
+
- JSON Schema for the `--json` document: `docs/reference/output-v1.schema.json`, generated from the
|
|
16
|
+
result types `encode` walks (`elenctic.json_view.schema_document`); regenerate with
|
|
17
|
+
`scripts/write_output_schema.py`. A test fails if the committed file is stale.
|
|
18
|
+
- `python -m elenctic` runs the same CLI as the `elenctic` console script (`src/elenctic/__main__.py`).
|
|
19
|
+
- Import contracts (import-linter, in `pyproject.toml`): the layering, clingo reached only by the fifteen
|
|
20
|
+
modules that read and run programs, clingcon only by `theory`, and `spans` free of both. They run
|
|
21
|
+
in `pixi run check` and in CI.
|
|
22
|
+
- Library: `Program`, `ground`, `solve` with `Limits` and a cancelable `Interrupt`; results as
|
|
23
|
+
`Consistent | Inconsistent | Inconclusive` over `AnswerSets` or `Consequences`.
|
|
24
|
+
- Query kernel in the public API: `Literal`, `Pattern`, `Query` (`Literal | Pattern | All | Any`), `Answer`,
|
|
25
|
+
`answer`, `bindings`, over a `WorldView` that only an exhausted search yields.
|
|
26
|
+
- A row for every name of `elenctic.__all__` in `docs/reference/reference.md`, checked by a test.
|
|
27
|
+
- `mgu(first, second)` and `Unifier`: the most general unifier of two patterns, renamed apart, and
|
|
28
|
+
the pattern both become. Public for answering queries and for the interactive session to come.
|
|
29
|
+
- `unify(first, second)`: the same unifier for two patterns that share their variables by name, as a
|
|
30
|
+
`Substitution`. It is what query answering calls; against a ground pattern it agrees with `mgu`.
|
|
31
|
+
- Contracts read from comments with source spans: `@expect`, `@count`, `@answer-set`, `@cautious`,
|
|
32
|
+
`@brave`, `@query` with optional `= { bindings }`, and an `assuming { … }` form on any claim.
|
|
33
|
+
- `check` with the verdicts `pass`, `fail` and `undecided`.
|
|
34
|
+
- Command line with `--json`, `--verbose`, `--max-seconds` and `--max-answer-sets`; exit codes `CLAIMS_HOLD` (0), `CLAIM_UNMET` (1) and
|
|
35
|
+
`FAULT` (2), and `INTERNAL_ERROR` (3) for a failure of elenctic itself, reported as one line on
|
|
36
|
+
stderr. They are the members of `elenctic.report.Exit`, an `IntEnum`, the outcome of `test`; `version` has its
|
|
37
|
+
own, `elenctic.about.VersionExit` (`REPORTED` 0, `INTERNAL_ERROR` 3), so no member is shared.
|
|
38
|
+
- Native cautious and brave consequences (`Grounded.consequences`), used by `check` when a search
|
|
39
|
+
holds only such claims.
|
|
40
|
+
- Minimal unsatisfiable cores over assumptions: `Refutation` is `These | NotThese | NoAssumptions`.
|
|
41
|
+
- Optimization: `Grounded.optimize` returns `Optimum` or `Incumbent`; claims `@answer-set optimal`,
|
|
42
|
+
`@cautious optimal`, `@brave optimal`, `@optimal { L }`, `@count optimal n` and `@cost { … }`.
|
|
43
|
+
- `@query optimal …`: the same three-valued query path over Opt(P); unqualified `@query` still reads
|
|
44
|
+
AS(P).
|
|
45
|
+
- Preconditions that make a misreadable contract an error: theory atoms, an objective that grounds
|
|
46
|
+
away, a nested `@count optimal`.
|
|
47
|
+
- `Report.lints`: notes when a query reads a literal the program can derive and does not `#show`.
|
|
48
|
+
- Subcommands: `elenctic test` (the default) and the reserved `repl`, `why`, `ask`, `explain`.
|
|
49
|
+
- `elenctic version`, `--version` and `-V`: one text with the version, the license, the holder and the
|
|
50
|
+
versions of clingo and clingcon that are loaded. `--version` and `-V` are options of `elenctic`
|
|
51
|
+
itself: valid before the command and winning over the rest of the line, not taken by a command.
|
|
52
|
+
- Every type that a public signature names is exported, so a signature can be read and a value built
|
|
53
|
+
from `elenctic` alone: the readings `Satisfiable`, `Census`, `Witness`, `Entails`, `Asks`, `Binds`
|
|
54
|
+
and their union `Reading`, the query parts `All` and `Any`, `Shown`, `Found` and `Cut`. A test walks
|
|
55
|
+
the signatures of `__all__` and fails on a type left out. `elenctic.Any` is the disjunction of
|
|
56
|
+
queries, not `typing.Any`.
|
|
57
|
+
- Examples with real output in `docs/reference/examples.md`, checked by a test.
|
|
58
|
+
- Packaging for uv, pixi and conda-forge (`recipe/recipe.yaml`).
|
|
59
|
+
- clingcon: `% @elenctic solver clingcon` in a contract, claims `@assign [optimal] { x=1, … }` and
|
|
60
|
+
`@answer-set … where { x=1, … }`, the library types `Solver`, `Assignment`, `Valuation` and
|
|
61
|
+
`AnswerSet.assignment`. An answer under clingcon is a shown set with its assignment, and `@count`
|
|
62
|
+
counts those. Optional: `elenctic[theory]`, or the pixi `theory` environment; a contract that
|
|
63
|
+
declares clingcon without it is an error (`FAULT`, 2).
|
|
64
|
+
|
|
65
|
+
- `--color auto|always|never` (default `auto`) and `--icons[=WHEN]` (Nerd Font glyphs, off unless asked),
|
|
66
|
+
each decided per stream, and valid before the command as well as after it. `auto` follows
|
|
67
|
+
`NO_COLOR`, `TERM=dumb`, then whether the stream is a terminal, so a redirected stream stays plain; only `--color always` colors it. `CLICOLOR_FORCE`,
|
|
68
|
+
`CLICOLOR` and `FORCE_COLOR` are not read. This governs elenctic's own report and diagnostics;
|
|
69
|
+
the help and usage messages are colored as Python's `argparse` does by default. See the output
|
|
70
|
+
section of the reference.
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
- Docs: JSON `exit_code` includes 3; guides state `world_view` on a truncation; `@expect`-only searches stop once satisfiability is decided.
|
|
74
|
+
- Guides: an interrupted `check` yields undecided findings; `solve` yields `Inconclusive`.
|
|
75
|
+
- An `#include` that resolves outside the root is refused before the case is run; the diagnostic names the escaping path and never its contents.
|
|
76
|
+
- Non-ASCII outside strings and comments is refused with a located diagnostic before clingo sees
|
|
77
|
+
it (program text, `#include`d files, and tag payloads), so a bad character never aborts the
|
|
78
|
+
process; `--json` still emits a document.
|
|
79
|
+
- Binding sets keep tuple structure: `{ (1,2) }` for one variable is one binding, and `{ ((1,2),3) }` for two is accepted; a bare `{ a, 1 }` for arity 2 is refused.
|
|
80
|
+
- A named `.lp` with no contract tag is a fault (exit 2), not a silent `0/0 passed`; a UTF-8 BOM is refused with a located diagnostic; a walked library whose `@tag` looks misspelled is a note.
|
|
81
|
+
- Search groups that read only shown identities project onto `#show`, so `@count` and `@answer-set` no longer enumerate hidden variation; optimal groups prove the cost first, then enumerate at that bound; witness-only groups stop once every claim is decided.
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
- Undecided truncated claims carry an `Ended` detail in JSON; `world_view` and `optimal_view` range over full answer sets by default; the hidden-read lint covers a query's contrary; `bindings` takes a single `Pattern`.
|
|
85
|
+
|
|
86
|
+
- JSON entries carry `verdict` and `solver`, omit program `text`; diagnostic `label`/`children` are optional in the schema; `$id` points at the raw file under `docs/reference/`; the schema ships in the wheel.
|
|
87
|
+
|
|
88
|
+
- An unexpected failure while checking one source is recorded for that source and the rest of the corpus still runs; the run exits 3.
|
|
89
|
+
|
|
90
|
+
- Satisfiability-only groups keep no answer-set symbols; signatures come from clingo; atom→literal is looked up on demand.
|
|
91
|
+
|
|
92
|
+
- A refutation core shows `not L` for an assumption that does not hold.
|
|
93
|
+
- A one-element tuple keeps its trailing comma when rendered.
|
|
94
|
+
- A query that is empty or uses `not` is refused as such; a variable query's help cites the user's pattern.
|
|
95
|
+
- `@count optimal 0` under `@expect sat` names `optimal` in the contradiction.
|
|
96
|
+
- Empty braces render as `{ }`; `@count` accepts only ASCII digits.
|
|
97
|
+
- At most one `@count` per scenario and range.
|
|
98
|
+
- `@expect` alone is admitted over a theory objective.
|
|
99
|
+
|
|
100
|
+
- A second SIGINT restores the default handler so the process can exit 130.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
- The include refusal says `outside the root DIR` and has a help line.
|
|
104
|
+
- An unknown `@model` is told the tag is spelled `@answer-set`; a walked library or a named file
|
|
105
|
+
whose tags are not tags says so (`@model` is not a tag) with the same help as an unknown tag.
|
|
106
|
+
- A missing clingcon's help no longer names a pixi environment.
|
|
107
|
+
- Consistency across docs, help and internals: truncation prose; reserved-command helps as imperatives; `--help` says "answer set programs"; `(default: …)` in the reference; "source" for a checked unit; stderr prefix rule; `render_run`, `holds_flag`, `Classes.merge`; CLI writes with `write` only; American-spelling and reading-name notes in the README; mechanical checks in `tests/test_consistency.py`.
|
|
108
|
+
- Diagnostics follow the rustc model by default (`--format full`): `error: message`, ` --> path:line:col`, a source snippet with carets and a primary label, then `= note:` / `= help:` lines. `--format short` keeps today's one-line gcc form; `--json` is an alias of `--format json`. Messages are lowercase with no trailing period; fix-it wording stands only on `help`. Clingo's byte columns are converted to Unicode scalar columns so carets land. The JSON schema keeps `SCHEMA = 1` and adds optional `label`, `children`, and `code` on `diagnostic` (no codes in 1.0).
|
|
109
|
+
- CI cancels superseded pull-request runs (`concurrency` with `cancel-in-progress` on pull requests only).
|
|
110
|
+
- `uv.lock` pins clingo 5.8.3 and clingcon 5.2.2 so Python 3.15 installs from wheels.
|
|
111
|
+
- The bindings of a query pattern are its most general unifiers with the cautious consequences of
|
|
112
|
+
the full answer sets: yes where it unifies with one, no where its contrary does. The brave
|
|
113
|
+
consequences give only the unknown instances (entertained, neither yes nor no). A ground literal
|
|
114
|
+
is yes when cautious and no when its contrary is. A conjunction or disjunction is still answered
|
|
115
|
+
over each answer set. The answers are unchanged; how they are found is not.
|
|
116
|
+
- Limits are named for what they bound: `--max-seconds` (was `--budget`) and `--max-answer-sets` (was
|
|
117
|
+
`--models`), `Limits.max_seconds` and `Limits.max_answer_sets`, `DEFAULT_MAX_SECONDS` and
|
|
118
|
+
`DEFAULT_MAX_ANSWER_SETS`, and the truncations `MAX_SECONDS` and `MAX_ANSWER_SETS`. `UNLIMITED` is gone: use `Limits()`.
|
|
119
|
+
- One answer set is an answer set everywhere: the tag `@answer-set` (was `@model`), the types `AnswerSet` (was
|
|
120
|
+
`Model`) and `AnswerSets` (was `Models`), `Atoms` (was `AnswerSet`), `Optimum.answer_sets`,
|
|
121
|
+
`Incumbent.answer_set`, `Seen.answer_set` and `search.answer_sets()`, and the same keys in the JSON.
|
|
122
|
+
The old names are gone.
|
|
123
|
+
- Every enum value in the JSON is snake_case, and each is its member's name in lower case, so the
|
|
124
|
+
truncations are `max_answer_sets`, `max_seconds` and `interrupted`; the command-line flags keep their
|
|
125
|
+
hyphens.
|
|
126
|
+
- The `kind` of every JSON object is its type's name in snake case (`report`, `entails`, `answer_set`), derived in
|
|
127
|
+
one place, `json_view.kind_of`.
|
|
128
|
+
- A search cut short is worded for people by the text view: `search ended: answer-set limit reached
|
|
129
|
+
(--max-answer-sets)`, `time limit reached (--max-seconds)` or `interrupted`, where the text said
|
|
130
|
+
`max_answer_sets`. A finding's `detail` is text or `Ended` (the `Truncation`, and whether an optimum went
|
|
131
|
+
unproven), which is an object in the JSON; `Detail` and `Ended` are exported. The option names come from
|
|
132
|
+
`determination.option`, which the command line uses too.
|
|
133
|
+
|
|
134
|
+
- Diagnostics for a source that could not be checked go to stderr, with the reserved-command note
|
|
135
|
+
and the internal-error line; verdicts and the summary stay on stdout. `--json` is all on stdout.
|
|
136
|
+
- The terminal text is `elenctic.human`, the JSON is `elenctic.json_view` (`encode`, moved from
|
|
137
|
+
`elenctic.report`, which holds the run's values); escape sequences are written by `elenctic.paint`
|
|
138
|
+
alone.
|
|
139
|
+
- `pixi run -e theory check` is the one documented gate; `check.sh` only runs it. `uv.lock` is tracked.
|
|
140
|
+
The CI uv job is a secondary install check (`uv sync --locked`, then the tests).
|
|
141
|
+
- `--max-seconds` and `--max-answer-sets` take a positive number or the word `all` (no limit); `--max-answer-sets` defaults to
|
|
142
|
+
100,000 (`DEFAULT_MAX_ANSWER_SETS`), so a search that would read more is `undecided`; `--max-answer-sets all` lifts
|
|
143
|
+
it. The library default stays `Limit.UNBOUNDED`. `0` and negative values are a usage error: `--max-seconds 0` used to mean no limit and `--max-answer-sets 0`
|
|
144
|
+
an immediate cut. `Limits.max_seconds` and `Limits.max_answer_sets` are a number or `Limit.UNBOUNDED`, replacing
|
|
145
|
+
`None`.
|
|
146
|
+
- A refutation by assumptions whose core may not be minimal (the search was cut short) now says so.
|
|
147
|
+
- `@cost` over `#maximize` is accepted: costs are clingo's minimized sums, so a maximized level is
|
|
148
|
+
negated.
|
|
149
|
+
- A query that reads a literal the program can derive and does not `#show` is noted (a lint on the
|
|
150
|
+
report), not refused; the note does not change the exit. The old refusal of such queries, and the
|
|
151
|
+
refusal of a `no`/`unknown` query whose contrary was derivable and unshown, are gone: full
|
|
152
|
+
answer-set evaluation makes both gates redundant for correctness. A later `--strict` may promote
|
|
153
|
+
the notes to faults; that flag is not built yet.
|
|
154
|
+
- A theory atom with no `#theory` declaration is reported with elenctic's own message and location
|
|
155
|
+
rather than clingo's.
|
|
156
|
+
- The theory-atom error points at the first theory atom, not at line 1.
|
|
157
|
+
|
|
158
|
+
### Removed
|
|
159
|
+
- `Pattern.match`, `Pattern.bindings_in` and the module `elenctic.matching`, a one-sided matcher of a
|
|
160
|
+
pattern against ground symbols. Query answering unifies instead (`unify`, the algorithm of `mgu`);
|
|
161
|
+
there is one mechanism for "does this pattern have this instance".
|
|
162
|
+
|
|
163
|
+
### Fixed
|
|
164
|
+
- Reference: `elenctic version` exits `REPORTED` (0), not `CLAIMS_HOLD`.
|
|
165
|
+
- A binding query with an anonymous variable, `@query yes { p(_, X) } = { … }`, is refused where it
|
|
166
|
+
is read (`FAULT`, 2: "an anonymous variable has no binding to state: name it") instead of
|
|
167
|
+
failing inside elenctic (`INTERNAL_ERROR`, 3); `bindings` raises `ValueError` for it. An instance
|
|
168
|
+
of such a pattern is not ground, so it has no answer to give.
|
|
169
|
+
- Documented that an objective only ranks answer sets and never removes any: unqualified claims and
|
|
170
|
+
every `@query` read all answer sets, and only `optimal` claims and `@cost` read the optimal ones
|
|
171
|
+
(`docs/explanation/semantics.md`, which the reference and the guides point to). The behavior is unchanged.
|
|
172
|
+
- Queries evaluate over the full answer sets, not the shown projection: `-b. #show b/0.` with
|
|
173
|
+
`@query yes { -b }` is yes, and `-p. #show p/0.` with `@query yes { p }` reports no (not unknown).
|
|
174
|
+
- An empty `no` binding is no longer a vacuous pass when a contrary holds:
|
|
175
|
+
`-p(1). #show p/1.` with `@query no { p(X) } = { }` fails; the no-set is `{1}`.
|
|
176
|
+
- A variable `#show X : q(X).` no longer makes every predicate "shown" for coverage checks, and a
|
|
177
|
+
conditional `#show p : r.` no longer counts as unconditionally showing `p`.
|
|
178
|
+
- `Program.read` accepts any path-like (`str | PathLike`).
|
|
179
|
+
- A search is about 2.3 times faster: an answer set is read into literals only once it is kept, and each
|
|
180
|
+
clingo symbol is converted once.
|
|
181
|
+
- Native `@cautious`/`@brave` under a limit on answer sets are decided from the solver, not the cut search.
|
|
182
|
+
- A minimal core is tested on a non-trivial case and under a time limit (`minimal=False`).
|
|
183
|
+
- A native `@cautious`/`@brave` claim is judged only from a search that ran to its end; if the search
|
|
184
|
+
was cut by a limit on seconds or answer sets, or by an interrupt, it is undecided, where it was reported as a pass or a
|
|
185
|
+
fail on the partial set.
|
|
186
|
+
- A term nested thousands deep in a theory atom, an objective or a `#show` is checked or refused
|
|
187
|
+
instead of raising `RecursionError`.
|
|
188
|
+
- Program text in the terminal report is shown with control characters escaped, so a program cannot
|
|
189
|
+
write terminal control sequences; JSON is unchanged.
|
|
190
|
+
- A file that is not UTF-8 is a refusal of the request (`FAULT`, 2), not a traceback.
|
|
191
|
+
- `pixi run check` prints the number of tests: pytest was given `-q` twice.
|
|
192
|
+
|
|
193
|
+
[1.0.0]: https://github.com/GregoryGelfond/elenctic/releases/tag/v1.0.0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
# Minimal image whose only purpose is running elenctic (CLI entrypoint; clingo and clingcon included).
|
|
3
|
+
#
|
|
4
|
+
# Build: docker build -f Containerfile -t elenctic .
|
|
5
|
+
# Run: docker run --rm -v "$PWD":/work:ro elenctic [elenctic arguments]
|
|
6
|
+
# No published image; no CI build — ship the recipe only.
|
|
7
|
+
|
|
8
|
+
FROM docker.io/library/python:3.14-slim-trixie@sha256:af8a5aa14780025dbfbcfbc81bd8a91ebd87711dd5b0f48ed71090949dd7cb1d AS build
|
|
9
|
+
COPY --from=docker.io/astral/uv:0.13.7@sha256:96f40d3904aad8d0bd010539001d36c8e2188b981e1334471a90c4a1d93bb490 /uv /bin/uv
|
|
10
|
+
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=never
|
|
11
|
+
WORKDIR /src
|
|
12
|
+
COPY pyproject.toml uv.lock README.md ./
|
|
13
|
+
COPY src ./src
|
|
14
|
+
COPY docs/reference/output-v1.schema.json ./docs/reference/output-v1.schema.json
|
|
15
|
+
RUN uv venv /opt/elenctic \
|
|
16
|
+
&& UV_PROJECT_ENVIRONMENT=/opt/elenctic uv sync --frozen --no-dev --extra theory --no-editable \
|
|
17
|
+
&& /opt/elenctic/bin/python -c "import clingo, clingcon; import elenctic"
|
|
18
|
+
|
|
19
|
+
FROM docker.io/library/python:3.14-slim-trixie@sha256:af8a5aa14780025dbfbcfbc81bd8a91ebd87711dd5b0f48ed71090949dd7cb1d
|
|
20
|
+
COPY --from=build /opt/elenctic /opt/elenctic
|
|
21
|
+
ENV PATH=/opt/elenctic/bin:$PATH
|
|
22
|
+
USER 65534:65534
|
|
23
|
+
WORKDIR /work
|
|
24
|
+
ENTRYPOINT ["elenctic"]
|
elenctic-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gregory Gelfond
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
elenctic-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: elenctic
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Contracts and queries for answer set programs: a library first, a command line second
|
|
5
|
+
Project-URL: Homepage, https://github.com/GregoryGelfond/elenctic
|
|
6
|
+
Project-URL: Documentation, https://github.com/GregoryGelfond/elenctic/blob/main/docs/index.md
|
|
7
|
+
Project-URL: Changelog, https://github.com/GregoryGelfond/elenctic/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/GregoryGelfond/elenctic/issues
|
|
9
|
+
Author-email: Gregory Gelfond <gregory_gelfond@fastmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Python: >=3.14
|
|
13
|
+
Requires-Dist: clingo<5.9,>=5.8.2
|
|
14
|
+
Provides-Extra: theory
|
|
15
|
+
Requires-Dist: clingcon<5.3,>=5.2.1; extra == 'theory'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# elenctic
|
|
19
|
+
|
|
20
|
+
[](https://github.com/GregoryGelfond/elenctic/actions/workflows/ci.yml)
|
|
21
|
+
[](https://www.python.org/downloads/)
|
|
22
|
+
[](https://github.com/astral-sh/ruff)
|
|
23
|
+
[](https://mypy-lang.org/)
|
|
24
|
+
[](https://github.com/astral-sh/uv)
|
|
25
|
+
[](https://pixi.sh)
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
[](https://pypi.org/project/elenctic/)
|
|
28
|
+
<!-- TODO(conda-forge): enable once the package is available on conda-forge.
|
|
29
|
+
[](https://anaconda.org/conda-forge/elenctic)
|
|
30
|
+
-->
|
|
31
|
+
|
|
32
|
+
A testing and analysis library for answer set programs, with a thin CLI. Claims about a program
|
|
33
|
+
live in its own comments (`@expect`, `@count`, `@answer-set`, `@cautious`, `@brave`, `@query`,
|
|
34
|
+
`@cost`) and are checked under clingo or clingcon.
|
|
35
|
+
|
|
36
|
+
*Elenctic*, from Greek ἐλεγκτικός (ἔλεγχος, *elenchos*), "serving to refute by
|
|
37
|
+
cross-examination": the Socratic elenchus. A contract is a thesis about the program; `FAIL` is
|
|
38
|
+
its refutation, and `UNDECIDED` is aporia, an honest "cannot say" rather than a false "no".
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
Requires Python 3.14+ and clingo 5.8. The optional `theory` extra pulls in clingcon 5.2 for
|
|
43
|
+
contracts that declare `% @elenctic solver clingcon`.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# pip
|
|
47
|
+
pip install elenctic
|
|
48
|
+
pip install 'elenctic[theory]'
|
|
49
|
+
|
|
50
|
+
# uv — as a project dependency, or as a tool on PATH
|
|
51
|
+
uv add elenctic
|
|
52
|
+
uv add 'elenctic[theory]'
|
|
53
|
+
uv tool install elenctic
|
|
54
|
+
uv tool install 'elenctic[theory]'
|
|
55
|
+
|
|
56
|
+
# pixi — from PyPI
|
|
57
|
+
pixi add --pypi elenctic
|
|
58
|
+
pixi add --pypi 'elenctic[theory]'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
<!-- TODO(conda-forge): enable once the package is available on conda-forge.
|
|
62
|
+
```bash
|
|
63
|
+
# pixi — from conda-forge
|
|
64
|
+
pixi add elenctic
|
|
65
|
+
|
|
66
|
+
# conda / mamba (conda-forge)
|
|
67
|
+
conda install -c conda-forge elenctic
|
|
68
|
+
mamba install -c conda-forge elenctic
|
|
69
|
+
```
|
|
70
|
+
-->
|
|
71
|
+
|
|
72
|
+
From source, straight from Git or from a clone:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install 'elenctic[theory] @ git+https://github.com/GregoryGelfond/elenctic'
|
|
76
|
+
|
|
77
|
+
git clone https://github.com/GregoryGelfond/elenctic && cd elenctic
|
|
78
|
+
pip install '.[theory]'
|
|
79
|
+
uv tool install '.[theory]'
|
|
80
|
+
pixi install -e theory # the locked environment
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Container
|
|
84
|
+
|
|
85
|
+
No prebuilt image is published. The `Containerfile` is a local recipe: build it yourself with
|
|
86
|
+
Docker or Podman, then mount a directory of `.lp` files and run a check.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
docker build -f Containerfile -t elenctic .
|
|
90
|
+
podman build -f Containerfile -t elenctic .
|
|
91
|
+
|
|
92
|
+
docker run --rm -v "$PWD":/work:ro elenctic drinks.lp
|
|
93
|
+
podman run --rm -v "$PWD":/work:ro elenctic drinks.lp
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The image includes the `theory` extra (clingo and clingcon). The entrypoint is `elenctic`; pass
|
|
97
|
+
the same arguments you would on the host.
|
|
98
|
+
|
|
99
|
+
## Quick start
|
|
100
|
+
|
|
101
|
+
Save this as `drinks.lp`:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
% @expect sat
|
|
105
|
+
% @count 2
|
|
106
|
+
% @brave { tea, coffee }
|
|
107
|
+
% @cautious { biscuit }
|
|
108
|
+
% @query unknown { tea }
|
|
109
|
+
{ tea; coffee } = 1.
|
|
110
|
+
biscuit.
|
|
111
|
+
#show tea/0.
|
|
112
|
+
#show coffee/0.
|
|
113
|
+
#show biscuit/0.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
$ elenctic drinks.lp
|
|
118
|
+
1/1 passed
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Change `@count 2` to `@count 3` and run again:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
$ elenctic drinks.lp
|
|
125
|
+
drinks.lp — FAIL
|
|
126
|
+
[FAIL] @count 3 (line 2): 3 expected, 2 distinct shown answer sets
|
|
127
|
+
|
|
128
|
+
0/1 passed, 1 fail
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`elenctic PATH…` is `elenctic test PATH…`. From Python the same check is silent and returns a
|
|
132
|
+
value:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from elenctic import Limits, Program, check
|
|
136
|
+
|
|
137
|
+
report = check(Program.read("drinks.lp"), limits=Limits(max_seconds=10))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Usage
|
|
141
|
+
|
|
142
|
+
| | |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `elenctic [test] PATH…` | check programs against their contracts ([reference](docs/reference/reference.md#command-line)) |
|
|
145
|
+
| `--format full\|short\|json` | rustc multi-line diagnostics (default), one-line short, or JSON ([output](docs/reference/reference.md#output), [diagnostics](docs/reference/reference.md#diagnostics)) |
|
|
146
|
+
| Exit `0` / `1` / `2` / `3` | claims hold, claim unmet, fault, internal error ([exit codes](docs/reference/reference.md#exit-codes)) |
|
|
147
|
+
|
|
148
|
+
Claims with real command output: [docs/reference/examples.md](docs/reference/examples.md).
|
|
149
|
+
Tutorial: [docs/tutorial/first-contract.md](docs/tutorial/first-contract.md). Full map:
|
|
150
|
+
[docs/index.md](docs/index.md).
|
|
151
|
+
|
|
152
|
+
## Develop
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pixi install -e theory
|
|
156
|
+
pixi run -e theory check # format, lint, strict types, import contracts, tests
|
|
157
|
+
pixi run -e py314 test # or -e py315
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`pixi run check` is the one gate; `./check.sh` runs it. `uv.lock` is tracked so
|
|
161
|
+
`uv sync --locked` reproduces the same dependencies.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
Licensed under the [MIT license](LICENSE). Changes are in the [changelog](CHANGELOG.md).
|