Repairing unrealisable specifications
  • Python 78.7%
  • C++ 19.4%
  • CMake 0.8%
  • HTML 0.5%
  • JavaScript 0.2%
  • Other 0.4%
Find a file
Ben Andrew 66e913eb00
Merge pull request #182 from benmandrew/feat/config-cleanup
feat(config): retire twelve config keys and move four defaults
2026-09-11 20:03:42 +01:00
.claude/skills docs(build): drop the internal Doxygen reference, publish include/ only 2026-08-06 15:20:15 +01:00
.githooks chore: track the git hooks and install them at configure time 2026-07-30 15:11:59 +01:00
.github/workflows ci: stop the arm64 docker cache from evicting build caches 2026-09-03 13:19:30 +01:00
bench refactor(prop_formula): share Formula's node arena via shared_ptr 2026-08-21 00:02:41 +01:00
cmake build: fix black on non-x86_64 Linux 2026-08-27 18:25:24 +01:00
docker feat(vm): build a VirtualBox appliance from the Docker image 2026-09-11 15:08:34 +01:00
docs Merge remote-tracking branch 'origin/main' into feat/config-cleanup 2026-09-11 19:55:42 +01:00
examples feat(requirement): implement FRETISH scopes (#14) 2026-09-01 17:17:16 +01:00
experiments feat(config): retire six more config keys and hard-code their defaults 2026-09-11 19:41:58 +01:00
fuzz feat(requirement): implement FRETISH scopes (#14) 2026-09-01 17:17:16 +01:00
include refactor(ganak): delete the timeout path left dead by the untimed exec 2026-09-11 19:48:24 +01:00
schemas feat(config): retire six more config keys and hard-code their defaults 2026-09-11 19:41:58 +01:00
scripts Merge remote-tracking branch 'origin/main' into feat/config-cleanup 2026-09-11 19:55:42 +01:00
src Merge remote-tracking branch 'origin/main' into feat/config-cleanup 2026-09-11 19:55:42 +01:00
test Merge remote-tracking branch 'origin/main' into feat/config-cleanup 2026-09-11 19:55:42 +01:00
vendor fix: stream formaliser batch output per line; test against real CLI 2026-07-06 17:58:35 +01:00
web feat: label each stage with the time it took 2026-07-30 15:34:51 +01:00
.clang-format feat: add cpptrace crash logging with stacktraces 2026-06-15 12:49:49 +01:00
.clang-tidy chore(lint): enable bugprone-exception-escape 2026-08-07 18:01:02 +01:00
.cppcheck_suppressions.txt feat(runner): port process spawning and profiling to macOS/arm64 2026-08-20 14:36:58 +01:00
.cpplint.cfg refactor: rename linter dotfiles 2026-06-16 20:07:27 +01:00
.dockerignore feat(docker): package counter as a container image 2026-08-27 18:27:04 +01:00
.envrc feat: add Nix dev shell as primary development workflow 2026-07-03 16:23:28 +01:00
.gitignore docs(scripts): correct what scripts/ says about itself 2026-08-22 00:56:14 +01:00
.taplo.toml feat: consolidate schemas/ directory with taplo config 2026-06-26 13:49:03 +01:00
analyse.sh feat: rework fsm/fsm-timing examples with mutual-exclusion invariants, add fsm-combined scenario 2026-07-09 14:21:29 +01:00
CLAUDE.md Merge remote-tracking branch 'origin/main' into feat/config-cleanup 2026-09-11 19:55:42 +01:00
CMakeLists.txt Merge pull request #180 from benmandrew/feat/fretish-implication 2026-09-11 16:03:34 +01:00
CMakePresets.json feat(coverage): commit a line-coverage badge and check it in CI 2026-08-20 16:11:58 +01:00
Dockerfile feat(docker): package counter as a container image 2026-08-27 18:27:04 +01:00
example-config.toml feat(config): retire six more config keys and hard-code their defaults 2026-09-11 19:41:58 +01:00
EXPERIMENTS.md style(docs): unwrap hard-wrapped paragraphs in Markdown and reStructuredText 2026-08-07 14:13:37 +01:00
flake.lock ci: switch to nix flake dev shell 2026-07-03 16:28:54 +01:00
flake.nix feat(coverage): commit a line-coverage badge and check it in CI 2026-08-20 16:11:58 +01:00
LICENCE chore: Add LICENCE 2026-06-11 16:28:08 +01:00
README.md feat(maximal): score fretish repair directories for maximality 2026-09-11 16:38:48 +01:00

Counter

Coverage

Counter repairs unrealisable reactive specifications using a genetic algorithm.

A reactive specification is unrealisable when no implementation can satisfy it against every environment — the specification is at fault, not the code. Tools like ltlsynt will say so, but not what to change. Counter searches for the repairs: edits to the specification that make it realisable while staying as close as possible to what was originally written.

Inputs are either FRETISH requirements as JSON, or basic TLSF — the Temporal Logic Synthesis Format used by the reactive-synthesis community.

Quickstart

Build, then repair one of the bundled examples (building from source covers the non-Nix route):

$ nix develop
$ cmake --workflow --preset release

$ ./build-release/realize examples/lily02/spec.tlsf
UNREALIZABLE

$ mkdir -p out
$ ./build-release/counter --input examples/lily02/spec.tlsf --output-dir out --seed 42
Realizable specifications: 11 (3 maximal), written to out/
Done in 4.12s

That writes the 3 maximal repairs to out/, each repair_N.tlsf paired with a repair_N.fitness.json holding its score. Expect a few seconds on 20 threads; the seed fixes the repairs, not the runtime, which swings with how the external solvers get scheduled.

The container image is the alternative to nix develop: it carries the binaries, the solvers and these examples, so docker run --rm counter:<tag> realize /opt/counter/share/counter/examples/lily02/spec.tlsf needs no toolchain on the host.

The example is a grant arbiter that must answer every request within three ticks, never grant twice in a row, and withhold grants after a cancel until a go arrives. Nothing forces go to ever arrive, so a cancelled request can be neither granted nor refused — and the specification cannot be implemented. All three repairs rewrite that third guarantee, which is also the one mucs identifies as the minimal unrealisable core. The TLSF guide walks through this run in full.

Commands

Command Purpose
counter --input <spec> --output-dir <dir> repair an unrealisable specification
realize <spec>... report whether a specification is realisable
ltl <spec>... print the LTL formulae a specification translates to
compare --repairs <dir> --ideals <dir> compare repairs against known-ideal ones
mucs <spec.tlsf> extract a minimal unrealisable core from a TLSF spec
maximal <dir-or-file>... report which specifications of a set are maximal under the implication order

A <spec> is either a FRETISH .json or a .tlsf file.

Run any command with --help for full option descriptions.

How it works

Counter evolves a population of candidate specifications over several generations, keeping those that are realisable and close to the original.

  1. Seed a population of specifications, each mutated slightly from the input.
  2. Score each candidate on three weighted components: semantic similarity (bounded model counting of satisfying traces), realisability status, and syntactic similarity.
  3. Evolve through rounds of selection, crossover, mutation, and filtering.
  4. Collect the realisable survivors, keep the genuine weakenings of the original and, of those, only the maximal ones under implication, then write each to the output directory.

Model counting uses Ganak over the transition matrices of SPOT-generated automata. Satisfiability and realisability queries use black and ltlsynt respectively.

Documentation

The full documentation is published at benmandrew.com/docs/counter.

Building from source Nix and non-Nix builds, dependencies, presets, tests
Docker building and running the container image
Virtual machine building a VirtualBox appliance from the image
Architecture algorithm flow, key types, module layout
Configuration tuning via TOML, fitness weights, selection schemes
TLSF specifications TLSF mode and a worked repair
API reference the include/ headers
Experiment scripts parameter sweeps and result analysis