diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 0ab0b6c91..1453d8992 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -13,5 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v17 - - run: nix run .#test + - uses: DeterminateSystems/nix-installer-action@v4 + - uses: DeterminateSystems/magic-nix-cache-action@v2 + - run: nix flake check diff --git a/.github/workflows/update-deps-lock.yaml b/.github/workflows/update-deps-lock.yaml index abe9eb791..5a0dd72d9 100644 --- a/.github/workflows/update-deps-lock.yaml +++ b/.github/workflows/update-deps-lock.yaml @@ -10,9 +10,10 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v17 + - uses: DeterminateSystems/nix-installer-action@v4 + - uses: DeterminateSystems/magic-nix-cache-action@v2 - name: Update deps-lock - run: "nix run .#deps-lock" + run: nix run .#deps-lock - name: Create Pull Request uses: peter-evans/create-pull-request@v4.0.3 diff --git a/.gitignore b/.gitignore index d38ac958b..f87d1a247 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ /doc/icfca-2013-tutorial/icfca2013-tutorial-talk.vrb /doc/tutorials/icfca-2013/icfca2013-tutorial-live.html /.lsp/ -/.direnv/ \ No newline at end of file +/.direnv/ +/.clj-kondo/ \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index 332175d75..44d7bfb63 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -16,6 +16,8 @@ Additional Contributors are * Johannes Hirth (pq-cores, scale-measures) * Gleb Kanterov (interval-scale) * Maximilian Marx (Wikidata) +* Jannik Nordmeyer (Metric Contexts, Causal Implications) * Maximilian Stubbemann (concept-robustness) * Anselm von Wangenheim (DimDraw) * Johannes Wollbold (bug reports, feature requests) + diff --git a/README.md b/README.md index f5925b81a..52d86aa0e 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ much more. 8. [Computing Traces in Contexts](doc/code/trace-context.clj) 9. [Counting Quasiorders](doc/code/quasiorders.clj) 10. [Rudolph's Algorithm for Computing Bases](doc/code/rudolph_computation.clj) + 11. [Discovering Causal Implications](doc/Causal-Implications.org) 5. Advanced Topics 1. [pq-cores](doc/pq-cores-in-Formal-Contexts.md) 2. [REST-API Usage](doc/REST-API-usage.md) diff --git a/deps-lock.json b/deps-lock.json index 32c5b33c0..f85e54e16 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -42,26 +42,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-4ZmTVCJYkBoGvnALoB14QOInNNXqsoLWJN8ceAPQ2TU=" }, - { - "mvn-path": "cheshire/cheshire/5.9.0/cheshire-5.9.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-Wbkn5BICCp+23Fw85K/l6MMRJFl7wfTdR3XbP/UGZQ0=" - }, - { - "mvn-path": "cheshire/cheshire/5.9.0/cheshire-5.9.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-2o0ZOdbSXm+053HUxYHq7jctocwGmaGsaLiAPnbSy1o=" - }, - { - "mvn-path": "cider/cider-nrepl/0.25.2/cider-nrepl-0.25.2.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-6P/T+ezkNK+HfqR2uY4zjxguDVYCBkZaliQLT+qH7k0=" - }, - { - "mvn-path": "cider/cider-nrepl/0.25.2/cider-nrepl-0.25.2.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-2Psx89fYmDr18J8kk6KO0ZKFxqxyxZ/Y1/+9B212gy0=" - }, { "mvn-path": "clj-http/clj-http/3.12.3/clj-http-3.12.3.jar", "mvn-repo": "https://repo.clojars.org/", @@ -87,11 +67,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-o0Hid1Wzrsp13Vl3jagdiOF5TruKmPIXon/3MpN24Ks=" }, - { - "mvn-path": "clj-time/clj-time/0.14.3/clj-time-0.14.3.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-AdXJ5WTB3OBAWYD9mgvP/yfIDzu2xOms6CF/TTgBnjk=" - }, { "mvn-path": "clj-time/clj-time/0.14.3/clj-time-0.14.3.pom", "mvn-repo": "https://repo.clojars.org/", @@ -118,14 +93,14 @@ "hash": "sha256-ZIsMIEuk8EDcQ8KfguRHHstmdWCK+wuzGp7BxCYTenQ=" }, { - "mvn-path": "clout/clout/2.2.1/clout-2.2.1.jar", + "mvn-path": "com/clojure-goes-fast/clj-async-profiler/1.0.5/clj-async-profiler-1.0.5.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-nzdGxROb7zUJmjOuuDMQCnWOJXKv1CM+ynAM3XLWhws=" + "hash": "sha256-GCU/PtxlxREIczFMtETc+xxGGK+y7c+hZjeTGY/M/dk=" }, { - "mvn-path": "clout/clout/2.2.1/clout-2.2.1.pom", + "mvn-path": "com/clojure-goes-fast/clj-async-profiler/1.0.5/clj-async-profiler-1.0.5.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-BNzQl/YWeodReE3AkISRm68YmBfPQcvt4sQs7gSMh6c=" + "hash": "sha256-ZVZ+y4rPUVaTwStPlBe0Gr6NKhwjOioqlWZaM8xMn04=" }, { "mvn-path": "com/damnhandy/handy-uri-templates/2.1.8/handy-uri-templates-2.1.8.jar", @@ -157,16 +132,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-fd0tyP0KY04n4wapqoVJWczEckV5dijFC8g3F3WkLjI=" }, - { - "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.9.9/jackson-core-2.9.9.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-MIMHm+YIjbLtCgxv+SIE4KpI+h3p21tZxGjzWs+ILCw=" - }, - { - "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.9.9/jackson-core-2.9.9.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-xWqygptlPMfKZIEGCV8GA4Vm2AlcAfIYE1XckHskT2E=" - }, { "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.10.2/jackson-dataformat-cbor-2.10.2.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -187,16 +152,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-11c/IdIWsbjzqqLnriuDegU1M1Mn0kBQeUkXcWAJABY=" }, - { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.9.9/jackson-dataformat-cbor-2.9.9.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-9ZVZbn4AyDdS2NWGsI0DYYXiNj+j1xrg4VWzv2psdaw=" - }, - { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.9.9/jackson-dataformat-cbor-2.9.9.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-ljd1drO9v9kPc89b39Cx1NRpheC/7RqEVuP0NhCswrY=" - }, { "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.10.2/jackson-dataformat-smile-2.10.2.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -217,16 +172,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-oXSd6iBODyF1XRo/ea+3Z47bpT+EggcoV7vJRoy4CHU=" }, - { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.9.9/jackson-dataformat-smile-2.9.9.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-EMtnsq/C7ghHkiFaq/aXQult1Y1kpWgh+sVY0MjPeSM=" - }, - { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.9.9/jackson-dataformat-smile-2.9.9.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-vadQqQnsL/gnrG6zMjmVlpN7Bcpu4DrZScJ3L2TIryk=" - }, { "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformats-binary/2.10.2/jackson-dataformats-binary-2.10.2.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -242,11 +187,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-S+eAID53athTMCWQW1j4l5YE/Igk1pgti2IZ5qMwygE=" }, - { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformats-binary/2.9.9/jackson-dataformats-binary-2.9.9.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-MSo3eyxurmKp4LC2ahSDt63DomhRFExolvXZ+QH/vpg=" - }, { "mvn-path": "com/fasterxml/jackson/jackson-base/2.10.2/jackson-base-2.10.2.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -262,11 +202,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-+81gnH4GPoOzDc5YVVsj3uPlGdwAqqx0ceUrQUCRcyk=" }, - { - "mvn-path": "com/fasterxml/jackson/jackson-base/2.9.9/jackson-base-2.9.9.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-QzaIZ9LYww3EbwzebLWjIL+5/XRjcovlAd5hVFJll10=" - }, { "mvn-path": "com/fasterxml/jackson/jackson-bom/2.10.2/jackson-bom-2.10.2.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -282,11 +217,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-9BOCmOts2HKgOORiRoAxqGA6p4SC6aIjnJFy81ci8Pg=" }, - { - "mvn-path": "com/fasterxml/jackson/jackson-bom/2.9.9/jackson-bom-2.9.9.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-I39YkwqwLX1S6a/MglsuLYoKvdDobR1dobV53GWAnJE=" - }, { "mvn-path": "com/fasterxml/jackson/jackson-parent/2.10/jackson-parent-2.10.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -302,21 +232,11 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-IlykrxyJWrSEXPLSn9WsUOU4s1OAXnW7K3Shs7nYa8U=" }, - { - "mvn-path": "com/fasterxml/jackson/jackson-parent/2.9.1.2/jackson-parent-2.9.1.2.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-lRfkBcazuKA1IVrVcnATo1Get1kXQ/4dzATfZjVoPPk=" - }, { "mvn-path": "com/fasterxml/oss-parent/33/oss-parent-33.pom", "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-xUNwlkz8ziMZvZqF9eFPUAyFGJia5WsbR13xs0i3MQg=" }, - { - "mvn-path": "com/fasterxml/oss-parent/34/oss-parent-34.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-mnXz4yv51uAGeNlEes5N6FlqLSIa9c9bvH9XHKx5UAY=" - }, { "mvn-path": "com/fasterxml/oss-parent/38/oss-parent-38.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -392,11 +312,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-VxY5EUJ+1pVuXJId/eH3UKok0b4Z+UBqkwPvGdyAMAU=" }, - { - "mvn-path": "commons-codec/commons-codec/1.10/commons-codec-1.10.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-QkHfqU5xHUNfKaRgSj4t5cSqPBZeI70Ga+b8H8QwlWk=" - }, { "mvn-path": "commons-codec/commons-codec/1.10/commons-codec-1.10.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -447,11 +362,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-FaWcDnV8bAfD0baJ1zXI46nsVpXWzrapQdQGKrIpAbc=" }, - { - "mvn-path": "commons-fileupload/commons-fileupload/1.3.3/commons-fileupload-1.3.3.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-4Uq320feEk9fnpwOA/T20qAH2DRYoK1nNWt73XdcjNA=" - }, { "mvn-path": "commons-fileupload/commons-fileupload/1.3.3/commons-fileupload-1.3.3.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -482,11 +392,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-bCIdwtypQzGpKpyxmzlDYx98t8AwIlX7XNBFBlToEsc=" }, - { - "mvn-path": "commons-io/commons-io/2.6/commons-io-2.6.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-+HfTBGYKwqFC84ZbrfyXHex+1zx0fH+NXS9ROcpzZRM=" - }, { "mvn-path": "commons-io/commons-io/2.6/commons-io-2.6.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -522,21 +427,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-UztBf2dHU/bHn6v7/vlqRj6pw8yj16g/8Ot9dmgpP8k=" }, - { - "mvn-path": "compojure/compojure/1.6.1/compojure-1.6.1.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-gfFlLhALTnJg1roctdKanPkJxmYZIhJUtI9htRCsFqA=" - }, - { - "mvn-path": "compojure/compojure/1.6.1/compojure-1.6.1.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-0X6Yn5mm6xGjC2q2bs9PWztRG/gPhoG4zFd3835KCbI=" - }, - { - "mvn-path": "crypto-equality/crypto-equality/1.0.0/crypto-equality-1.0.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-fdGcgcHmPXaIY2DtqYI8cefkaA5KJKgNHJvYQsRGOPk=" - }, { "mvn-path": "crypto-equality/crypto-equality/1.0.0/crypto-equality-1.0.0.pom", "mvn-repo": "https://repo.clojars.org/", @@ -552,11 +442,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-Yy5XAtXTVhk9GKUN4KJhoZwUqtYIc05GToWjYA509Es=" }, - { - "mvn-path": "crypto-random/crypto-random/1.2.0/crypto-random-1.2.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-iRTQrrFQf50SEzFacCHqKCqVaxnbCE4pwYCbt8ufjQg=" - }, { "mvn-path": "crypto-random/crypto-random/1.2.0/crypto-random-1.2.0.pom", "mvn-repo": "https://repo.clojars.org/", @@ -572,36 +457,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-2OgLA0KFMl6QX1RkmhWYtoe5pKmaOk9LlO7TWXyyEEg=" }, - { - "mvn-path": "gorilla-plot/gorilla-plot/0.1.4/gorilla-plot-0.1.4.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-Tz9YJx+/2rMO5UKZEK8tLNCbPq4PMg13ipshtVZ1OOc=" - }, - { - "mvn-path": "gorilla-plot/gorilla-plot/0.1.4/gorilla-plot-0.1.4.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-WSEDYpj5Zp8GDI3bW4OPV4Qso4m0Ksy3Zi7cOSk3lyU=" - }, - { - "mvn-path": "gorilla-renderable/gorilla-renderable/2.0.0/gorilla-renderable-2.0.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-UGXhgmjEpLHuPo9yxQB/IEpslI5Bs0RBlFWCYuAXKNA=" - }, - { - "mvn-path": "gorilla-renderable/gorilla-renderable/2.0.0/gorilla-renderable-2.0.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-kHytrypFgjzzoChhvglmTRqLGCwCoqlXpNIj7AjyChU=" - }, - { - "mvn-path": "grimradical/clj-semver/0.2.0/clj-semver-0.2.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-zlqBqd/Xq+C34aA1NiDa0aPe+w9MViyGQpYCROrMVsU=" - }, - { - "mvn-path": "grimradical/clj-semver/0.2.0/clj-semver-0.2.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-OiolUJk/rq8aOxWSXzJwiu0JuAY2IifCQWwGBWSQieM=" - }, { "mvn-path": "hiccup/hiccup/1.0.5/hiccup-1.0.5.jar", "mvn-repo": "https://repo.clojars.org/", @@ -612,16 +467,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-OGBZ1P4rXyKX6peRwllnGDM6eQAIgsOfyZqXSEPA5VI=" }, - { - "mvn-path": "http-kit/http-kit/2.4.0-alpha6/http-kit-2.4.0-alpha6.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-XyXlrqbD+LcMd4Sx9K+a7verxsIq+ZzMUZsc7qiTuIM=" - }, - { - "mvn-path": "http-kit/http-kit/2.4.0-alpha6/http-kit-2.4.0-alpha6.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-NPdrS86tzY9CC7MgnLsRifay9QO9TtBKnk2c2lTcV8A=" - }, { "mvn-path": "http-kit/http-kit/2.6.0/http-kit-2.6.0.jar", "mvn-repo": "https://repo.clojars.org/", @@ -632,16 +477,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-mzngH1mQUDDBHh5BwOgEZ+jhv2Rc7n2gl2hVz/W2mSM=" }, - { - "mvn-path": "instaparse/instaparse/1.4.8/instaparse-1.4.8.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-f3FtOKxcfsqmBDWNIbjNCU2YHVfwy9ulhkT2PA6pfAQ=" - }, - { - "mvn-path": "instaparse/instaparse/1.4.8/instaparse-1.4.8.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-Abw9VkmoG6vyyVkVtWbFQxNyqG/LwViDrBoT9JCEIxw=" - }, { "mvn-path": "j18n/j18n/1.0.2/j18n-1.0.2.jar", "mvn-repo": "https://repo.clojars.org/", @@ -682,11 +517,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-3t7z1zItiWPlFT9qIOkAV3TN+kZUooVVL5D8Q2W/zho=" }, - { - "mvn-path": "joda-time/joda-time/2.9.9/joda-time-2.9.9.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-sEmkPBBXlC5qz77OAI5JSbLjXRZY0Mjgb0SFOX4vpOc=" - }, { "mvn-path": "joda-time/joda-time/2.9.9/joda-time-2.9.9.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -712,16 +542,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-0jmKkRGLe9T7zNhtSiQZSsP6qWFfshG9LHw6MzWL1JI=" }, - { - "mvn-path": "medley/medley/1.0.0/medley-1.0.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-nSj+gKDZVIp4jTc+aZS5OWX3LgyEf3i6CfGZ+xdV6fo=" - }, - { - "mvn-path": "medley/medley/1.0.0/medley-1.0.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-lpOhooHEVTdGXgi/KEM6UpgAZdXt9Y6S+A16zPRrzUE=" - }, { "mvn-path": "net/cgrand/parsley/0.9.2/parsley-0.9.2.jar", "mvn-repo": "https://repo.clojars.org/", @@ -762,16 +582,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-QQlhb1Xl2+WcMt0Q6ibhbfeeJDciWW+f5QLw8tMh8A0=" }, - { - "mvn-path": "nrepl/nrepl/0.7.0/nrepl-0.7.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-fMYRtNYyT5Djwmh7K2XuSjTjt/vnYgayk4NS8oWNP8E=" - }, - { - "mvn-path": "nrepl/nrepl/0.7.0/nrepl-0.7.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-yqn3nivBpvotTftjmLD38a5fkWDNNyMxo9WVk2aJMbg=" - }, { "mvn-path": "nrepl/nrepl/0.8.3/nrepl-0.8.3.pom", "mvn-repo": "https://repo.clojars.org/", @@ -1127,26 +937,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-PW66QoVVpVjeBGtddurMH1pUtPXyC4TWNu16/xiqSMM=" }, - { - "mvn-path": "org/clojars/benfb/gorilla-repl/0.7.0/gorilla-repl-0.7.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-jtNrbHSsSMrBpNgV5hFGNIrtRXQAfprWghL+2mZjnYo=" - }, - { - "mvn-path": "org/clojars/benfb/gorilla-repl/0.7.0/gorilla-repl-0.7.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-Lxats/1b27srOo5RXQtPP9gigg6E/roTF/j/COfdp9I=" - }, - { - "mvn-path": "org/clojars/benfb/lein-gorilla/0.7.0/lein-gorilla-0.7.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-LJvJtREace0lD1BxB+dZBFVhuszMolKZ6YEwozil678=" - }, - { - "mvn-path": "org/clojars/benfb/lein-gorilla/0.7.0/lein-gorilla-0.7.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-oo7kXwJ9Pkxj3RITBEdkCM4/H6+e2Qp0ltCZucaEXEI=" - }, { "mvn-path": "org/clojars/trptcolin/sjacket/0.1.1.1/sjacket-0.1.1.1.jar", "mvn-repo": "https://repo.clojars.org/", @@ -1337,16 +1127,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-F3i70Ti9GFkLgFS+nZGdG+toCfhbduXGKFtn1Ad9MA4=" }, - { - "mvn-path": "org/clojure/data.codec/0.1.0/data.codec-0.1.0.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-aD1oGVBAPGHCNjVBgeuhtcja9sE1geoTiZNKfV6yjgc=" - }, - { - "mvn-path": "org/clojure/data.codec/0.1.0/data.codec-0.1.0.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-8T2ZaEbW16cCQ2JlqjhjKmdGkgJaQYpWaVxQKBPd2ng=" - }, { "mvn-path": "org/clojure/data.csv/1.0.1/data.csv-1.0.1.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1467,11 +1247,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-w/lnehWxSPzvMAsGC29fn2fToTWUMhq+svIFpau+qZE=" }, - { - "mvn-path": "org/clojure/pom.contrib/0.0.25/pom.contrib-0.0.25.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-68ezduVtg/bEhM2x03Hv3AEw3bvK3n1tpuNU9OQm/Is=" - }, { "mvn-path": "org/clojure/pom.contrib/0.1.2/pom.contrib-0.1.2.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1562,16 +1337,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-v9jf44Bp4mJIzqRyQ9+Zvv/0mjGGzDyk1fNTefp9u3M=" }, - { - "mvn-path": "org/clojure/tools.macro/0.1.5/tools.macro-0.1.5.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-JxTXKUyQ+SaO7vNyj+TZjr+q7fJAoCN02u8rhVhEgkg=" - }, - { - "mvn-path": "org/clojure/tools.macro/0.1.5/tools.macro-0.1.5.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-cGCU9H2ljugXofq5uAwxLs0nZHK85uHVRCOfFAcR2zE=" - }, { "mvn-path": "org/clojure/tools.namespace/0.2.11/tools.namespace-0.2.11.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1807,16 +1572,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-SZCg3bNUDE1Ed6GtqP1mP62RSRScmaYGL7/XSKXwGJo=" }, - { - "mvn-path": "ring/ring-codec/1.1.0/ring-codec-1.1.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-1eNzT90+4y8CCJOfPDgX0xUuiJDDdeeX4hj88e/wU9M=" - }, - { - "mvn-path": "ring/ring-codec/1.1.0/ring-codec-1.1.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-tdPfkyWt0D1RfaxCiIHtRdydYEYLbpOEqob9AFSDUOM=" - }, { "mvn-path": "ring/ring-codec/1.1.1/ring-codec-1.1.1.pom", "mvn-repo": "https://repo.clojars.org/", @@ -1847,11 +1602,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-u2TefpyESbB9MB079SA+VCs0GIQcDjTeR3STK1gJosk=" }, - { - "mvn-path": "ring/ring-core/1.7.1/ring-core-1.7.1.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-WnVN6mqomoRWh+q73cxAtzoyL/S5/KAYl+64mqSnARk=" - }, { "mvn-path": "ring/ring-core/1.7.1/ring-core-1.7.1.pom", "mvn-repo": "https://repo.clojars.org/", @@ -1872,16 +1622,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-CVk/j1Kyuozf5B7Y7emC9PP8D+bxPRKoqrZy+MmjaGc=" }, - { - "mvn-path": "ring/ring-json/0.5.0/ring-json-0.5.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-7k/b7FLcxjKCdCL/4IIGgPAw6deLRFn6H3BO82xNjfk=" - }, - { - "mvn-path": "ring/ring-json/0.5.0/ring-json-0.5.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-BDxzJtoWiilx+yOpHLctK08S4zcr1NCo9ibASXYcwTI=" - }, { "mvn-path": "ring/ring-json/0.5.1/ring-json-0.5.1.jar", "mvn-repo": "https://repo.clojars.org/", @@ -1942,11 +1682,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-K1LX2dBxuTl5vhB7VoNHfC2ftndYn5r5WMyWdg3jjpw=" }, - { - "mvn-path": "tigris/tigris/0.1.1/tigris-0.1.1.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-3AV+TXCvFWvUmktx6ouXkoXDjiLJrsLzsMi67v3bCLs=" - }, { "mvn-path": "tigris/tigris/0.1.1/tigris-0.1.1.pom", "mvn-repo": "https://repo.clojars.org/", diff --git a/doc/Causal-Implications.org b/doc/Causal-Implications.org new file mode 100644 index 000000000..775d303fc --- /dev/null +++ b/doc/Causal-Implications.org @@ -0,0 +1,141 @@ +#+property: header-args :wrap src text +#+property: header-args:text :eval never + +* Computing Causal Rules in ~conexp-clj~ + +~conexp-clj~ provides methods to discover causal rules within a context as described in "Mining Causal Association Rules" (https://www.researchgate.net/publication/262240022_Mining_Causal_Association_Rules). +We will consider the following data set: + +#+begin_src clojure +(def smoking-ctx (make-context [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40] + ["smoking" "male" "female" "education-level-high" "education-level-low" "cancer"] + #{[0 "smoking"] [0 "male"] [0 "education-level-high"] [0 "cancer"] + [1 "smoking"] [1 "male"] [1 "education-level-high"] [1 "cancer"] + [2 "smoking"] [2 "male"] [2 "education-level-high"] [2 "cancer"] + [3 "smoking"] [3 "male"] [3 "education-level-high"] [3 "cancer"] + [4 "smoking"] [4 "male"] [4 "education-level-high"] [4 "cancer"] + [5 "smoking"] [5 "male"] [5 "education-level-high"] [5 "cancer"] + [6 "smoking"] [6 "male"] [6 "education-level-high"] + [7 "smoking"] [7 "male"] [7 "education-level-high"] + [8 "smoking"] [8 "male"] [8 "education-level-low"] [8 "cancer"] + [9 "smoking"] [9 "male"] [9 "education-level-low"] [9 "cancer"] + [10 "smoking"] [10 "male"] [10 "education-level-low"] [10 "cancer"] + [11 "smoking"] [11 "male"] [11 "education-level-low"] [11 "cancer"] + [12 "smoking"] [12 "male"] [12 "education-level-low"] + [13 "smoking"] [13 "female"] [13 "education-level-high"] [13 "cancer"] + [14 "smoking"] [14 "female"] [14 "education-level-high"] [14 "cancer"] + [15 "smoking"] [15 "female"] [15 "education-level-high"] [15 "cancer"] + [16 "smoking"] [16 "female"] [16 "education-level-high"] [16 "cancer"] + [17 "smoking"] [17 "female"] [17 "education-level-high"] [17 "cancer"] + [18 "smoking"] [18 "female"] [18 "education-level-high"] + [19 "smoking"] [19 "female"] [19 "education-level-high"] + [20 "smoking"] [20 "female"] [20 "education-level-low"] [20 "cancer"] + [21 "smoking"] [21 "female"] [21 "education-level-low"] [21 "cancer"] + [22 "smoking"] [22 "female"] [22 "education-level-low"] [22 "cancer"] + [23 "smoking"] [23 "female"] [23 "education-level-low"] [23 "cancer"] + [24 "smoking"] [24 "female"] [24 "education-level-low"] + [25 "male"] [25 "education-level-high"] [25 "cancer"] + [26 "male"] [26 "education-level-high"] [26 "cancer"] + [27 "male"] [27 "education-level-high"] + [28 "male"] [28 "education-level-high"] + [29 "male"] [29 "education-level-high"] + [30 "male"] [30 "education-level-low"] [30 "cancer"] + [31 "male"] [31 "education-level-low"] + [32 "male"] [32 "education-level-low"] + [33 "male"] [33 "education-level-low"] + [34 "female"] [34 "education-level-high"] [34 "cancer"] + [35 "female"] [35 "education-level-high"] + [36 "female"] [36 "education-level-high"] + [37 "female"] [37 "education-level-low"] [37 "cancer"] + [38 "female"] [38 "education-level-low"] + [39 "female"] [39 "education-level-low"] + [40 "female"] [40 "education-level-low"]}) +) +#+end_src + +We would like to ascertain, if smoking is causally related to cancer: + +#+begin_src clojure :exports both +(def smoking-rule (make-implication #{"smoking"} #{"cancer"})) +#+end_src + +The algorith determines causality by emulating a controlled study, in which one group is exposed to the premise attribute, in this case "smoking" and the control group is not. Additionally, we choose a set of controlled variables. +These are supposed to have the same values across pairs of objects in the exposure group and control group, to make sure neither of these is the true cause for the conclusion attribute, in this case "cancer". + +If two objects in the context satisfy these conditions, they are considerd a matched record pair. For example, if we control for variables "male" "female" "education-level-high" and "education-level-low" (0, 25), (9, 31) and (13 34) +each form a matched record pair. This can be verified using the method ~matched-record-pair?~: + +#+begin_src clojure :exports both +(matched-record-pair? smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"} + 0 + 25) + +(matched-record-pair? smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"} + 9 + 31) +(matched-record-pair? smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"} + 13 + 34) +#+end_src + +The method ~fair-data-set~ computes a set of matched record pairs, by trying to match each object to exactly one different object. + +#+begin_src clojure :exports both +(= (fair-data-set smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"}) + smoking-fair-data-set) +#+end_src + +#+RESULTS: +#+begin_src clojure +([#{7 29} + #{13 34} + #{15 36} + #{6 26} + #{1 28} + #{0 27} + #{17 35} + #{33 9} + #{31 12} + #{30 10} + #{22 37} + #{4 25} + #{21 38} + #{32 11} + #{24 40} + #{20 39}]) +#+end_src + +This set is used to verify whether an association rule is causal in nature. +The method ~causal?~ tests causality for a specific implication: + +#+begin_src clojure :exports both +(causal? smoking-ctx smoking-rule #{} 1.7 1) +#+end_src + +The final three parameters are: +-the irrelevant variables, variables that will not be controlled for +-the confidence in the causality of the implication; a value of 1.7 corresponds to a 70% confidence +-a threshold for computing exclusive variables. Two variables a and b are mutually exclusive, if the absolute support for (a and b) or (b and not a) is no larger than the threshold + +To discover all causes of a certain attribute in the context the method ~causal-association-rule-discovery~ can be used: + +#+begin_src clojure :exports both +(causal-association-rule-discovery smoking-ctx 0.7 3 "cancer" 1.7) +#+end_src + +#+RESULTS: +#+begin_src clojure +(#{"smoking"}) +#+end_src + +0.7 represents the minimum local support that a generated variable must exceet to be considered. 1.7 again represents the confidence of the rule, and 3 represents the maximum number of attributes of the premise. + diff --git a/doc/Getting-Started.org b/doc/Getting-Started.org index 6173c63b2..22756430f 100644 --- a/doc/Getting-Started.org +++ b/doc/Getting-Started.org @@ -17,7 +17,7 @@ java -jar conexp-clj-2.1.1-SNAPSHOT-standalone.jar This will get you a prompt for ~conexp-clj~ much like #+begin_src text -conexp.main=> +conexp.analysis=> #+end_src You can now use all the power of formal concept analysis from ~conexp-clj~, and @@ -25,7 +25,7 @@ also everything Clojure provides. For example, you can compute the value of the expression ~1 + 1~ as #+begin_src text -conexp.main=> (+ 1 1) +conexp.analysis=> (+ 1 1) 2 #+end_src diff --git a/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-allergens.ctx b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-allergens.ctx new file mode 100644 index 000000000..87bb90c42 --- /dev/null +++ b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-allergens.ctx @@ -0,0 +1,26 @@ +B + +7 +7 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +barley +milk +peanuts +almond +wheat +egg +soy +.XX..XX +XX..XX. +.X.X.XX +.X..XXX +.X...XX +XX..XXX +.X..XXX diff --git a/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors-small.ctx b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors-small.ctx new file mode 100644 index 000000000..42125ebc9 --- /dev/null +++ b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors-small.ctx @@ -0,0 +1,25 @@ +B + +7 +6 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +Choco +Brownie +Dough +Peanut +Vanilla +Caramel +X..X.. +XX.... +X....X +XX..XX +X....X +XXX.X. +X.X.X. diff --git a/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors.ctx b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors.ctx new file mode 100644 index 000000000..9a1466fd7 --- /dev/null +++ b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors.ctx @@ -0,0 +1,28 @@ +B + +7 +9 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +Choco Ice +Peanut Ice +Choco Pieces +Brownie +Dough +Peanut Butter +Caramel Ice +Vanilla +Caramel +.XX..X... +X..X..... +X.X...X.X +..XX...XX +..X...X.X +X.XXX..X. +..X.X..X. diff --git a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org new file mode 100644 index 000000000..a45a7a01b --- /dev/null +++ b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org @@ -0,0 +1,491 @@ +#+property: header-args :wrap src text +#+property: header-args:text :eval never + +* ~conexp-clj~ Tutorial at ICFCA 2023 + +This is a tutorial for the ICFCA 2023. It contains an example analysis of a +formal context with the tool ~conexp-clj~. + +** Basic FCA operations + +*** Getting started + +To run ~conexp-clj~, a Java Runtime Environment with version 1.8 or higher is necessary. +A pre-compiled version of ~conexp-clj~ is available [[https://algebra20.de/conexp/][here]]. The jar file can be used +like this: + +#+begin_src sh :exports both +java -jar conexp-clj-2.3.0-SNAPSHOT-standalone.jar +#+end_src + +A prompt for ~conexp-clj~ like this will appear: + +#+RESULTS: +#+begin_src text +conexp.main=> +#+end_src + +*** Read a context + +During the workshop, you can use your own context or, for example, the +[[../../../testing-data/Living-Beings-and-Water.ctx][Living-Beings-and-Water]] or [[../../../testing-data/ben-and-jerrys-flavors.ctx][Ben-and-Jerrys]] context. The examples in this tutorial use the +Ben-and-Jerrys ice cream context. + +It is possible to read formal contexts in several formats, e.g., Burmeister and csv. +A more detailed overview of the context formats can be found in [[../../IO.org][Input/Output of Formal Contexts]]. When reading a context, in most cases the format will be automatically determined: + +#+begin_src clojure :results silent +(def ben-and-jerrys-ctx (read-context "path-to-file/ben-and-jerrys-flavors.ctx")) +#+end_src + +If this does not work, you can specify the input format. A list of all input formats can be shown with + +#+begin_src clojure :exports both +(list-context-input-formats) +#+end_src + +#+RESULTS: +#+begin_src text +(:burmeister :csv :conexp :named-binary-csv :anonymous-burmeister :graphml :simple :binary-csv :fcalgs :colibri :json :galicia) +#+end_src + +You can read a context, e.g., in ~:burmeister~ format, by writing the format after the file path: + +#+begin_src clojure :results silent +(def ben-and-jerrys-ctx (read-context "path-to-file/ben-and-jerrys-flavors.ctx" :burmeister)) +#+end_src + +To see the formal context, evaluate the ~ben-and-jerrys-ctx~ variable explicitly. + +#+begin_src clojure :exports both +ben-and-jerrys-ctx +#+end_src + +The ben-and-jerrys context contains ice cream types as objects and ingredients as +attributes: + +#+RESULTS: +#+begin_src text + |Brownie Caramel Caramel Ice Choco Ice Choco Pieces Dough Peanut Butter Peanut Ice Vanilla +-----------------------+------------------------------------------------------------------------------------------ +Caramel Chew Chew |. x x . x . . . . +Caramel Sutra |. x x x x . . . . +Cookie Dough |. . . . x x . . x +Fudge Brownie |x . . x . . . . . +Half Baked |x . . x x x . . x +Peanut Butter Cup |. . . . x . x x . +Salted Caramel Brownie |x x . . x . . . x +#+end_src + +*** Reduce, clarify + +With ~context-reduced?~ and ~context-clarified?~, you can check if a context is reduced or clarified. +The attributes, objects and the whole context can be reduced with ~reduce-attributes~, +~reduce-objects~ and ~reduce-context~. The same applies to clarify. + +The whole example context can be clarified as follows: + +#+begin_src clojure :exports both +(def ben-and-jerrys-clarified + (clarify-context ben-and-jerrys-ctx) +ben-and-jerrys-clarified +#+end_src + +#+RESULTS: +#+begin_src text + |Brownie Caramel Caramel Ice Choco Ice Choco Pieces Dough Peanut Ice Vanilla +-----------------------+---------------------------------------------------------------------------- +Caramel Chew Chew |. x x . x . . . +Caramel Sutra |. x x x x . . . +Cookie Dough |. . . . x x . x +Fudge Brownie |x . . x . . . . +Half Baked |x . . x x x . x +Peanut Butter Cup |. . . . x . x . +Salted Caramel Brownie |x x . . x . . x +#+end_src + +As the attributes ~Peanut Butter~ and ~Peanut Ice~ have the same derivation, one of them (in this +case ~Peanut Butter~) is removed. + +*** Compute derivations + +~conexp-clj~ provides functions to compute the attribute and object derivation. +In the following example, the object derivation of two types of ice cream is +computed (to see what they have in common): + +#+begin_src clojure :exports both +(object-derivation ben-and-jerrys-ctx #{"Cookie Dough" "Half Baked"}) +#+end_src + +#+RESULTS: +#+begin_src text +#{"Choco Pieces" "Dough" "Vanilla"} +#+end_src + +The same can be done for a set of attributes with ~attribute-derivation~: + +#+begin_src clojure :exports both +(attribute-derivation ben-and-jerrys-ctx #{"Choco Pieces" "Dough" "Vanilla"}) +#+end_src + +#+RESULTS: +#+begin_src text +#{"Half Baked" "Cookie Dough"} +#+end_src + +This example shows that ~#{"Half Baked" "Cookie Dough"}~ is a closed set. +To directly compute the closure of a set of objects, you can use + +#+begin_src clojure :exports both +(context-object-closure ben-and-jerrys-ctx #{"Half Baked" "Cookie Dough"}) +#+end_src + +#+RESULTS: +#+begin_src text +#{"Half Baked" "Cookie Dough"} +#+end_src + +The closure of a set of attributes can be computed with ~context-attribute-closure~. + +#+begin_src clojure :exports both +(context-attribute-closure ben-and-jerrys-ctx #{"Choco Pieces" "Dough" "Vanilla"}) +#+end_src + +#+RESULTS: +#+begin_src text +#{"Choco Pieces" "Dough" "Vanilla"} +#+end_src + +All formal concepts of the context can be computed as + +#+begin_src clojure :exports both +(concepts ben-and-jerrys-ctx) +#+end_src + +#+RESULTS: +#+begin_src text +([#{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} #{}] + [#{"Fudge Brownie" "Caramel Sutra" "Half Baked"} #{"Choco Ice"}] + [#{} #{"Choco Ice" "Peanut Ice" "Choco Pieces" "Brownie" "Dough" "Peanut Butter" "Caramel Ice" "Vanilla" "Caramel"}] + [#{"Caramel Sutra" "Half Baked"} #{"Choco Ice" "Choco Pieces"}] + [#{"Half Baked"} #{"Choco Ice" "Choco Pieces" "Brownie" "Dough" "Vanilla"}] + [#{"Caramel Sutra"} #{"Choco Ice" "Choco Pieces" "Caramel Ice" "Caramel"}] + [#{"Fudge Brownie" "Half Baked"} #{"Choco Ice" "Brownie"}] + [#{"Peanut Butter Cup"} #{"Peanut Ice" "Choco Pieces" "Peanut Butter"}] + [#{"Peanut Butter Cup" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} #{"Choco Pieces"}] + [#{"Salted Caramel Brownie" "Half Baked"} #{"Choco Pieces" "Brownie" "Vanilla"}] + [#{"Salted Caramel Brownie"} #{"Choco Pieces" "Brownie" "Vanilla" "Caramel"}] + [#{"Half Baked" "Cookie Dough"} #{"Choco Pieces" "Dough" "Vanilla"}] + [#{"Caramel Sutra" "Caramel Chew Chew"} #{"Choco Pieces" "Caramel Ice" "Caramel"}] + [#{"Salted Caramel Brownie" "Half Baked" "Cookie Dough"} #{"Choco Pieces" "Vanilla"}] + [#{"Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew"} #{"Choco Pieces" "Caramel"}] + [#{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked"} #{"Brownie"}]) +#+end_src + +*** Draw the concept lattice + +To draw the concept lattice of a formal context, use these commands: +#+begin_src clojure :results silent +(use 'conexp.gui.draw) +(draw-concept-lattice ben-and-jerrys-ctx) +#+end_src + +The lattice will appear in a new window. + +#+caption: Concept lattice of ben-and-jerrys context +[[./images/ben-and-jerrys-lattice.png]] + +In left bar of the ~Lattice~ window, you have several options, e.g., you can change the +layout and turn on the labels. In addition, you have the option to show several +valuations, like probability, distributivity and stability. + +The ~ben-and-jerrys-lattice~ with DimDraw layout, labels and stability looks like this: + +#+caption: Concept lattice of ben-and-jerrys context with DimDraw layout, labels and +stability +[[./images/ben-and-jerrys-lattice-dimdraw-labels-stability.png]] + +You can also create your own valuations, e.g., the extent and intent size of each formal +concept. + +#+begin_src clojure :result silent +(draw-concept-lattice ben-and-jerrys-ctx + :value-fn (fn [concept] + [(count (first c)) (count (second c))])) +#+end_src + +After enabeling the labels, the concept lattice looks like this: + +#+caption: Concept lattice of ben-and-jerrys context with manually set valuations +[[./images/ben-and-jerrys-lattice-manual_valuations.png]] + +You can save the lattice in several formats. To do so, click ~Export to File~ (the second +last button on the left). A new window opens, in which you can specify the path and format. +In the example, the lattice is saved in ~tikz~ format. It is important to write the file +ending in the file name that matches the file type. Otherwise an error occurs. + +#+caption: Window to save concept lattice in tikz format +[[./images/ben-and-jerrys-export-tikz.png]] + +*** Computing implications - Canonical base + +The canonical base of a context can be computed with: + +#+begin_src clojure :exports both +(def ben-and-jerrys-implications (canonical-base ben-and-jerrys-ctx)) +ben-and-jerrys-implications +#+end_src + +#+RESULTS: +#+begin_src text +((#{"Caramel"} ⟶ #{"Choco Pieces"}) + (#{"Vanilla"} ⟶ #{"Choco Pieces"}) + (#{"Caramel Ice"} ⟶ #{"Choco Pieces" "Caramel"}) + (#{"Peanut Butter"} ⟶ #{"Peanut Ice" "Choco Pieces"}) + (#{"Dough"} ⟶ #{"Choco Pieces" "Vanilla"}) + (#{"Choco Pieces" "Vanilla" "Caramel"} ⟶ #{"Brownie"}) + (#{"Choco Pieces" "Brownie"} ⟶ #{"Vanilla"}) + (#{"Choco Pieces" "Brownie" "Caramel Ice" "Vanilla" "Caramel"} ⟶ #{"Choco Ice" "Peanut Ice" "Dough" "Peanut Butter"}) + (#{"Choco Pieces" "Brownie" "Dough" "Vanilla"} ⟶ #{"Choco Ice"}) + (#{"Peanut Ice"} ⟶ #{"Choco Pieces" "Peanut Butter"}) + (#{"Peanut Ice" "Choco Pieces" "Peanut Butter" "Caramel"} ⟶ #{"Choco Ice" "Brownie" "Dough" "Caramel Ice" "Vanilla"}) + (#{"Peanut Ice" "Choco Pieces" "Peanut Butter" "Vanilla"} ⟶ #{"Choco Ice" "Brownie" "Dough" "Caramel Ice" "Caramel"}) + (#{"Choco Ice" "Choco Pieces" "Caramel"} ⟶ #{"Caramel Ice"}) + (#{"Choco Ice" "Choco Pieces" "Vanilla"} ⟶ #{"Brownie" "Dough"}) + (#{"Choco Ice" "Peanut Ice" "Choco Pieces" "Peanut Butter"} ⟶ #{"Brownie" "Dough" "Caramel Ice" "Vanilla" "Caramel"})) +#+end_src + +*** Outputs + +Depending on the size of the contexts, the computation of the concept can take a long time. +Therefore, the results can be saved so that the computation does not need to be repeated. +For the output, the format needs to be specified. The formats to save a concept lattice are + +#+begin_src clojure :exports both +(list-lattice-output-formats) +#+end_src + +#+RESULTS: +#+begin_src text +(:simple :json) +#+end_src + +A concept lattice can be saved in the ~:json~ format with the following command + +#+begin_src clojure :result silent +(def ben-and-jerrys-lattice (concept-lattice ben-and-jerrys-ctx)) +(write-lattice :json ben-and-jerrys-lattice "path/ben-and-jerrys-lattice.json") +#+end_src + +(It can be loaded again with ~(read-lattice "path/ben-and-jerrys-lattice.json")~.) + +For implications, there is only the ~:json~ output format. Implications can be saved via + +#+begin_src clojure :result silent +(write-implication :json ben-and-jerrys-implications "path/ben-and-jerrys-implications.json") +#+end_src + +~conexp-clj~ also provides the option to save a whole Formal Concept Analysis in one +file (in ~:json~) format. This FCA needs to contain a formal context. The ~:lattice~ +and ~:implication-sets~ in the following map are optional. + +#+begin_src clojure :result silent +(def ben-and-jerrys-fca {:context ben-and-jerrys-context + :lattice ben-and-jerrys-lattice + :implication-sets [ben-and-jerrys-implications]}) +#+end_src + +Note that such an FCA object can contain several implication sets. The +~ben-and-jerrys-fca~ can be saved with + +#+begin_src clojure :result silent +(write-fca :json ben-and-jerrys-fca "path/ben-and-jerrys-fca.json") +#+end_src + + +** Scaling data and scale-measures + +~conexp-clj~ provides the functionality for conceptual scaling. For an example, +load the smaller ~ben-and-jerrys-flavors-small.ctx~: + +#+begin_src clojure :exports both +(def ben-and-jerrys-small-ctx (read-context "path-to-file/ben-and-jerrys-flavors-small.ctx")) +ben-and-jerrys-small-ctx +#+end_src + +The ben-and-jerrys context contains the same ice cream types as objects, but a smaller +set of flavors as attributes: + +#+RESULTS: +#+begin_src text + |Brownie Caramel Choco Dough Peanut Vanilla +-----------------------+------------------------------------------- +Caramel Chew Chew |. x x . . . +Caramel Sutra |. x x . . . +Cookie Dough |. . x x . x +Fudge Brownie |x . x . . . +Half Baked |x . x x . x +Peanut Butter Cup |. . x . x . +Salted Caramel Brownie |x x x . . x +#+end_src + +To check if this smaller context is a scale of the ~ben-and-jerrys-ctx~, the conceptual +scaling error is computed: + +#+begin_src clojure :exports both +(use 'conexp.fca.smeasure) +(conceptual-scaling-error (make-smeasure-nc ben-and-jerrys-ctx ben-and-jerrys-small-ctx identity)) +#+end_src + +#+RESULTS: +#+begin_src text +0 +#+end_src + +As the error is 0, the ~ben-and-jerrys-small-ctx~ is a scale of the ~ben-and-jerrys-ctx~. + +Another context uses the same ice cream types, but allergens instead of flavors. + +#+begin_src clojure :exports both +(def ben-and-jerrys-allergens-ctx (read-context "path-to-file/ben-and-jerrys-allergens.ctx")) +ben-and-jerrys-allergens-ctx +#+end_src + +#+RESULTS: +#+begin_src text + |almond barley egg milk peanuts soy wheat +-----------------------+----------------------------------------- +Caramel Chew Chew |. . x x . x . +Caramel Sutra |x . x x . x . +Cookie Dough |. . x x . x x +Fudge Brownie |. x x x . . x +Half Baked |. x x x . x x +Peanut Butter Cup |. . x x x x . +Salted Caramel Brownie |. . x x . x x +#+end_src + +You can compute the conceptual scaling error in the same way as for the ~ben-and-jerrys-small-ctx~. + +#+begin_src clojure :exports both +(conceptual-scaling-error (make-smeasure-nc ben-and-jerrys-ctx ben-and-jerrys-allergens-ctx identity)) +#+end_src + +#+RESULTS: +#+begin_src text +1 +#+end_src + +In this case, the error is 1 and therefore the allergens context is not a scale of the +original ~ben-and-jerrys-ctx~. + +To get more information about the scaling error, you can use + +#+begin_src clojure :exports both +(error-in-smeasure (make-smeasure-nc ben-and-jerrys-ctx ben-and-jerrys-allergens-ctx identity)) +#+end_src + +#+RESULTS: +#+begin_src text +(#{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked" "Cookie Dough"}) +#+end_src + +This set of objects is closed in ~ben-and-jerrys-allergens-ctx~, but not in ~ben-and-jerrys-ctx~, +as can be seen with + +#+begin_src clojure :exports both +(context-object-closure ben-and-jerrys-ctx + #{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked" "Cookie Dough"}) +#+end_src + +#+RESULTS: +#+begin_src text +#{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" + "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" + "Cookie Dough"} +#+end_src + + +** Attribute exploration + +~conexp-clj~ provides a function for attribute exploration. + +#+begin_src clojure :results silent +(attribute-exploration :context ben-and-jerrys-small-ctx) +#+end_src + +The following attribute exploration is interactive. For a suggested implication, the +user accepts or rejects it with ~yes~ or ~no~: + +#+begin_src text +Does the implication (#{} ⟶ #{Choco}) hold? no +#+end_src + +If an implication is rejected, a counterexample needs to be provided. First, the object +of the counterexample needs to be given. In this case, we give an additional "Peanut" +ice cream. + +#+begin_src text +Then please provide a counterexample +counterexample> object +Please enter new object: "Peanut" +#+end_src + +After that, the attributes of the counterexample are given in the following input format. + +#+begin_src text +counterexample> attributes +Please enter new attributes: "Peanut" "Vanilla" +#+end_src + +The process of providing a counterexample is finished with the input ~q~. It is possible +to give another counterexample. + +#+begin_src text +counterexample> q +Do you want to give another counterexample? no +#+end_src + +The following example shows an attribute exploration of the ~ben-and-jerrys-small-ctx~. +In the end, the attribute exploration returns the list of learned implications and the +new context. + +#+begin_src text +conexp.main=> (explore-attributes :context ben-and-jerrys-small-ctx) +Does the implication (#{} ⟶ #{Choco}) hold? no +Then please provide a counterexample +counterexample> object +Please enter new object: "Peanut" +counterexample> attributes +Please enter the attributes the new object should have: "Peanut" "Vanilla" +counterexample> q +Do you want to give another counterexample? no +Does the implication (#{Caramel} ⟶ #{Choco}) hold? yes +Does the implication (#{Dough} ⟶ #{Choco Vanilla}) hold? yes +Does the implication (#{Brownie} ⟶ #{Choco}) hold? yes +Does the implication (#{Choco Vanilla Caramel} ⟶ #{Brownie}) hold? yes +Does the implication (#{Choco Peanut Caramel} ⟶ #{Brownie Dough Vanilla}) hold? yes +Does the implication (#{Choco Peanut Vanilla} ⟶ #{Brownie Dough Caramel}) hold? yes +Does the implication (#{Choco Brownie Caramel} ⟶ #{Vanilla}) hold? yes +Does the implication (#{Choco Brownie Peanut} ⟶ #{Dough Vanilla Caramel}) hold? yes +Does the implication (#{Choco Brownie Dough Vanilla Caramel} ⟶ #{Peanut}) hold? yes +{:implications #{(#{"Choco" "Peanut" "Caramel"} ⟶ #{"Brownie" "Dough" "Vanilla"}) + (#{"Brownie"} ⟶ #{"Choco"}) + (#{"Choco" "Brownie" "Caramel"} ⟶ #{"Vanilla"}) + (#{"Caramel"} ⟶ #{"Choco"}) + (#{"Choco" "Vanilla" "Caramel"} ⟶ #{"Brownie"}) + (#{"Choco" "Brownie" "Dough" "Vanilla" "Caramel"} ⟶ #{"Peanut"}) + (#{"Choco" "Peanut" "Vanilla"} ⟶ #{"Brownie" "Dough" "Caramel"}) + (#{"Dough"} ⟶ #{"Choco" "Vanilla"}) + (#{"Choco" "Brownie" "Peanut"} ⟶ #{"Dough" "Vanilla" "Caramel"})}, +:context |Brownie Caramel Choco Dough Peanut Vanilla +-----------------------+------------------------------------------- +Caramel Chew Chew |. x x . . . +Caramel Sutra |. x x . . . +Cookie Dough |. . x x . x +Fudge Brownie |x . x . . . +Half Baked |x . x x . x +Peanut Butter Cup |. . x . x . +Peanut |. . . . x x +Salted Caramel Brownie |x x x . . x +} +#+end_src diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-export-tikz.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-export-tikz.png new file mode 100644 index 000000000..9076e6e60 Binary files /dev/null and b/doc/tutorials/icfca-2023/images/ben-and-jerrys-export-tikz.png differ diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-stability.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-stability.png new file mode 100644 index 000000000..a8de54f1b Binary files /dev/null and b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-stability.png differ diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-support.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-support.png new file mode 100644 index 000000000..803717d9a Binary files /dev/null and b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-support.png differ diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-manual_valuations.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-manual_valuations.png new file mode 100644 index 000000000..6b90de687 Binary files /dev/null and b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-manual_valuations.png differ diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice.png new file mode 100644 index 000000000..567b01e60 Binary files /dev/null and b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice.png differ diff --git a/flake.lock b/flake.lock index d197ce887..1b39c426f 100644 --- a/flake.lock +++ b/flake.lock @@ -3,20 +3,17 @@ "clj-nix": { "inputs": { "devshell": "devshell", - "flake-utils": [ - "utils", - "flake-utils" - ], + "nix-fetcher-data": "nix-fetcher-data", "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1677342613, - "narHash": "sha256-BqhKj7jQahSVThEwLHt164kJHGx9LXzBARFZaFNLPW8=", + "lastModified": 1689754411, + "narHash": "sha256-DBaZAmBR5EA03Zjf4k7XHqvOovJP+pFhzl+BJ4V+lFw=", "owner": "jlesquembre", "repo": "clj-nix", - "rev": "7d9e244ea96988524ba3bd6c2bbafdf0a5340b96", + "rev": "6a017fb2bc7b60c9e67b1c6f0b04bbefcf8dc698", "type": "github" }, "original": { @@ -27,21 +24,18 @@ }, "devshell": { "inputs": { - "flake-utils": [ - "clj-nix", - "flake-utils" - ], "nixpkgs": [ "clj-nix", "nixpkgs" - ] + ], + "systems": "systems" }, "locked": { - "lastModified": 1658746384, - "narHash": "sha256-CCJcoMOcXyZFrV1ag4XMTpAPjLWb4Anbv+ktXFI1ry0=", + "lastModified": 1687944744, + "narHash": "sha256-4ZtRVG/5yWHPZpkit1Ak5Mo1DDnkx1AG1HpNu/P+n5U=", "owner": "numtide", "repo": "devshell", - "rev": "0ffc7937bb5e8141af03d462b468bd071eb18e1b", + "rev": "3864857b2754ab0e16c7c7c626f0e5a1d4e42f38", "type": "github" }, "original": { @@ -50,6 +44,41 @@ "type": "github" } }, + "flake-part": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1685546676, + "narHash": "sha256-XDbjJyAg6odX5Vj0Q22iI/gQuFvEkv9kamsSbQ+npaI=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "6ef2707776c6379bc727faf3f83c0dd60b06e0c6", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1685546676, + "narHash": "sha256-XDbjJyAg6odX5Vj0Q22iI/gQuFvEkv9kamsSbQ+npaI=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "6ef2707776c6379bc727faf3f83c0dd60b06e0c6", + "type": "github" + }, + "original": { + "id": "flake-parts", + "type": "indirect" + } + }, "flake-utils": { "locked": { "lastModified": 1644229661, @@ -85,13 +114,36 @@ "type": "github" } }, + "nix-fetcher-data": { + "inputs": { + "flake-part": "flake-part", + "flake-parts": "flake-parts", + "nixpkgs": [ + "clj-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1685572850, + "narHash": "sha256-lYKEqFG9F84xu51H1rM1u+Ip88cINL0+W26sT+vFEZc=", + "owner": "jlesquembre", + "repo": "nix-fetcher-data", + "rev": "f14967db6c92c79b77419f52c22a698518c91120", + "type": "github" + }, + "original": { + "owner": "jlesquembre", + "repo": "nix-fetcher-data", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1685451684, - "narHash": "sha256-Y5iqtWkO82gHAnrBvNu/yLQsiVNJRCad4wWGz2a1urk=", + "lastModified": 1689605451, + "narHash": "sha256-u2qp2k9V1smCfk6rdUcgMKvBj3G9jVvaPHyeXinjN9E=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6b0edc9c690c1d8a729f055e0d73439045cfda55", + "rev": "53657afe29748b3e462f1f892287b7e254c26d77", "type": "github" }, "original": { @@ -101,6 +153,42 @@ "type": "github" } }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1682879489, + "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib_2": { + "locked": { + "dir": "lib", + "lastModified": 1682879489, + "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "clj-nix": "clj-nix", @@ -109,6 +197,21 @@ "utils": "utils" } }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "utils": { "inputs": { "flake-utils": "flake-utils" diff --git a/flake.nix b/flake.nix index 3767de07e..e7c9c5058 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,5 @@ { - description = - "conexp-clj, a general purpose software tool for Formal Concept Analysis"; + description = "conexp-clj, a general purpose software tool for Formal Concept Analysis"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; @@ -8,10 +7,7 @@ clj-nix = { url = "github:jlesquembre/clj-nix"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "utils/flake-utils"; - }; + inputs.nixpkgs.follows = "nixpkgs"; }; gitignore = { @@ -20,30 +16,55 @@ }; }; - outputs = { self, nixpkgs, utils, ... }@inputs: - let inherit (utils.lib) mkApp mkFlake; - in mkFlake { + outputs = { + self, + nixpkgs, + utils, + ... + } @ inputs: let + inherit (utils.lib) mkApp mkFlake; + in + mkFlake { inherit self inputs; - channels.nixpkgs.overlaysBuilder = channels: - [ inputs.clj-nix.overlays.default ]; + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + "aarch64-darwin" + "x86_64-darwin" + ]; + + channels.nixpkgs.overlaysBuilder = channels: [inputs.clj-nix.overlays.default]; overlays.default = final: prev: { inherit (self.packages."${final.system}") conexp-clj; }; - outputsBuilder = channels: - let - inherit (inputs.gitignore.lib) gitignoreSource; - inherit (inputs.clj-nix.lib) mk-deps-cache; - inherit (channels.nixpkgs) mkCljBin mkShell writeShellScriptBin; - - conexp = let - pname = "conexp-clj"; - version = "2.4.0"; - in mkCljBin rec { + outputsBuilder = channels: let + inherit (inputs.gitignore.lib) gitignoreSource; + inherit (inputs.clj-nix.lib) mk-deps-cache; + inherit (channels.nixpkgs) mkCljBin mkShell writeShellScriptBin; + inherit (channels.nixpkgs.lib) pipe; + + conexp = let + versionFromDefproject = name: + pipe ./project.clj [ + builtins.readFile + (builtins.match '' + .*\([[:SPACE:]]*defproject[[:SPACE:]]+${name}[[:SPACE:]]+"([^"]+)".*'') + builtins.head + ]; + pname = "conexp-clj"; + in + mkCljBin rec { name = "conexp/${pname}"; - inherit version; + version = versionFromDefproject pname; + + meta = { + description = "A General-Purpose Tool for Formal Concept Analysis"; + homepage = "https://github.com/tomhanika/conexp-clj"; + license = channels.nixpkgs.lib.licenses.epl10; + }; projectSrc = gitignoreSource ./.; main-ns = "conexp"; @@ -56,38 +77,43 @@ doCheck = true; checkPhase = "lein test"; }; + in rec { + packages = { + conexp-clj = conexp; + default = conexp; + }; - in { - packages = rec { - conexp-clj = conexp; - default = conexp-clj; + apps = rec { + deps-lock = mkApp { + drv = writeShellScriptBin "deps-lock" '' + ${channels.nixpkgs.deps-lock}/bin/deps-lock --lein $@ + ''; }; - apps = rec { - conexp-clj = mkApp { drv = conexp; }; - default = conexp-clj; - - deps-lock = mkApp { - drv = writeShellScriptBin "deps-lock" '' - ${channels.nixpkgs.deps-lock}/bin/deps-lock --lein $@ - ''; - }; - - test = let deps = mk-deps-cache { lock-file = ./deps-lock.json; }; - in mkApp { + test = let + deps = mk-deps-cache {lock-file = ./deps-lock.json;}; + in + mkApp { drv = writeShellScriptBin "conexp-clj-tests" '' lein test $@ ''; }; - }; - - devShells.default = mkShell { - buildInputs = with channels.nixpkgs; [ clojure-lsp leiningen ]; - }; + }; - formatter = channels.nixpkgs.alejandra; + checks = { + inherit (packages) conexp-clj; + devShell = devShells.default; + }; + devShells.default = mkShell { + buildInputs = with channels.nixpkgs; [clojure-lsp leiningen]; }; + formatter = channels.nixpkgs.alejandra; + }; }; } +# Local Variables: +# apheleia-formatter: alejandra +# End: + diff --git a/project.clj b/project.clj index cf14b513c..4edc68c30 100644 --- a/project.clj +++ b/project.clj @@ -7,7 +7,7 @@ ;; You must not remove this notice, or any other, from this software. -(defproject conexp-clj "2.4.0" +(defproject conexp-clj "2.4.2-SNAPSHOT" :min-lein-version "2.0.0" :description "A ConExp rewrite in clojure -- and so much more ..." @@ -52,11 +52,11 @@ :dev {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"] - [nrepl/nrepl "1.0.0"]] + [nrepl/nrepl "1.0.0"] + [com.clojure-goes-fast/clj-async-profiler "1.0.5"]] :plugins [[lein-aot-order "0.1.0"]] - :javac-options ["-Xlint:deprecation" "-Xlint:unchecked"]} - :gorilla {:main conexp.main - :plugins [[org.clojars.benfb/lein-gorilla "0.7.0"]]}} + :javac-options ["-Xlint:deprecation" "-Xlint:unchecked"] + :jvm-opts ["-Djdk.attach.allowAttachSelf"]}} :keep-non-project-classes true :source-paths ["src/main/clojure" "src/test/clojure"] :java-source-paths ["src/main/java"] @@ -64,4 +64,6 @@ :resource-paths ["src/main/resources"] :target-path "builds/%s" :compile-path "%s/classes/" - :java-opts ["-Dawt.useSystemAAFontSettings=lcd_hbgr" "-Xmx4G"]) + :java-opts ["-Dawt.useSystemAAFontSettings=on" "-Xmx4G"] + :repl-options {:init-ns conexp.analysis + :init (use 'conexp.analysis :reload)}) diff --git a/src/main/clojure/conexp/analysis.clj b/src/main/clojure/conexp/analysis.clj new file mode 100644 index 000000000..6489cd059 --- /dev/null +++ b/src/main/clojure/conexp/analysis.clj @@ -0,0 +1,64 @@ +(ns conexp.analysis + "Dafault Namespace." + (:require + [conexp.main :refer :all] + [conexp.base :refer :all] + [conexp.layouts :refer :all] + [conexp.gui :refer :all] + [conexp.api :refer :all] + [conexp.fca + [causal-implications :refer :all] + [contexts :refer :all] + [cover :refer :all] + [dependencies :refer :all] + [exploration :refer :all] + [graph :refer :all] + [implications :refer :all] + [incremental-ganter :refer :all] + [lattices :refer :all] + [many-valued-contexts :refer :all] + [metrics :refer :all] + [more :refer :all] + [posets :refer :all] + [pqcores :refer :all] + [protoconcepts :refer :all] + [random-contexts :refer :all] + [triadic-exploration :refer :all]] + [conexp.math + [algebra :refer :all] + [markov :refer :all] + [numbers :refer :all] + [optimize :refer :all] + [sampling :refer :all] + [statistics :refer :all] + [util :refer :all]] + [conexp.layouts + [base :refer :all] + [common :refer :all] + [dim-draw :refer :all] + ;[force :refer :all] + [freese :refer :all] + [layered :refer :all] + [util :refer :all]] + [conexp.io + [base :refer :all] + [contexts :refer :all] + [fcas :refer :all] + [implications :refer :all] + [incomplete-contexts :refer :all] + [json :refer :all] + [latex :refer :all] + [lattices :refer :all] + [layouts :refer :all] + [many-valued-contexts :refer :all] + [util :refer :all]] + [conexp.gui + [base :refer :all] + [draw :refer :all] + [plugins :refer :all] + [repl :refer :all] + [repl-utils :refer :all]] + [clojure.set :as set] + [clojure.edn :as edn] + [clojure.java.io :as io])) + diff --git a/src/main/clojure/conexp/fca/causal_implications.clj b/src/main/clojure/conexp/fca/causal_implications.clj new file mode 100644 index 000000000..386288a44 --- /dev/null +++ b/src/main/clojure/conexp/fca/causal_implications.clj @@ -0,0 +1,250 @@ +(ns conexp.fca.causal-implications + "Causal Implications for Formal Concept Analysis." + (:require + [conexp.base :refer :all] + [conexp.io.contexts :refer :all] + [conexp.fca.contexts :refer :all] + [conexp.fca.implications :refer :all] + [clojure.set :as set])) + +;For a full Explanation of the Concepts refer to *Mining Causal Association Rules* +;https://www.researchgate.net/publication/262240022_Mining_Causal_Association_Rules + + +(defn matched-record-pair? [ctx impl controlled-variables a b] + "Returns true if a and b form a matched record pair in respect to the context, implication and controlled variables, + false otherwise. + a and b for a matched record pair, if they both have the same value for each controlled variable, but only one contains + the premise of the implication." + (let [premise (premise impl) + conclusion (conclusion impl) + a-attributes (object-derivation ctx [a]) + b-attributes (object-derivation ctx [b])] + + ;check whether premise is present in exactly one of the objects + (and (or (and (subset? premise a-attributes) (not (subset? premise b-attributes))) + (and (subset? premise b-attributes) (not (subset? premise a-attributes)))) + ;check whether controlled variables have same realizations in both objects + (subset? controlled-variables + (set/union (set/intersection a-attributes b-attributes) + (set/intersection (set/difference controlled-variables a-attributes) + (set/difference controlled-variables b-attributes))))))) + +(defn find-matched-record-pair [ctx impl controlled-variables objs-considered a] + "Searches objs-considered for an object that forms a matched record pair with a, + then returns a set containing a and that element. Returns the empty set, if no match is found." + (let [objs (into [] objs-considered)] + (if (= (count objs) 0) + #{} + (if (matched-record-pair? ctx impl controlled-variables a (first objs)) + #{a (first objs)} + (find-matched-record-pair ctx impl controlled-variables (rest objs) a))))) + + +(defn fair-data-set [ctx impl controlled-variables] + "Computes the fair data set of ctx in respect to impl by finding matched record pairs + among the objects. Each object may only be matched with exactly one other object." + (let [objs (objects ctx)] + (filter seq + (reduce (fn [present-objs new-obj] + (if (contains? (reduce set/union present-objs) new-obj) + present-objs + (conj present-objs (find-matched-record-pair + ctx + impl + controlled-variables + (set/difference objs (reduce set/union present-objs)) + new-obj)))) + #{} objs)))) + +(defn- only-exposed [exposed nonexposed ctx premise-attr conclusion-attr] + "Returns true, if *exposed* contains both the premise and conclusion attributes, + and *nonexposed* does not contain the conclusion." + (and (incident? ctx exposed premise-attr) + (and (incident? ctx exposed conclusion-attr) + (not (incident? ctx nonexposed conclusion-attr)))) +) + +(defn- only-nonexposed [exposed nonexposed ctx premise-attr conclusion-attr] + "Returns true, if *exposed* contains the premise attribute, but only *nonexposed* + contains the conclusion attribute." + (and (incident? ctx exposed premise-attr) + (and (not (incident? ctx exposed conclusion-attr)) + (incident? ctx nonexposed conclusion-attr))) +) + +(defn fair-odds-ratio [ctx impl fair-data] + "Computes the odds ratio of an implication by dividing the number of matched pairs, + where the only the exposed element contains the conclusion by the number of matched pairs, + where only the non-exposed object contains the conclusion. + (The divisor is capped at a minimum of 1) + Only works on implications with single attributes as premise and conclusion." + (let [premise-attr (first (premise impl)) + conclusion-attr (first (conclusion impl))] + + (/ (reduce + (for [pair fair-data] + (let [a (first pair), b (first (rest pair))] + (if (or (only-exposed a b ctx premise-attr conclusion-attr) + (only-exposed b a ctx premise-attr conclusion-attr)) + 1 + 0)))) + + (max (reduce + (for [pair fair-data] + (let [a (first pair), b (first (rest pair))] + (if (or (only-nonexposed a b ctx premise-attr conclusion-attr) + (only-nonexposed b a ctx premise-attr conclusion-attr)) + 1 + 0)))) + + 1)))) + + +(defn- confidence-bound [op ctx premise conclusion odds-ratio zconf] + "Used to compute the bounds of the confidence interval within the confidence-interval method. + Computes the upper bound if + is supplied as op, lower bound if - is supplied." + (Math/exp (op (Math/log odds-ratio) + (* zconf (Math/sqrt (+ (/ 1 (absolute-support ctx [(set/union premise conclusion) #{}])) + (/ 1 (absolute-support ctx [premise conclusion])) + (/ 1 (absolute-support ctx [conclusion premise])) + (/ 1 (absolute-support ctx [#{} (set/union premise conclusion)])))))))) + +(defn confidence-interval [ctx impl odds-ratio zconf] + "Computes the confidence interval of the implication. odds-ratio is the regular odds ratio of + the implication on its context, zconf is a standard normal deviate corresponding to the desired + level of confidence. (1.7 => 70% confidence)" + (let [premise (premise impl) + conclusion (conclusion impl)] + [(confidence-bound + ctx premise conclusion odds-ratio zconf) + (confidence-bound - ctx premise conclusion odds-ratio zconf)])) + +(defn- fair-confidence-bound [op ctx premise-attr conclusion-attr fair-odds-ratio fair-data zconf] + "Used to compute the bounds of the fair confidence interval within the fair-confidence-interval method. + Computes the upper bound if + is supplied as op, lower bound if - is supplied." + (Math/exp (op (Math/log fair-odds-ratio) + (* zconf (Math/sqrt (+ (/ 1 (max (reduce + + (for [pair fair-data] + (let [a (first pair), b (first (rest pair))] + (if (or (only-exposed a b ctx premise-attr conclusion-attr) + (only-exposed b a ctx premise-attr conclusion-attr)) + 1 + 0)))) + + 1)) + (/ 1 (max (reduce + + (for [pair fair-data] + (let [a (first pair), b (first (rest pair))] + (if (or (only-nonexposed a b ctx premise-attr conclusion-attr) + (only-nonexposed b a ctx premise-attr conclusion-attr)) + 1 + 0)))) + 1)) )))))) + +(defn fair-confidence-interval [ctx impl fair-odds-ratio fair-data zconf] + "Computes the confidence interval of an implication on its fair data set. + fair-odds-ratio is the implication's odds ratio on its fair data set, + zconf is a standard normal deviate corresponding to the desired + level of confidence. (1.7 => 70% confidence) + Only works on implications with single attributes as premise and conclusion." + (let [premise-attr (first (premise impl)) + conclusion-attr (first (conclusion impl))] + [(fair-confidence-bound + ctx premise-attr conclusion-attr fair-odds-ratio fair-data zconf) + (fair-confidence-bound - ctx premise-attr conclusion-attr fair-odds-ratio fair-data zconf)])) + +(defn causally-relevant? [ctx variable response-variable zconfidence] + "Computes whether a variable is relevant to the response variable, by computing whether or not it + is associated with the response variable. + A variable p is associated with the response variable z, if the lower bound of the confidence interval of the + implication p -> z is greater than 1." + (let [impl (->Implication #{variable} #{response-variable})] + (> (last (confidence-interval ctx impl (odds-ratio ctx impl) zconfidence)) + 1))) + +(defn irrelevant-variables [ctx vars response-variable zconfidence] + "Returns a set that contains all variables in vars that are irrelevant to response-var." + (set (filter #(not (causally-relevant? ctx % response-variable zconfidence)) vars))) + +(defn exclusive-variables [ctx item-set thresh] + "Finds mutually exclusive variables to those in item-set. Returns tuples of the exclusive variables. + Two variables a and b are mutually exclusive, if the absolute support for (a and b) or (b and not a) + is no larger than thresh." + (set + (filter some? + (for [a item-set, b (attributes ctx)] + (if (not (= a b)) + (if (or (<= (absolute-support ctx [#{a b} #{}]) thresh) + (<= (absolute-support ctx [#{b} #{a}]) thresh)) + #{a b})))))) + +(defn causal? [ctx impl irrelevant-vars zconf thresh] + "Computes whether an implication is causal. An implication is causal, if the lower bound of its fair + confidence interval is greather than 1." + (let [premise (premise impl) + conclusion (conclusion impl) + E (reduce set/union (exclusive-variables ctx premise thresh)) + controlled-variables (set/difference (attributes ctx) + (set/union conclusion irrelevant-vars E premise)) + fair-data (fair-data-set ctx impl controlled-variables) + fair-odds (fair-odds-ratio ctx impl fair-data)] + + (< 1 (last (fair-confidence-interval ctx impl fair-odds fair-data zconf))))) + +(defn generate-causal-rules [ctx premises response-var irrelevant-vars zconf thresh] + "Generates all causal implications comprised of the premise in premises and the response variable. + Returns only the premises of the causal implications." + (filter #(causal? ctx (->Implication % #{response-var}) irrelevant-vars zconf thresh) premises)) + +(defn find-redundant [ctx current-item-sets new-item-sets response-var] + "Computes redundant rules by comparing the support of the premise to that of its subsets. + If they have the same support, they cover the same objects, and the more specific rule redundant." + (set (for [new new-item-sets, old current-item-sets] + (if (and (subset? old new) + (= (local-support ctx (->Implication new #{response-var})) + (local-support ctx (->Implication old #{response-var})))) + new)))) + +(defn causal-association-rule-discovery + ([ctx min-lsupp max-length response-var zconf] + "Computes all causal implication rules with response-var as the conclusion. Returns only the premises of the causal + implications. Trivial implications are not considered. + min-lsupp is the minimum local support required of variables to be testet. + zconf is a standard normal deviate corresponding to the desired level of confidence. (1.7 => 70% confidence)" + ;initial setup + (let [frequent-vars (set (filter + #(> (local-support ctx (->Implication #{%} #{response-var})) min-lsupp) + (attributes ctx))) + ivars (irrelevant-variables ctx frequent-vars response-var zconf)] + + (causal-association-rule-discovery + ctx ;context + #{} ;current causal rules + (set/difference frequent-vars #{response-var}) ;frequent single variables + (for [x (set/difference frequent-vars #{response-var})] #{x}) ;itemsets of the current iteration + ivars ;irrelevant variables in respect to response-var + min-lsupp ;minimum local support + 0 ;counter, counts up to max-length + max-length ;maximum length of rules + response-var ;response variable + zconf ;confidence for significance test (1.7) + )) + ) + + ([ctx rule-set variables current ivars min-lsupp counter max-length response-var zconf] + + (if (= counter max-length) + rule-set + (let [new-causal-rules (generate-causal-rules ctx current response-var ivars zconf 1) + new-item-sets (set (filter #(= (count %) (+ counter 2)) (for [c current, i variables] (conj c i))))] + + (causal-association-rule-discovery + ctx + (set/union rule-set new-causal-rules) + variables + (set/difference (filter #(> (local-support ctx (->Implication #{%} #{response-var})) min-lsupp) new-item-sets) + (find-redundant ctx current new-item-sets response-var));filter item sets + ivars + min-lsupp + (inc counter) + max-length + response-var + zconf))))) + diff --git a/src/main/clojure/conexp/fca/exploration.clj b/src/main/clojure/conexp/fca/exploration.clj index 4808c4f49..482298902 100644 --- a/src/main/clojure/conexp/fca/exploration.clj +++ b/src/main/clojure/conexp/fca/exploration.clj @@ -13,6 +13,26 @@ conexp.fca.implications) (:require [clojure.core.reducers :as r])) + +(defn exploration-step + "Conduct one exploration step using counterexamples and background knowledge about implications" + [ctx input-implications] + (loop [implications input-implications + last (close-under-implications implications #{}), + ctx ctx] + (if (not last) + {:implications (difference (set implications) (set input-implications)) :context ctx} + (let [conclusion-from-last (context-attribute-closure ctx last)] + (if (= last conclusion-from-last) + (recur implications + (next-closed-set (attributes ctx) + (clop-by-implications implications) + last) + ctx) + (let [newimp (make-implication last conclusion-from-last)] + (recur (conj implications newimp) nil ctx)) ;; new candidate implication + ))))) + ;;; Helpers (defn falsifies-implication? diff --git a/src/main/clojure/conexp/fca/implications.clj b/src/main/clojure/conexp/fca/implications.clj index c1593d96e..e560d1a46 100644 --- a/src/main/clojure/conexp/fca/implications.clj +++ b/src/main/clojure/conexp/fca/implications.clj @@ -11,7 +11,8 @@ (:require [clojure.core.reducers :as r] [conexp.base :refer :all] [conexp.math.algebra :refer :all] - [conexp.fca.contexts :refer :all])) + [conexp.fca.contexts :refer :all] + [clojure.set :as set])) ;;; @@ -573,6 +574,22 @@ :else (illegal-argument "Cannot determine support of " (print-str thing)))) +(defn absolute-support + "Counts the total number of occurences of an itemset in the context. + itemset needs to consist of two entries a and b, both sets of attributes. + absolute-support computes the support of the itemset with all attributes in a and the + negation of each attribute in b." + [ctx itemset] + (let [[attributes neg-attributes] itemset, objects (objects ctx), incidence (incidence-relation ctx)] + (max (count (filter identity (for [object objects] + (every? true? (concat + (for [attribute attributes] + (some? (incident? ctx object attribute))) + (for [attribute neg-attributes] + (not (some? (incident? ctx object attribute))))))))) + 1 +))) + (defn confidence "Computes the confidence of the given implication in the given context." [implication context] @@ -583,7 +600,29 @@ (union (premise implication) (conclusion implication)))) premise-count)))) -;; +(defn absolute-confidence + "Computes the confidence of an implication using the absolute-support method." + [ctx impl] + (let [premise (premise impl) conclusion (conclusion impl)] + (/ (absolute-support ctx [(set/union premise conclusion) #{}]) + (absolute-support ctx [premise #{}])))) + +(defn odds-ratio + "Computes the odds ratio of an implication using the asupp method." + [ctx impl] + (let [premise (premise impl) conclusion (conclusion impl)] + (/ (* (absolute-support ctx [(set/union premise conclusion) #{}]) + (absolute-support ctx [#{} (set/union premise conclusion)])) + (* (absolute-support ctx [premise conclusion]) + (absolute-support ctx [conclusion premise])) + ))) + +(defn local-support [ctx impl] + "Computes the local support of an implication by dividing the support of the implication + by the support of its conclusion. Uses the absolute-support function." + (let [premise (premise impl) conclusion (conclusion impl)] + (/ (absolute-support ctx [(set/union premise conclusion) #{}]) + (absolute-support ctx [conclusion #{}])))) (defn- frequent-itemsets "Returns all frequent itemsets of context, given minsupp as minimal support." diff --git a/src/main/clojure/conexp/fca/lattices.clj b/src/main/clojure/conexp/fca/lattices.clj index 20a3bfcd2..5312e146c 100644 --- a/src/main/clojure/conexp/fca/lattices.clj +++ b/src/main/clojure/conexp/fca/lattices.clj @@ -11,7 +11,8 @@ (:use conexp.base conexp.math.algebra conexp.fca.contexts - conexp.fca.posets)) + conexp.fca.posets) + (:gen-class)) ;;; Datastructure @@ -460,6 +461,18 @@ (let [B+D (intersection B D)] [(attribute-derivation ctx B+D) B+D])))))) +(defn generated-sublattice [lat generators] + "Computes the sublattice of the specified lattice with the specified set of generators." + (let [lat-join (sup lat) + lat-meet (inf lat)] + (loop [X generators] + (let [X-new (clojure.set/union (into #{} (for [a X b X] (lat-join a b))) + (into #{} (for [a X b X] (lat-meet a b))))] + (if (= X X-new) (make-lattice X lat-meet lat-join) + (recur X-new))))) +) + + ;;; nil diff --git a/src/main/clojure/conexp/fca/metric_context.clj b/src/main/clojure/conexp/fca/metric_context.clj new file mode 100644 index 000000000..f7c6f3a40 --- /dev/null +++ b/src/main/clojure/conexp/fca/metric_context.clj @@ -0,0 +1,339 @@ +(ns conexp.fca.metric-context + (:require [conexp.base :refer :all] + [conexp.fca.contexts :refer :all] + [clojure.set :as set])) + +(defn object-hamming-template [ctx obj1 obj2] + "Computes the Hamming distance between objects by comparing the incident attributes." + (if (and (.contains (objects ctx) obj1) + (.contains (objects ctx) obj2)) + (count (set/union (set/difference (object-derivation ctx #{obj1}) + (object-derivation ctx #{obj2})) + (set/difference (object-derivation ctx #{obj2}) + (object-derivation ctx #{obj1})))))) + +(defn attribute-hamming-template [ctx attr1 attr2] + "Computes the Hamming distance between attributes by comparing the incident objects." + (if (and (.contains (attributes ctx) attr1) + (.contains (attributes ctx) attr2)) + (count (set/union (set/difference (attribute-derivation ctx #{attr1}) + (attribute-derivation ctx #{attr2})) + (set/difference (attribute-derivation ctx #{attr2}) + (attribute-derivation ctx #{attr1})))))) + + +(defn create-object-hamming [ctx] + "Returns a function that computes the Hamming distance between objects of the specified context." + #(object-hamming-template ctx %1 %2) +) + +(defn create-attribute-hamming [ctx] + "Returns a function that computes the Hamming distance between attributes of the specified context." + #(attribute-hamming-template ctx %1 %2) +) + +(defprotocol Metric-Context + + (context [this] "Returns the underlying context.") + + (object-metrics [this] "Returns a map of the metrics on objects and their names/keys.") + (attribute-metrics [this] "Returns a map of the metrics on attributes and their names/keys.") + + (object-distance [this metric obj1 obj2] "Computes the distance between two objects based on the specified metric.") + (attribute-distance [this metric attr1 attr2] "Computes the distance between two attributes based on the specified metric.") + + (object-hamming [this] "Returns a Hamming metric function for the objects of the current context.") + (attribute-hamming [this] "Returns a Hamming metric function for the attributes of the current context.")) + +(deftype metric-context [ctx object-metrics attribute-metrics] + + Context + (objects [this] (objects ctx)) + (attributes [this] (attributes ctx)) + (incidence [this] (incidence ctx)) + + Metric-Context + (context [this] ctx) + (object-metrics [this] object-metrics) + (attribute-metrics [this] attribute-metrics) + (object-distance [this metric obj1 obj2] (metric obj1 obj2)) + (attribute-distance [this metric attr1 attr2] (metric attr1 attr2)) + + (object-hamming [this] (create-object-hamming this)) + (attribute-hamming [this] (create-attribute-hamming this)) + +) + +(defn make-object-valuation [mctx dist-fn metric-name] + "Returns a valuation function that displays the result of dist-fn on each nodes extent." + #(dist-fn mctx metric-name (first %)) +) + +(defn make-attribute-valuation [mctx dist-fn metric-name] + "Returns a valuation function that displays the result of dist-fn on each nodes intent." + #(dist-fn mctx metric-name (second %)) +) + +(defn make-combined-valuation [obj-value-fn attr-value-fn] + "Returns a valuation function that displays a tuple of the results of the specified function. + The first of those functions is meant to compute valuations on extents, the second one on intents." + #( identity [(obj-value-fn %) (attr-value-fn %)]) +) + + +(defn add-object-metrics [mctx metrics] + "Adds metrics to the context's object metrics. + The metrics need to be input as a map of names/keys and the corresponding functions." + (metric-context. (context mctx) + (merge (object-metrics mctx) metrics) + (attribute-metrics mctx)) + ) + +(defn add-attribute-metrics [mctx metrics] + "Adds metrics to the context's attribute metrics. + The metrics need to be input as a map of names/keys and the corresponding functions." + (metric-context. (context mctx) + (object-metrics mctx) + (merge (attribute-metrics mctx) metrics)) + ) + +(defn remove-object-metric [mctx metric-name] + "Removes the metric with the specified name/key from the context's object metrics." + (metric-context. (context mctx) + (dissoc (object-metrics mctx) metric-name) + (attribute-metrics mctx)) + ) + +(defn remove-attribute-metric [mctx metric-name] + "Removes the metric with the specified name/key from the context's attribute metrics." + (metric-context. (context mctx) + (object-metrics mctx) + (dissoc (attribute-metrics mctx) metric-name)) + ) + + +(defn convert-to-metric-context + ( + [ctx] + "Converts a context to a metric context." + (metric-context. ctx {} {}) + ) + ( + [ctx object-metrics attribute-metrics] + "Converts a context to a metric context and adds specified metrics. + The metrics need to be input as maps of names/keys and corresponding functions for both object and attribute metrics." + (add-attribute-metrics (add-object-metrics (convert-to-metric-context ctx) object-metrics) attribute-metrics) + ) +) + + +(defn make-metric-context + ( + [objects attributes incidence] + "Creates a new metric context, based on its objects, attributes and incidence relation." + (convert-to-metric-context (make-context objects attributes incidence))) + + ( + [objects attributes incidence object-metrics attribute-metrics] + "Creates a new metric context, based on its objects, attributes and incidence relation, and adds metrics to the new context. + The metrics need to be input as maps of names/keys and corresponding functions for both object and attribute metrics." + (convert-to-metric-context (make-context objects attributes incidence) object-metrics attribute-metrics)) +) + + + +(defn max-object-distance + ( + [mctx metric-name] + "Computes the maximum distance between objects of the context using the specified metric." + (max-object-distance mctx metric-name (objects mctx)) + ) + + ( + [mctx metric-name objs] + "Computes the maximum distance between the specified objects using the specified metric. + The maximum distance on an empty set of objects is by convention defined to be 0." + (if (< (count objs) 2) 0 + (apply max (for [obj1 objs + obj2 (set/difference objs #{obj1})] + (object-distance mctx metric-name obj1 obj2))))) +) + +(defn min-object-distance + ( + [mctx metric-name] + "Computes the minimum distance between objects of the context using the specified metric." + (min-object-distance mctx metric-name (objects mctx)) + ) + + ( + [mctx metric-name objs] + "Computes the minimum distance between the specified objects using the specified metric. + The minimum distance on an empty set of objects is by convention defined to be 0." + (if (< (count objs) 2) 0 + (apply min (for [obj1 objs + obj2 (set/difference objs #{obj1})] + (object-distance mctx metric-name obj1 obj2))))) +) + +(defn average-object-distance + ( + [mctx metric-name] + "Computes the average distance between objects of the context using the specified metric." + (average-object-distance mctx metric-name (objects mctx)) + ) + + ( + [mctx metric-name objs] + "Computes the average distance between the specified objects using the specified metric. + The average distance on an empty set of objects is by convention defined to be 0." + (if (< (count objs) 2) 0 + (apply #(/ (reduce + %) (count %)) [(for [obj1 objs + obj2 (set/difference objs #{obj1})] + (object-distance mctx metric-name obj1 obj2))]))) +) + + +(defn max-attribute-distance + ( + [mctx metric-name] + "Computes the maximum distance between attributess of the context using the specified metric." + (max-attribute-distance mctx metric-name (attributes mctx)) + ) + + ( + [mctx metric-name attrs] + "Computes the maximum distance between the specified attributes using the specified metric. + The maximum distance on an empty set of attributes is by convention defined to be 0." + (if (< (count attrs) 2) 0 + (apply max (for [attr1 attrs + attr2 (set/difference attrs #{attr1})] + (attribute-distance mctx metric-name attr1 attr2))))) +) + +(defn min-attribute-distance + ( + [mctx metric-name] + "Computes the minimum distance between attributes of the context using the specified metric." + (min-attribute-distance mctx metric-name (attributes mctx)) + ) + + ( + [mctx metric-name attrs] + "Computes the minimum distance between the specified attributes using the specified metric. + The minimum distance on an empty set of attributes is by convention defined to be 0." + (if (< (count attrs) 2) 0 + (apply min (for [attr1 attrs + attr2 (set/difference attrs #{attr1})] + (attribute-distance mctx metric-name attr1 attr2))))) +) + +(defn average-attribute-distance + ( + [mctx metric-name] + "Computes the average distance between attributes of the context using the specified metric." + (average-attribute-distance mctx metric-name (attributes mctx)) + ) + + ( + [mctx metric-name attrs] + "Computes the average distance between the specified attributes using the specified metric. + The average distance on an empty set of attributes is by convention defined to be 0." + (if (< (count attrs) 2) 0 + (apply #(/ (reduce + %) (count %)) [(for [attr1 attrs + attr2 (set/difference attrs #{attr1})] + (attribute-distance mctx metric-name attr1 attr2))]))) +) + + + +(defn object-confusion-matrix [mctx metric-name & opts] + "Return a matrix of all distances between objects computed using the specified metric. + Also returns a vector denoting the order of entries. + Use :norm to mormalize the distances." + (let [objs (into [] (objects mctx)) + divisor (if (and opts (.contains opts :norm)) (max-object-distance mctx metric-name) 1)] + [objs + (into [] (for [obj1 objs] + (into [] (for [obj2 objs] + (/ (object-distance mctx metric-name obj1 obj2) divisor)))))]) +) + +(defn attribute-confusion-matrix [mctx metric-name & opts] + "Return a matrix of all distances between attributes computed using the specified metric. + Also returns a vector denoting the order of entries. + Use :norm to mormalize the distances." + (let [attrs (into [] (attributes mctx)) + divisor (if (and opts (.contains opts :norm)) (max-attribute-distance mctx metric-name) 1)] + [attrs + (into [] (for [attr1 attrs] + (into [] (for [attr2 attrs] + (/ (attribute-distance mctx metric-name attr1 attr2) divisor)))))]) +) + + +;;;For the functions below, keep in mind that metrics may no longer work as intended after the context has been altered. + +(defn dual-metric-context [mctx] + "Computes the dual context of a metric context. Metrics remain unchanged, but object metrics become + attribute metrics and vice versa." + (convert-to-metric-context (dual-context (context mctx)) + (attribute-metrics mctx) + (object-metrics mctx)) +) + +(defn invert-metric-context [mctx] + "Computes the inverted context of a metric context. Metrics remain unchanged." + (convert-to-metric-context (invert-context (context mctx)) + (object-metrics mctx) + (attribute-metrics mctx)) +) + +(defn metric-context-union [mctx1 mctx2] + "Computes the union of two metric contexts. The resulting metric context contains the metrics of both contexts. + If metrics have the same name/key, those of the latter context are retained." + (convert-to-metric-context (context-union (context mctx1) (context mctx2)) + (merge (object-metrics mctx1) (object-metrics mctx2)) + (merge (attribute-metrics mctx1) (attribute-metrics mctx2))) +) + +(defn metric-context-disjoint-union [mctx1 mctx2] + "Computes the disjoint union of two metric contexts. The resulting metric context contains the metrics of both contexts. + If metrics have the same name/key, those of the latter context are retained." + (convert-to-metric-context (context-disjoint-union (context mctx1) (context mctx2)) + (merge (object-metrics mctx1) (object-metrics mctx2)) + (merge (attribute-metrics mctx1) (attribute-metrics mctx2))) +) + +(defn metric-context-intersection [mctx1 mctx2] + "Computes the intersection of two metric contexts. The resulting metric context contains the metrics of both contexts. + If metrics have the same name/key, those of the latter context are retained." + (convert-to-metric-context (context-intersection (context mctx1) (context mctx2)) + (merge (object-metrics mctx1) (object-metrics mctx2)) + (merge (attribute-metrics mctx1) (attribute-metrics mctx2))) +) + +(defn metric-context-composition [mctx1 mctx2] + "Computes the composition of two metric contexts. The resulting metric context contains the metrics of both contexts. + If metrics have the same name/key, those of the latter context are retained." + (convert-to-metric-context (context-composition (context mctx1) (context mctx2)) + (merge (object-metrics mctx1) (object-metrics mctx2)) + (merge (attribute-metrics mctx1) (attribute-metrics mctx2))) +) + +(defn metric-context-apposition [mctx1 mctx2] + "Computes the apposition of two metric contexts. The resulting metric context contains the metrics of both contexts. + If metrics have the same name/key, those of the latter context are retained." + (convert-to-metric-context (context-apposition (context mctx1) (context mctx2)) + (merge (object-metrics mctx1) (object-metrics mctx2)) + (merge (attribute-metrics mctx1) (attribute-metrics mctx2))) +) + +(defn metric-context-subposition [mctx1 mctx2] + "Computes the subposition of two metric contexts. The resulting metric context contains the metrics of both contexts. + If metrics have the same name/key, those of the latter context are retained." + (convert-to-metric-context (context-subposition (context mctx1) (context mctx2)) + (merge (object-metrics mctx1) (object-metrics mctx2)) + (merge (attribute-metrics mctx1) (attribute-metrics mctx2))) +) + diff --git a/src/main/clojure/conexp/gui/editors/contexts.clj b/src/main/clojure/conexp/gui/editors/contexts.clj index c88d2e16a..ccdc66446 100644 --- a/src/main/clojure/conexp/gui/editors/contexts.clj +++ b/src/main/clojure/conexp/gui/editors/contexts.clj @@ -35,6 +35,16 @@ (make-context-editor thing) (str "Context " path))))) +(defn- load-binary-csv-and-go + "Loads a named binary csv and adds a new tab with a context-editor." + [frame] + (when-let [^File file (choose-open-file frame)] + (let [path (.getPath file), + thing (read-context path :named-binary-csv)] + (add-tab frame + (make-context-editor thing) + (str "Context " path))))) + (defn- clone-context-and-go "Loads context with given loader and adds a new tab with a context-editor." [frame] @@ -104,6 +114,9 @@ (menu-item :text "Load Context", :listen [:action (fn [_] (load-context-and-go frame))]), + (menu-item :text "Load Binary CSV", + :listen [:action (fn [_] + (load-binary-csv-and-go frame))]), (menu-item :text "Random Context", :listen [:action (fn [_] (context-and-go frame (rand-context 5 5 0.4)))]), diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 6b237e070..4f44212ec 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -667,7 +667,7 @@ [file] (with-in-reader file (let [file-content (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/context_schema_v1.1.json"] + schema-file "schemas/context_schema_v1.1.json"] (assert (matches-schema? file-content schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->ctx file-content)))) diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj index 024748c73..c51bfc3b4 100644 --- a/src/main/clojure/conexp/io/fcas.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -65,7 +65,7 @@ [file] (with-in-reader file (let [json-fca (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/fca_schema_v1.0.json"] + schema-file "schemas/fca_schema_v1.0.json"] (assert (matches-schema? json-fca schema-file) (str "The input file does not match the schema given at " schema-file ".")) (create-fca-input-map json-fca)))) diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index 3531ce854..7bfdef23a 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -62,7 +62,7 @@ [file] (with-in-reader file (let [impl (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/implications_schema_v1.0.json"] + schema-file "schemas/implications_schema_v1.0.json"] (assert (matches-schema? impl schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->implications impl)))) diff --git a/src/main/clojure/conexp/io/json.clj b/src/main/clojure/conexp/io/json.clj index fae1f57a3..f5885bc8f 100644 --- a/src/main/clojure/conexp/io/json.clj +++ b/src/main/clojure/conexp/io/json.clj @@ -9,13 +9,14 @@ (ns conexp.io.json "Provides functionality to read and process json files" (:require [json-schema.core :as json-schema] - [clojure.data.json :as json])) + [clojure.data.json :as json] + [clojure.java.io :as io])) (defn- read-schema "Returns the file content as Json object." [file] (json-schema/prepare-schema - (-> file slurp + (-> (io/resource file) slurp (cheshire.core/parse-string true)) ;; referencing inside of schemas with relative references {:classpath-aware? true diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 5a83501f0..b64d7af39 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -102,7 +102,7 @@ [file] (with-in-reader file (let [json-lattice (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/lattice_schema_v1.1.json"] + schema-file "schemas/lattice_schema_v1.1.json"] (assert (matches-schema? json-lattice schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->lattice json-lattice)))) diff --git a/src/main/clojure/conexp/io/layouts.clj b/src/main/clojure/conexp/io/layouts.clj index 87f30778e..521d6a549 100644 --- a/src/main/clojure/conexp/io/layouts.clj +++ b/src/main/clojure/conexp/io/layouts.clj @@ -275,7 +275,7 @@ [file] (with-in-reader file (let [json-layout (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/layout_schema_v1.0.json"] + schema-file "schemas/layout_schema_v1.0.json"] (assert (matches-schema? json-layout schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->layout json-layout)))) diff --git a/src/main/clojure/conexp/io/many_valued_contexts.clj b/src/main/clojure/conexp/io/many_valued_contexts.clj index 9a6d782c4..d4a6869fc 100644 --- a/src/main/clojure/conexp/io/many_valued_contexts.clj +++ b/src/main/clojure/conexp/io/many_valued_contexts.clj @@ -165,7 +165,7 @@ [file] (with-in-reader file (let [json-mv-context (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/mv-context_schema_v1.0.json"] + schema-file "schemas/mv-context_schema_v1.0.json"] (assert (matches-schema? json-mv-context schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->mv-context json-mv-context)))) diff --git a/src/test/clojure/conexp/fca/causal_implications_test.clj b/src/test/clojure/conexp/fca/causal_implications_test.clj new file mode 100644 index 000000000..b7460123e --- /dev/null +++ b/src/test/clojure/conexp/fca/causal_implications_test.clj @@ -0,0 +1,166 @@ +;; Copyright ⓒ the conexp-clj developers; all rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file LICENSE at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns conexp.fca.causal-implications-test + (:require + [conexp.base :refer :all] + [conexp.io.contexts :refer :all] + [conexp.fca.contexts :refer :all] + [conexp.fca.implications :refer :all] + [conexp.fca.causal-implications :refer :all] + [clojure.set :as set]) + (:use clojure.test)) + +(def smoking-ctx (make-context [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40] + ["smoking" "male" "female" "education-level-high" "education-level-low" "cancer"] + #{[0 "smoking"] [0 "male"] [0 "education-level-high"] [0 "cancer"] + [1 "smoking"] [1 "male"] [1 "education-level-high"] [1 "cancer"] + [2 "smoking"] [2 "male"] [2 "education-level-high"] [2 "cancer"] + [3 "smoking"] [3 "male"] [3 "education-level-high"] [3 "cancer"] + [4 "smoking"] [4 "male"] [4 "education-level-high"] [4 "cancer"] + [5 "smoking"] [5 "male"] [5 "education-level-high"] [5 "cancer"] + [6 "smoking"] [6 "male"] [6 "education-level-high"] + [7 "smoking"] [7 "male"] [7 "education-level-high"] + [8 "smoking"] [8 "male"] [8 "education-level-low"] [8 "cancer"] + [9 "smoking"] [9 "male"] [9 "education-level-low"] [9 "cancer"] + [10 "smoking"] [10 "male"] [10 "education-level-low"] [10 "cancer"] + [11 "smoking"] [11 "male"] [11 "education-level-low"] [11 "cancer"] + [12 "smoking"] [12 "male"] [12 "education-level-low"] + [13 "smoking"] [13 "female"] [13 "education-level-high"] [13 "cancer"] + [14 "smoking"] [14 "female"] [14 "education-level-high"] [14 "cancer"] + [15 "smoking"] [15 "female"] [15 "education-level-high"] [15 "cancer"] + [16 "smoking"] [16 "female"] [16 "education-level-high"] [16 "cancer"] + [17 "smoking"] [17 "female"] [17 "education-level-high"] [17 "cancer"] + [18 "smoking"] [18 "female"] [18 "education-level-high"] + [19 "smoking"] [19 "female"] [19 "education-level-high"] + [20 "smoking"] [20 "female"] [20 "education-level-low"] [20 "cancer"] + [21 "smoking"] [21 "female"] [21 "education-level-low"] [21 "cancer"] + [22 "smoking"] [22 "female"] [22 "education-level-low"] [22 "cancer"] + [23 "smoking"] [23 "female"] [23 "education-level-low"] [23 "cancer"] + [24 "smoking"] [24 "female"] [24 "education-level-low"] + [25 "male"] [25 "education-level-high"] [25 "cancer"] + [26 "male"] [26 "education-level-high"] [26 "cancer"] + [27 "male"] [27 "education-level-high"] + [28 "male"] [28 "education-level-high"] + [29 "male"] [29 "education-level-high"] + [30 "male"] [30 "education-level-low"] [30 "cancer"] + [31 "male"] [31 "education-level-low"] + [32 "male"] [32 "education-level-low"] + [33 "male"] [33 "education-level-low"] + [34 "female"] [34 "education-level-high"] [34 "cancer"] + [35 "female"] [35 "education-level-high"] + [36 "female"] [36 "education-level-high"] + [37 "female"] [37 "education-level-low"] [37 "cancer"] + [38 "female"] [38 "education-level-low"] + [39 "female"] [39 "education-level-low"] + [40 "female"] [40 "education-level-low"]}) +) + +(def smoking-rule (make-implication #{"smoking"} #{"cancer"})) +(def smoking-fair-data-set (seq [#{7 29} + #{13 34} + #{15 36} + #{6 26} + #{1 28} + #{0 27} + #{17 35} + #{33 9} + #{31 12} + #{30 10} + #{22 37} + #{4 25} + #{21 38} + #{32 11} + #{24 40} + #{20 39}])) +(def smoking-fair-odds-ratio 8) + +(def birds-ctx (read-context "testing-data/Bird-Diet.ctx")) +(def birds-rule (make-implication #{"haferflocken"} #{"insekten"})) +(def birds-fair-data-set (seq [#{"baumläufer" "wintergoldhähnchen"}])) +(def birds-fair-odds-ratio 0) + + +(def diagnosis-ctx (read-context "testing-data/Diagnosis.ctx")) + + +(deftest test-record-pair + (is (matched-record-pair? smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"} + 0 + 25)) + (is (matched-record-pair? smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"} + 9 + 31)) + (is (matched-record-pair? smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"} + 13 + 34)) + (is (not (matched-record-pair? smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"} + 0 + 1))) + (is (not (matched-record-pair? smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"} + 0 + 30))) + (is (= (fair-data-set smoking-ctx + smoking-rule + #{"male" "female" "education-level-high" "education-level-low"}) + smoking-fair-data-set)) + + (is (= (fair-data-set birds-ctx + birds-rule + #{"beeren" "hirse" "meisenring" "sonnenblume" "talg" "äpfel"}) + birds-fair-data-set)) +) + +(deftest test-fair-odds-ratio + + (is (= (fair-odds-ratio smoking-ctx smoking-rule smoking-fair-data-set) + smoking-fair-odds-ratio)) + (is (= (fair-odds-ratio birds-ctx smoking-rule birds-fair-data-set) + birds-fair-odds-ratio)) + ) + +(deftest test-relevant + + (is (causally-relevant? smoking-ctx #{"smoking"} #{"cancer"} 1.9)) + (is (causally-relevant? smoking-ctx #{"male"} #{"cancer"} 1.9)) + (is (not (causally-relevant? birds-ctx #{"hirse"} #{"äpfel"} 1.9))) + (is (not (causally-relevant? birds-ctx #{"insekten"} #{"hirse"} 1.9))) + +) + +(deftest test-exclusive + + (is (= (exclusive-variables smoking-ctx #{"male" "education-level-low"} 1) + #{#{"male" "female"} #{"education-level-high" "education-level-low"}})) +) + +(deftest test-causal + + (is (causal? smoking-ctx smoking-rule #{} 1.7 1)) + (is (not (causal? smoking-ctx (make-implication #{"male"} #{"smoking"}) #{} 1.999 1))) + (is (causal? diagnosis-ctx (make-implication #{"[Bladder inflammation? yes]"} #{"[Urine pushing yes]"}) #{} 1.7 1)) + (is (not (causal? diagnosis-ctx (make-implication #{"[Burning yes]"} #{"[Urine pushing yes]"}) #{} 1.7 1))) + + (is (= (causal-association-rule-discovery smoking-ctx 0.7 3 "cancer" 1.7) + (seq [#{"smoking"}]))) + (is (= (causal-association-rule-discovery diagnosis-ctx 0.7 3 "[Urine pushing yes]" 1.7) + (seq [#{"[Bladder inflammation? yes]"}]))) + +) + diff --git a/src/test/clojure/conexp/fca/metric_context_test.clj b/src/test/clojure/conexp/fca/metric_context_test.clj new file mode 100644 index 000000000..f6e1e36df --- /dev/null +++ b/src/test/clojure/conexp/fca/metric_context_test.clj @@ -0,0 +1,199 @@ +(ns conexp.fca.metric-context-test + (:use conexp.base + conexp.fca.contexts + conexp.fca.lattices + conexp.fca.metric-context) + (:use clojure.test)) + +(defn object-metric-1 [x y] identity) +(defn object-metric-2 [x y] identity) +(defn object-metric-3 [x y] identity) + +(defn attribute-metric-1 [x y] identity) +(defn attribute-metric-2 [x y] identity) +(defn attribute-metric-3 [x y] identity) + +(def test-objs #{1 2 3 4 5 6}) +(def test-attrs #{"A" "B" "C" "D" "E"}) +(def test-inc #{[1 "A"] [1 "B"] [1 "D"] [1 "E"] + [2 "A"] [2 "B"] + [3 "B"] [3 "C"] [3 "D"] [3 "E"] + [4 "A"] [4 "C"] [4 "D"] + [5 "C"] [5 "D"] [5 "E"] + [6 "A"] [6 "B"] [6 "C"] [6 "D"] [6 "E"]}) + +(def testctx (make-context test-objs + test-attrs + test-inc)) + +(def testmctx (make-metric-context test-objs test-attrs test-inc)) + + + +(def cities-ctx (make-context #{"Washington, D.C." "Berlin" "Beijing" "Cairo" "Canberra" "Brasilia"} + #{"Population > 1M" "Population > 3M" "Population > 10M" + "Area > 100km^2" "Area > 1000km^2" "Area > 10000km^2"} + + #{["Washington, D.C." "Area > 100km^2"] + + ["Berlin" "Population > 1M"] ["Berlin" "Population > 3M"] ["Berlin" "Area > 100km^2"] + + ["Beijing" "Population > 1M"] ["Beijing" "Population > 3M"] ["Beijing" "Population > 10M"] + ["Beijing" "Area > 100km^2"] ["Beijing" "Area > 1000km^2"] ["Beijing" "Area > 10000km^2"] + + ["Cairo" "Population > 1M"] ["Cairo" "Population > 3M"] ["Cairo" "Population > 10M"] + ["Cairo" "Area > 100km^2"] ["Cairo" "Area > 1000km^2"] + + ["Canberra" "Area > 100km^2"] + + ["Brasilia" "Population > 1M"] ["Brasilia" "Area > 100km^2"] ["Brasilia" "Area > 1000km^2"]})) + +(def cities-mctx (convert-to-metric-context cities-ctx)) + +(def distance-map {"Washington, D.C." {"Washington, D.C." 0 + "Berlin" 7611 + "Beijing" 11145 + "Cairo" 9348 + "Canberra" 15945 + "Brasilia" 6791} + "Berlin" {"Washington, D.C." 7611 + "Berlin" 0 + "Beijing" 3754 + "Cairo" 2892 + "Canberra" 16066 + "Brasilia" 9593} + "Beijing" {"Washington, D.C." 11145 + "Berlin" 3754 + "Beijing" 0 + "Cairo" 7542 + "Canberra" 9011 + "Brasilia" 16929} + "Cairo" {"Washington, D.C." 9348 + "Berlin" 2892 + "Beijing" 7542 + "Cairo" 0 + "Canberra" 14266 + "Brasilia" 9877} + "Canberra" {"Washington, D.C." 15945 + "Berlin" 16066 + "Beijing" 9011 + "Cairo" 14266 + "Canberra" 0 + "Brasilia" 14059} + "Brasilia" {"Washington, D.C." 6791 + "Berlin" 9593 + "Beijing" 16929 + "Cairo" 9877 + "Canberra" 14059 + "Brasilia" 0}}) + +(defn distance-metric [a b]((distance-map a) b)) + + + +(deftest test-create-metric-context + + (let [mctx (make-metric-context test-objs test-attrs test-inc) + mctx-with-metrics (make-metric-context test-objs + test-attrs + test-inc + {:o-metric-1 object-metric-1 :o-metric-2 object-metric-2} + {:a-metric-1 attribute-metric-1})] + (is (= (context mctx) testctx)) + (is (= (context mctx-with-metrics) testctx)) + + (is (contains? (object-metrics mctx-with-metrics) :o-metric-1)) + (is (contains? (object-metrics mctx-with-metrics) :o-metric-2)) + (is (contains? (attribute-metrics mctx-with-metrics) :a-metric-1))) +) + +(deftest test-convert-to-metric-context + + (let [mctx (convert-to-metric-context testctx) + mctx-with-metrics (convert-to-metric-context testctx + {:o-metric-1 object-metric-1 :o-metric-2 object-metric-2} + {:a-metric-1 attribute-metric-1})] + (is (= (context mctx) testctx)) + (is (= (context mctx-with-metrics) testctx)) + + (is (contains? (object-metrics mctx-with-metrics) :o-metric-1)) + (is (contains? (object-metrics mctx-with-metrics) :o-metric-2)) + (is (contains? (attribute-metrics mctx-with-metrics) :a-metric-1))) +) + +(deftest test-add-metrics + + (let [mctx (convert-to-metric-context testctx + {:o-metric-1 object-metric-1 :o-metric-2 object-metric-2} + {:a-metric-1 attribute-metric-1}) + mctx2 (add-object-metrics mctx {:o-metric-2 object-metric-2 :o-metric-3 object-metric-3}) + mctx3 (add-attribute-metrics mctx2 {:a-metric-2 attribute-metric-2 :a-metric-3 attribute-metric-3})] + + (is (contains? (object-metrics mctx3) :o-metric-1)) + (is (contains? (object-metrics mctx3) :o-metric-2)) + (is (contains? (object-metrics mctx3) :o-metric-3)) + (is (= (count (object-metrics mctx3)) 3)) + + (is (contains? (attribute-metrics mctx3) :a-metric-1)) + (is (contains? (attribute-metrics mctx3) :a-metric-2)) + (is (contains? (attribute-metrics mctx3) :a-metric-3)) + (is (= (count (attribute-metrics mctx3)) 3))) +) + +(deftest test-remove-metrics + + (let [mctx (convert-to-metric-context testctx + {:o-metric-1 object-metric-1 :o-metric-2 object-metric-2} + {:a-metric-1 attribute-metric-1}) + mctx2 (remove-object-metric mctx :o-metric-1) + mctx3 (remove-object-metric mctx2 :o-metric-3) + mctx4 (remove-attribute-metric mctx3 :a-metric-1)] + + (is (not (contains? (object-metrics mctx4) :o-metric-1))) + (is (contains? (object-metrics mctx4) :o-metric-2)) + (is (= (count (object-metrics mctx4)) 1)) + + (is (not (contains? (attribute-metrics mctx4) :a-metric-1))) + (is (= (count (attribute-metrics mctx4)) 0))) +) + +(deftest test-object-dist-hamming + + (is (= (object-distance testmctx (object-hamming testmctx) 1 2) 2)) + (is (= (object-distance testmctx (object-hamming testmctx) 2 6) 3)) + + (is (= (max-object-distance testmctx (object-hamming testmctx)) 5)) + (is (= (max-object-distance testmctx (object-hamming testmctx) #{1 3 4 5 6}) 3)) + + (is (= (min-object-distance testmctx (object-hamming testmctx)) 1)) + (is (= (max-object-distance testmctx (object-hamming testmctx) #{1 3}) 2)) + + (is (= (average-object-distance testmctx (object-hamming testmctx))37/15 )) + (is (= (average-object-distance testmctx (object-hamming testmctx) #{1 2 3}) 8/3)) + +) + +(deftest test-attribute-dist-hamming + + (is (= (attribute-distance testmctx (attribute-hamming testmctx) "A" "C") 4)) + (is (= (attribute-distance testmctx (attribute-hamming testmctx) "B" "D") 3)) + + (is (= (max-attribute-distance testmctx (attribute-hamming testmctx)) 4)) + (is (= (max-attribute-distance testmctx (attribute-hamming testmctx) #{"A" "B" "D"}) 3)) + + (is (= (min-attribute-distance testmctx (attribute-hamming testmctx)) 1)) + (is (= (max-attribute-distance testmctx (attribute-hamming testmctx) #{"A" "B"}) 2)) + + (is (= (average-attribute-distance testmctx (attribute-hamming testmctx)) 13/5 )) + (is (= (average-attribute-distance testmctx (attribute-hamming testmctx) #{"A" "B" "C"}) 10/3)) + +) + +(deftest test-confusion-matrices + + (object-confusion-matrix testmctx (object-hamming testmctx)) + (attribute-confusion-matrix testmctx (attribute-hamming testmctx)) + + (object-confusion-matrix testmctx (object-hamming testmctx) :norm) + (attribute-confusion-matrix testmctx (attribute-hamming testmctx) :norm) +) diff --git a/testing-data/ben-and-jerrys-allergens.ctx b/testing-data/ben-and-jerrys-allergens.ctx new file mode 100644 index 000000000..87bb90c42 --- /dev/null +++ b/testing-data/ben-and-jerrys-allergens.ctx @@ -0,0 +1,26 @@ +B + +7 +7 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +barley +milk +peanuts +almond +wheat +egg +soy +.XX..XX +XX..XX. +.X.X.XX +.X..XXX +.X...XX +XX..XXX +.X..XXX diff --git a/testing-data/ben-and-jerrys-flavors-small.ctx b/testing-data/ben-and-jerrys-flavors-small.ctx new file mode 100644 index 000000000..42125ebc9 --- /dev/null +++ b/testing-data/ben-and-jerrys-flavors-small.ctx @@ -0,0 +1,25 @@ +B + +7 +6 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +Choco +Brownie +Dough +Peanut +Vanilla +Caramel +X..X.. +XX.... +X....X +XX..XX +X....X +XXX.X. +X.X.X. diff --git a/testing-data/ben-and-jerrys-flavors.ctx b/testing-data/ben-and-jerrys-flavors.ctx new file mode 100644 index 000000000..9a1466fd7 --- /dev/null +++ b/testing-data/ben-and-jerrys-flavors.ctx @@ -0,0 +1,28 @@ +B + +7 +9 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +Choco Ice +Peanut Ice +Choco Pieces +Brownie +Dough +Peanut Butter +Caramel Ice +Vanilla +Caramel +.XX..X... +X..X..... +X.X...X.X +..XX...XX +..X...X.X +X.XXX..X. +..X.X..X.