{"id":51112919,"url":"https://github.com/saviorand/scasp-clj","last_synced_at":"2026-06-24T19:30:20.216Z","repository":{"id":364542097,"uuid":"1259679403","full_name":"saviorand/scasp-clj","owner":"saviorand","description":"Clojure port of the s(CASP) (Goal directed Constraint Answer Set Programming) system","archived":false,"fork":false,"pushed_at":"2026-06-13T11:36:18.000Z","size":626,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T13:18:12.076Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saviorand.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-04T18:46:13.000Z","updated_at":"2026-06-13T11:37:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/saviorand/scasp-clj","commit_stats":null,"previous_names":["saviorand/scasp-clj"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/saviorand/scasp-clj","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saviorand%2Fscasp-clj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saviorand%2Fscasp-clj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saviorand%2Fscasp-clj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saviorand%2Fscasp-clj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saviorand","download_url":"https://codeload.github.com/saviorand/scasp-clj/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saviorand%2Fscasp-clj/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34747387,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-24T19:30:18.979Z","updated_at":"2026-06-24T19:30:20.210Z","avatar_url":"https://github.com/saviorand.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# scasp-clj\n\nA Clojure port of [s(CASP)](https://gitlab.software.imdea.org/ciao-lang/sCASP) — a top-down, goal-directed Answer Set Programming solver that works without grounding.\n\ns(CASP) extends stable-model ASP with:\n- Negation-as-failure (NAF) via constructive coinduction\n- Strong (classical) negation\n- Constraint Logic Programming over reals (CLP(R))\n- Abductive reasoning\n- Inductive learning via FOLD-R\n\nPrograms are represented as Clojure data structures — no Prolog parser is included.\n\n## Installation\n\nAdd to your `deps.edn`:\n\n```clojure\n;; coming soon — clone and use as a local dep in the meantime\n{:deps {scasp-clj/scasp-clj {:local/root \"../scasp-clj\"}}}\n```\n\nRequires Clojure 1.12+.\n\n## Quick start\n\n```clojure\n(require '[scasp.main :as scasp]\n         '[scasp.program :as prog])\n\n;; Term helpers\n(defn compound [op \u0026 args] {:op op :args (vec args)})\n(defn rule [head \u0026 body]   (prog/make-rule head (vec body)))\n(defn naf  [g]             {:op :not :args [g]})\n\n;; Classic bird / penguin default-reasoning example\n(def rules\n  [(rule (compound :flies \"X\")  (compound :bird \"X\") (naf (compound :ab \"X\")))\n   (rule (compound :ab \"X\")     (compound :penguin \"X\"))\n   (rule (compound :bird \"X\")   (compound :penguin \"X\"))\n   (rule (compound :bird :tweety))\n   (rule (compound :penguin :sam))])\n\n(scasp/solve-all rules [(compound :flies \"X\")])\n;=\u003e one result — X = :tweety  (sam is a penguin, so ab(sam) blocks flies(sam))\n```\n\n## Core API (`scasp.main`)\n\n```clojure\n;; Build + run in one step (returns lazy seq of result maps)\n(scasp/solve rules query)\n(scasp/solve rules query abducibles)          ; abducibles — set of functor strings e.g. #{\"fly/1\"}\n(scasp/solve rules query abducibles opts)     ; opts — {:no-olon true, :no-nmr true}\n\n;; Convenience wrappers\n(scasp/solve-all rules query)                 ; eagerly collect all answers (may not terminate)\n(scasp/solve-n n rules query)                 ; take at most n answers\n\n;; Build a compiled program separately (useful for inspecting or reusing)\n(scasp/build-program rules query)\n(scasp/build-program rules query abducibles opts)\n\n;; Extract variable bindings from a result\n(scasp/result-bindings result [\"X\" \"Y\"])      ;=\u003e {\"X\" :tweety, \"Y\" ...}\n\n;; Print answers to stdout (Prolog style)\n(scasp/print-results results query-goals)\n```\n\nEach result map contains:\n- `:var-env` — variable bindings (pass to `scasp.vars/fill-in` or `result-bindings`)\n- `:chs` — the Coinductive Hypothesis Set (selected literals in the answer set)\n- `:just` — justification tree\n- `:even-loops` — coinductive loop info\n\n## Term representation\n\n| Prolog | Clojure |\n|--------|---------|\n| `foo` (atom) | `:foo` (keyword) |\n| `X` (variable) | `\"X\"` (string) |\n| `42`, `3.14` | `42`, `3.14` |\n| `f(X, a)` | `{:op :f :args [\"X\" :a]}` |\n| `not p(X)` | `{:op :not :args [{:op :p :args [\"X\"]}]}` |\n| `-p(X)` (strong neg) | `{:op :sneg :args [inner]}` |\n| `[H\\|T]` | `{:op :cons :args [H T]}` |\n| `[]` | `(keyword \"[]\")` |\n\n## Features\n\n### Negation-as-failure\n\n```clojure\n;; p(X) :- bird(X), not ab(X).\n(rule (compound :p \"X\") (compound :bird \"X\") (naf (compound :ab \"X\")))\n```\n\n### CLP(R) — constraint arithmetic\n\n```clojure\n;; Goals: X \u003e 0, X \u003c 10  →  X remains unbound with interval (0, 10)\n(scasp/solve-all [] [(compound :\u003e \"X\" 0) (compound :\u003c \"X\" 10)])\n\n;; Supported operators: \u003c \u003e =\u003c \u003e= =:= =\\= .\u003c. .\u003e. .=\u003c. .\u003e=. .=. .\u003c\u003e. #\u003c #\u003e #=\u003c #\u003e= #= #\u003c\u003e\n;; is/2 evaluates arithmetic: X is 2 + 3  →  X = 5\n```\n\n### Strong negation\n\n```clojure\n;; -flies(X) :- ab(X).   (classical/explicit negation)\n(rule (prog/make-compound \"-flies\" [\"X\"]) (compound :ab \"X\"))\n;; Consistency axiom :- flies(X), -flies(X). is added automatically.\n```\n\n### Abduction\n\n```clojure\n;; Mark a predicate as abducible — it can be assumed true with no rules\n(scasp/solve-all rules [goal] #{\"fly/1\"})\n```\n\n### Induction (FOLD-R)\n\n```clojure\n(require '[scasp.inference :refer [inference]])\n\n(inference :induction ontology :white)\n;=\u003e {:positive-rules [{white(X) :- from_s1(X)}]\n;    :exception-rules []}\n```\n\n### Unified inference API (`scasp.inference`)\n\n```clojure\n(require '[scasp.inference :refer [inference print-justification]])\n\n(doseq [r (inference ontology goal)]             ; deduction\n  (print-justification r))\n\n(doseq [r (inference :abduction ontology goal)]  ; abduction (open predicates auto-detected)\n  (print-justification r))\n\n(inference :induction ontology :target-predicate) ; induction via FOLD-R\n```\n\n## Semantics \u0026 divergence from upstream\n\nThis engine targets the same semantics as Ciao/SWI s(CASP), but a few points differ. Reference behavior was checked empirically against **SWI s(CASP) 1.1.4** and the Ciao algorithm variants.\n\n- **Constructive negation over existentials follows Ciao's *sound* `forall`, not SWI's default.** SWI's default (`scasp_forall=all`) is **unsound** on `not p` where `p` is a rule with multiple existential body variables. Only Ciao's `--prev_forall` / `--sasp_forall` are sound there. This engine matches the **sound** behavior — on such programs our answer set can differ from what the default SWI CLI prints, intentionally.\n\n- **No vacuous-truth `forall`.** `forall(V, G)` fails if `G` has no solution with `V` free. Matches Ciao `solve_forall`.\n\n- **Negation of an undefined predicate succeeds.** An undefined `q` never holds (completion), so `not q(X)` holds universally. Matches s(CASP).\n\n- **No Prolog parser (yet).** Programs are built as Clojure data structures.\n\n## Performance\n\nThe solver uses **first-argument clause indexing**, so query cost does not grow with knowledge base size — only with the work the proof actually does.\n\n| Phase | Cost | Notes |\n|---|---|---|\n| **Build** (`build-program`) | linear, ≈ 3.5 µs/fact | One-time; dominated by dual generation. |\n| **Solve** (per query) | independent of KB size | First-argument indexing prunes non-matching clauses. |\n\nMeasured on a single-predicate fact base (Apple M-series, JVM):\n\n| Facts | Build | Solve |\n|------:|------:|------:|\n| 10k | ~90 ms | 0.2 ms |\n| 100k | ~0.5 s | 0.3 ms |\n| 1M | ~3.5 s | 0.2 ms |\n\n- **Large fact bases queried positively scale well** — ground/indexed lookups are effectively O(1).\n- **Compilation is the upfront tax.** Build once, run many queries against it.\n- **Deep recursion is the current limit.** Loop detection scans the call stack per call, making recursive proofs roughly O(depth²).\n\nBenchmark harness: `bench/bench.clj`, `bench/bigbuild.clj` —\n`clj -Sdeps '{:paths [\"src\" \"resources\" \"bench\"]}' -M -m bench`.\n\n## Running the tests\n\n```\nclj -X:test\n```\n\n## Known limitations\n\n- **No Prolog parser.** Programs must be constructed as Clojure data. A parser is the next major addition.\n- **CLP(R) propagation across rule boundaries is incomplete.** Constraints like `X \u003e Y` stored on `X` do not automatically re-fire when `Y` is bound by a later rule body goal.\n- **`not(Comparison)` with an unbound variable is not handled.** `not(X \u003e 3)` with unbound `X` should add `X =\u003c 3` as a CLP constraint but currently fails. Use the flipped form instead: `X =\u003c 3`.\n- **`\\=` (disequality) requires at least one ground argument.** Both-unbound `X \\= Y` throws rather than deferring.\n\n## Architecture\n\n| File | Role |\n|------|------|\n| `term.clj` | Term types, operator table, pretty-printing |\n| `vars.clj` | Variable environment (union-find, CLP(R) numeric bounds, relational constraint store) |\n| `unify.clj` | Structural unification and disequality |\n| `program.clj` | In-memory program database, rule indexing, abducibles |\n| `duals.clj` | Dual rule compilation (NAF semantics without grounding) |\n| `nmr.clj` | OLON detection (path-based DFS), NMR sub-check generation |\n| `chs.clj` | Coinductive Hypothesis Set — loop detection, coinductive success/failure |\n| `solver.clj` | Core solver, CLP(R) dispatch, `findall/3`, `call/N`, `forall/2`, DCC |\n| `output.clj` | Answer set formatting, justification trees |\n| `main.clj` | Public API |\n| `inference.clj` | Unified deduction / abduction / induction API |\n| `fold.clj` | FOLD-R inductive logic programming |\n\nSee [DESIGN.md](DESIGN.md) for internal architecture and invariants.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaviorand%2Fscasp-clj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaviorand%2Fscasp-clj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaviorand%2Fscasp-clj/lists"}