{"id":18008594,"url":"https://github.com/mgree/kmt","last_synced_at":"2025-03-26T12:32:10.128Z","repository":{"id":37614017,"uuid":"149648258","full_name":"mgree/kmt","owner":"mgree","description":"Kleene algebra modulo theories","archived":false,"fork":false,"pushed_at":"2022-11-21T18:00:59.000Z","size":252,"stargazers_count":27,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-21T19:12:53.598Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mgree.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-20T17:48:53.000Z","updated_at":"2024-11-13T20:51:47.000Z","dependencies_parsed_at":"2023-01-21T11:46:34.892Z","dependency_job_id":null,"html_url":"https://github.com/mgree/kmt","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgree%2Fkmt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgree%2Fkmt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgree%2Fkmt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgree%2Fkmt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mgree","download_url":"https://codeload.github.com/mgree/kmt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245654354,"owners_count":20650852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":"2024-10-30T01:19:15.122Z","updated_at":"2025-03-26T12:32:09.792Z","avatar_url":"https://github.com/mgree.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Main workflow](https://github.com/mgree/kmt/actions/workflows/build.yml/badge.svg)](https://github.com/mgree/kmt/actions/workflows/build.yml)\n[![DOI](https://zenodo.org/badge/149648258.svg)](https://zenodo.org/badge/latestdoi/149648258)\n\nThis repository implements [Kleene algebra modulo\ntheories](https://arxiv.org/abs/1707.02894) (KMT), a framework for\nderiving _concrete_ Kleene algebras with tests (KATs), an algebraic\nframework for While-like programs with decidable program equivalence.\n\nMore plainly: KMT is a framework for building simple programming\nlanguages with structured control (if, while, etc.) where we can\nalgorithmically decide whether or not two programs are equivalent. You\ncan use equivalence to verify programs. If `a` is a nice property to\nhave after running your program, then if `p;a == p`, you know that `p`\nsatisfies `a`. Kleene algebra with tests subsumes Hoare logic: if\n`a;p;~b == 0` then all runs starting from `a` either diverge or end\nwith `b`, i.e., that equation corresponds to the partial correctness\nspecification `{a} p {b}`. While prior work on KAT often focuses on\n_abstract_ properties, we write programs over theories that assign\n_concrete_ meanings to primitive tests and actions.\n\nIn addition to providing an OCaml library for defining KMTs over your\nown theories, we offer a command-line tool for testing equivalence in\na variety of pre-defined theories.\n\n# Getting Started Guide\n\n## How do I build it?\n\nKMT is [available on OPAM](https://opam.ocaml.org/packages/kmt/); if you have OCaml and [OPAM](https://opam.ocaml.org/) installed, œyou should be able to simply run:\n\n```ShellSession\n$ opam install kmt\n```\n\nBut you can also build a Docker container from the root of the repo:\n\n```ShellSession\n$ docker build -t kmt .    # build KMT, run tests and evaluation\n```\n\nIf your `docker build` command exits with status 137, that indicates\nthat the build ran out of memory (typically when building Z3). We find\nthat 12GB of RAM is sufficient, but more may be necessary on your\nmachine. You might have to reconfigure Docker to have sufficient memory.\n\nBuilding the image will automatically run unit tests as well as the\nPLDI 2022 evaluation. When running the image, you can use the `kmt`\nexecutable to test equivalence of various terms directly:\n\n```ShellSession\n$ docker run -it kmt       # enter a shell\nopam@b3043b7dca44:~/kmt$ kmt --boolean 'x=T' 'x=T + x=F;set(x,F);x=T'\n[x=T parsed as x=T]\nnf time: 0.000004s\nlunf time: 0.000022s\n[x=T + x=F;set(x,F);x=T parsed as x=T + x=F;set(x,F)[1];x=T]\nnf time: 0.000008s\nlunf time: 0.000006s\n[1 equivalence class]\n1: { x=T + x=F;set(x,F);x=T, x=T }\n```\n\nThe message `1 equivalence class` indicates that all terms given as\ncommand-line arguments form a single equivalence class, i.e., the two\nterms are equivalent. Each equivalence class is printed after:\n\n```ShellSession\nopam@b3043b7dca44:~/kmt$ kmt --boolean 'x=T' 'x=T + x=F;set(x,T)'\n[x=T parsed as x=T]\nnf time: 0.000003s\nlunf time: 0.000016s\n[x=T + x=F;set(x,T) parsed as x=T + x=F;set(x,T)[1]]\nnf time: 0.000007s\nlunf time: 0.000010s\n[2 equivalence classes]\n1: { x=T + x=F;set(x,T) }\n2: { x=T }\n```\n\nNote that `b3043b7dca44` will be replaced by some new hash each time\nyou run `docker run -it kmt`.\n\nRunning `run_eval` inside the Docker container will reproduce the\nevaluation from our paper. You can run the regression tests by running\n`test_word` (for regular expression word equivalence, part of our\ndecision procedure) and `test_equivalence` (for KMT term\nequivalence). All of these steps are performed automatically during\n`docker build`.\n\nThe source code for all of these is in the `src` directory; see\n`src/dune` for the build script.\n\n# How do I use the `kmt` executable?\n\nThe default way of using the `kmt` executable is to give it a theory (here `--boolean`) and 2 or more KMT programs in that theory. It will give you the equivalence classes of those terms. The `-v` flag is useful when many terms are given:\n\n```ShellSession\nopam@3ce9eaca9fb1:~/kmt$ kmt -v --boolean 'x=T' 'x=F' 'x=T + x=F' 'x=T + x=F;x=T'\n[x=T parsed as x=T]\nkmt: [INFO] nf = {(x=T,true)}\nnf time: 0.000004s\nkmt: [INFO] lunf = {(x=T,true), (x=F,false)}\nlunf time: 0.000015s\n[x=F parsed as x=F]\nkmt: [INFO] nf = {(x=F,true)}\nnf time: 0.000003s\nkmt: [INFO] lunf = {(x=T,false), (x=F,true)}\nlunf time: 0.000008s\n[x=T + x=F parsed as true]\nkmt: [INFO] nf = {(true,true)}\nnf time: 0.000003s\nkmt: [INFO] lunf = {(true,true)}\nlunf time: 0.000014s\n[x=T + x=F;x=T parsed as x=T]\nkmt: [INFO] nf = {(x=T,true)}\nnf time: 0.000003s\nkmt: [INFO] lunf = {(x=T,true), (x=F,false)}\nlunf time: 0.000006s\n[3 equivalence classes]\nkmt: [INFO] 1: {(x=T,true), (x=F,false)}; {(x=T,true), (x=F,false)}\nkmt: [INFO] 2: {(true,true)}\nkmt: [INFO] 3: {(x=T,false), (x=F,true)}\n```\n\nThe last three lines identify the three equivalence classes in terms\nof their normal forms.\n\nIf you don't specify a theory, the default will be the theory of\nbooleans.\n\nIf you give just one term, `kmt` will normalize it for you.\n\nRun `kmt --help` for command-line help in a manpage-like format.\n\n## What is the syntax?\n\nA Kleene algebra with tests breaks syntax into two parts: tests (or prediates) and actions. Actions are in some sense the 'top level', as every test is an action.\n\nWe use the following syntax, where `a` and `b` are tests and `p` and\n`q` are actions. The following is the core KAT notation; individual\ntheories introduce their own notations.\n\n| Tests   | Interpretation   |\n| :-----: | :--------------- |\n| `false` | always fails     |\n| `true`  | always succeeds  |\n| `not a` | negation         |\n| `a + b` | or, disjunction  |\n| `a ; b` | and, conjunction |\n\n| Actions | Interpretation         |\n| :-----: | :---------------       |\n| `false` | failed trace           |\n| `true`  | noop trace             |\n| `a`     | filter traces by test  |\n| `p + q` | parallel composition   |\n| `p ; q` | sequential composition |\n| `p*`    | Kleene star; iteration |\n\nWhitespace is ignored, and comments are written with `/* ... */`.\n\n### Theory-specific forms\n\nOn its own, the Kleene algebra with tests above doesn't let you\nexpress any interesting programs: we need a notion of concrete\npredicates and actions. KMT builds a concrete KAT around a _theory_,\nwhich defines a predicates and actions. Our implementation has several\npredefined, and [the library itself lets you define new theories](#what-do-I-have-to-provide-to-write-my-own-theory).\n\nTheories add predicates and actions of the form `NAME(ARGS,...)` and\n`ARG1 OP ARG2`. Each theory specificies its own language: an `ARG`\nwill be a variable or a theory-specific constant of some kind; `NAME`\nwill be a conventional function symbol name, like `set`; `OP` takes a\nvariety of forms, like `\u003c` or `=`.\n\n#### Booleans\n\nYou can use the booleans by specifying `--boolean` on the `kmt`\ncommand line. It is the default theory, so you can also leave it\noff. The theory of booleans adds two forms, where `x` and `y` are\nvariables. We write `T` and `F` for the boolean _values_ true and\nfalse, which should not be confused with the KAT terms `true` and\n`false`.\n\n  - `x=T` and `y=F` are tests that are true when `x` is true and `y`\n    is false, respectively\n  - `set(x,T)` and `set(y,F)` are actions that set `x` to true and `y`\n    to false, respectively\n    \n#### Monotonic naturals\n\nYou can use the monotonically increasing naturals by specifying\n`--incnat` on the command line. Monotonic naturals have several\ntheory-specific forms, where variables `x`, `y`, and `z` range over\nnatural numbers; we write `n` to mean a _constant_ natural number.\n\n  - `x \u003e n` is a test that is true when the variable `x`'s value is greater than `n`\n  - `inc(y)` is an action that increments the variable `y`\n  - `set(z, n)` is an action that sets the variable `z` to `n`\n  \n#### Other theories\n\nWe have several other theories built in:\n\n  - `--addition` is a theory of naturals with both `\u003c` and `\u003e`, along with `inc(x,n)`\n  - `--network` is a theory of tracing NetKAT over natural-valued\n    fields `src`, `dst`, `pt`, and `sw`; use `FIELD \u003c- n` for\n    assignment\n  - `--product` is a product theory of booleans and monotonic naturals\n  - `--product-addition` is a product theory of booleans and the\n    `--addition` theory of naturals\n\nYou can [add new\ntheories](#what-do-I-have-to-provide-to-write-my-own-theory) to the\n`kmt` tool by updating the `modes` in `src/kmt.ml`.\n\n# Step-by-Step\n\nThe paper makes three core claims about the implementation.\n\n 1. It is extensible.\n 2. We have implemented some optimizations.\n 3. The benchmarks according to our evaluation in Section 5.\n \n## How can I tell that the implementation is extensible?\n\nLook at `src/kat.ml`. It defines several modules.\n\n - The `KAT_IMPL` signature characterizes what a KAT has. Here `A` is\n   for theory tests and `P` is for theory actions. (The `Test` and\n   `Term` modules are for defining comparison and hashing operations\n   on the hashconsed KMT terms.)\n - The `THEORY` signature characterizes what a client theory must\n   define to generate a KMT.\n - The `KAT` module is a functor that takes a `THEORY` and produces a\n   `KAT_IMPL`.\n   \nThat is, we use OCaml functors to transform a `THEORY` into a `KAT`.\n\nYou can see this process in action in `src/boolean.ml`. After some\nbase definitions (outside the module to simplify things), we define\nthe module `Boolean` recursively as a `THEORY`... where we use `K =\nKAT (Boolean)` inside our definition. That is, `Boolean.K` is the KMT\nover booleans. You can see that there is very little boilerplate:\nparsing is just a few lines; we define `push_back` in just a few\nlines. The satisfiability checker is somewhat complicated by our use\nof a 'fast' path in the `satisfiable` function, where we discharge\nsimple queries (with just conjunction and negation of theory\npredicates, but no disjunction---see `can_use_fast_solver`) without\ncalling Z3 at all.\n\n## What optimizations are implemented?\n\nAll KAT terms are hashconsed. The library for that is in\n`src/hashcons.ml`; KAT terms are hashconsed using `'a pred`/`'a\npred_hons` and `('a, 'p) kat` and `('a, 'p) kat_hons` in\n`src/kat.ml`. We use smart constructors extensively in the `KAT`\nmodule (see `not`, `ppar`, `pseq`, etc.).\n\nWhen we check word equivalence of actions in `src/decide.ml` (see\n`same_actions`), we use the `equivalent_words` function in\n`src/word.ml`. That method uses the Brzozowski derivative to generate\nword automata lazily during checking (see `derivative` and `accepting` in that\n`src/word.ml`).\n\nFinally, several theories implement custom satisfiability checkers\nthat don't merely defer to Z3: `boolean.ml`, `incnat.ml`, and\n`addition.ml`.\n\n## How do I reproduce the paper's evaluation?\n\nBy default, the [Docker build](#how-do-i-build-it) will run the\nevaluation from Section 5, using a 30s timeout. Here is sample output\n(your hash and exact times will differ):\n\n```\nStep 14/18 : RUN opam exec -- dune exec -- src/kmt_eval\n ---\u003e Running in f609aca22e92\ntest                      time (seconds)\n                             30s timeout\n----------------------------------------\na* != a (10 random `a`s)          0.0399\ncount twice                       0.0006\ncount order                       0.0008\nparity loop                       0.0003\nboolean tree                      0.0004\npopulation count                  0.3677\ntoggle three bits                timeout\n```\n\nThese numbers are slightly higher than those in the paper, which\nreports numbers from a local installation. Times will of course vary:\nmachines differ (the original eval is on a 2014 MacBook Pro with 16GB\nof RAM); Docker on macOS is really a VM, and will be substantially\nslower than Docker on Linux; Docker will always be slower than [a\nlocal installation](#building-locally). It _should_, however, be the\ncase that these benchmarks will have the same relative performance.\n\nYou can change the evaluation timeout by passing `-t SECONDS` or\n`--timeout SECONDS` to `kmt_eval`. In Docker on macOS 10.13 on the\n2014 MacBook Pro, we find a high timeout is necessary to get the last\nbenchmark to terminate:\n\n```ShellSession\nopam@6792c093ed91:~/kmt$ kmt_eval -t 3600\ntest                      time (seconds)\n                           3600s timeout\n----------------------------------------\na* != a (10 random `a`s)          0.0682\ncount twice                       0.0006\ncount order                       0.0008\nparity loop                       0.0005\nboolean tree                      0.0008\npopulation count                  0.4311\ntoggle three bits                1175.1909\n```\n\n## What _isn't_ evaluated?\n\nNot every theory described in the paper is completely implemented in\nKMT. Namely:\n\n  - The implementation of the tracing NetKAT theory uses restricted\n    fields and natural numbers as values, rather than the richer\n    domain NetKAT enjoys.\n  - LTLf is not implemented, and neither is Temporal NetKAT. (But the\n    [PLDI 2016 implementation is available on\n    GitHub](https://github.com/rabeckett/Temporal-NetKAT).)\n  - Sets and maps are not implemented.\n\n## Building locally\n\nThe simplest way to play with KMT right away is to [use\nDocker](#how-do-i-build-it). If for some reason you would prefer to\nrun KMT on your own Linux machine, run the following commands from a clone\nof the repo:\n\n```ShellSession\n$ sudo apt-get install -y libgmp-dev python3\n$ opam install ocamlfind ppx_deriving batteries ANSIterminal fmt alcotest cmdliner logs zarith z3 dune\n$ eval $(opam env)\n$ dune build -- src/kmt      # build the CLI\n$ dune test                  # unit tests on regex word equivalence and KMT equivalence\n$ dune exec -- src/kmt_eval  # PLDI2022 eval\n```\n\nOn macOS, `brew install gmp python3 ; sudo mkdir -p\n/opt/local/lib` should replace the call to `apt-get`.\n\nIf the above fails, the CI automation is a good guide for manual installation: see the `Dockerfile` and `.github/workflows/build.yml`.\n\n## What do I have to provide to write my own theory?\n\nThe source code in `src/incnat.ml` is a nice example. You have to provide:\n\n  - sub-modules `P` and `A` for the primitive parts of your language\n  - a `parse` function to indicate how to parse the syntax of your\n    primitives; return `Left` for tests and `Right` for actions\n  - a `push_back` operation that calculates weakest preconditions on a\n    pair of a primitive and a predicate\n  - a `satisfiable` function to test whether a predicate is satisfiable\n\nTo use the Z3 backend, your theory can describe how it extracts to Z3 using functions `variable`, `variable_test`, `create_z3_var`, and `theory_to_z3_expr`.\n\nNote that `incnat.ml`'s theory solver in `satisfiable` has two cases: a fast path that need not use Z3, and a more general decision procedure in Z3.\n\n### Which example theories should I look at first?\n\nThe code in `src/boolean.ml` is for a simple language with boolean-valued variables.\n\nCheck out `src/incnat.ml` for a simple language with increment and assignment operations. It defines types `a` and `p` for the primitive parts of the language (one predicate, which tests whether a variable is greater than a number, and two actions, which increment and set variables).\n\nThe code in `src/product.ml` is for a _higher-order theory_, combining\ntwo theories into one. You can see it in action using the `--product`\nand `--product-addition` flags for KMT.\n\n## How is equivalence decided?\n\nWe decide equivalence via _normalization_. We convert KMT terms to a normal form using the novel `push_back` operation; to compare two such normal forms, we disambiguate the tests and compare the terms pointwise. When this procedure is fast, it's _quite_ fast... but deeply nested loops or loops with lots of conditionals slow it down severely.\n\nIn more detail, see `src/decide.ml`. The top-level function is:\n\n```OCaml\nlet equivalent (p: K.Term.t) (q: K.Term.t) : bool =\n  let nx = normalize_term 0 p in\n  let ny = normalize_term 0 q in\n  equivalent_nf nx ny\n```\n\nThat is, we normalize and then compare normal forms.\n\n```OCaml\nlet equivalent_nf (nx: nf) (ny: nf) : bool =\n  (* optimization: just if syntactically equal first *)\n  if PSet.equal nx ny\n  then\n    begin\n      Log.debug (fun m -\u003e m \"syntactic equality on %s\" (show_nf nx));\n      true\n    end\n  else begin\n      Log.debug (fun m -\u003e m\n                         \"running cross product on %s and %s\"\n                         (show_nf nx) (show_nf ny));\n      let xhat = locally_unambiguous_form nx in\n      Log.debug (fun m -\u003e m \"%s is locally unambiguous as %s\" (show_nf nx) (show_nf xhat));\n      let yhat = locally_unambiguous_form ny in\n      Log.debug (fun m -\u003e m \"%s is locally unambiguous as %s\" (show_nf ny) (show_nf yhat));\n      equivalent_lunf xhat yhat\n  end\n```\n\nIt may be easier to understand without the logging/optimization:\n\n```OCaml\nlet equivalent_nf (nx: nf) (ny: nf) : bool =\n  let xhat = locally_unambiguous_form nx in\n  let yhat = locally_unambiguous_form ny in\n  equivalent_lunf xhat yhat\n```\n\nGiven normal forms `nx` and `ny`, we first compute locally unambiguous\nforms `xhat` and `yhat`; we then check _those_ for equivalence.\n\nTo generate locally unambiguous forms, suppose the normal form `nx` is\nequal to `a1;m1 + a2;m2 + ... + an;mj`. We generate `xhat` by\nconsidering every possibly combination of the tests `ai`, which\nengender every possibly combination of the actions `mi`. That is:\n\n```\nxhat =     a1 ;     a2 ; ... ;     aj ; (m1 + m2 + ... + mj)\n     + not a1 ;     a2 ; ... ;     aj ; (     m2 + ... + mj)\n     +     a1 ; not a2 ; ... ;     aj ; (m1 +      ... + mj)\n     + ...                         \n     + not a1 ; not a2 ; ... ;     aj ; (                mj)\n     + not a1 ; not a2 ; ... ; not aj ; false\n```\n\nWe build `yhat` from `y = b1;n1 + ... + bk;nk` similarly:\n\n```\nyhat =     b1 ;     b2 ; ... ;     bk ; (n1 + n2 + ... + nk)\n     + not b1 ;     b2 ; ... ;     bk ; (     n2 + ... + nk)\n     +     b1 ; not b2 ; ... ;     bk ; (n1 +      ... + nk)\n     + ...                         \n     + not b1 ; not b2 ; ... ;     bk ; (                nk)\n     + not b1 ; not b2 ; ... ; not bk ; false\n```\n\nWe call these `hat`ted forms \"locally unambiguous\" because each possible test in `xhat` is syntactically unambiguous.\n\nNow we can compare `xhat` and `yhat` (in `equivalent_lunf`): consider\nevery pair of a predicates from `xhat` and `yhat`. If the combination\nof the predicates is unsatisfiable, then we can ignore that case. If\nit's satisfiable, then for `xhat` and `yhat` to be equivalent, the\nactions on both sides must be equivalent. We can decide _that_\nequivalence using the Hopcroft-Karp algorithm (see `equivalent_words`\nin `src/word.ml`).\n\nCongratulations, you read the whole thing! 😁\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgree%2Fkmt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmgree%2Fkmt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgree%2Fkmt/lists"}