From ad35c378a1087c5fb413ac0bc62ef20f5ff6e551 Mon Sep 17 00:00:00 2001 From: Tom Hanika Date: Mon, 17 Jul 2023 22:21:27 +0200 Subject: [PATCH 01/22] Release 2.4 (#120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added concept separation * fixed :anonymous-burmeister write to file function * changed context io test to ignore :anonymous-burmeister format The fix for the :anonymous-burmeister write to file function caused some context io tests to fail. This commit removes the :anonymous-burmeister format from the out-in and out-in-out-in tests. The reasoning is as follows: - Before fixing the io write function, the tests went through because calling `count` on a context results in an `UnsupportedOperationException` which is caught and accepted (other formats throw this exception, namely :fcalgs, which only accepts integral objects and attributes). - Fixing the write part for the :anonymous-burmeister format meant that the tests were actually running - The out-in tests fail for the :anonymous-burmeister format basically by design because the object and attribute labels are discarded on write - The out-in-out-in tests usually fail too because the order of objects and attributes is not guaranteed to be kept intact on write. This is because `(seq (set (range N)))` is not guaranteed to be the same as `(range N)`. Hence the context after importing is the same context but possibly with shuffled objects and attributes, and thus the equality check fails. * doc: corrected example for :anonymous-burmeister format * Added possible-isomorphy tests for :anonymous-burmeister format Previously there were no working tests for the :anonymous-burmeister format. Because this format drops object and attribute names when writing a context to a file and there is no determinism in the order of objects and attributes written to file, we can not test for equality in out-in and out-in-out-in tests. Therefore, we resort to testing for possible isomorphy -- testing for true isomorphy is not trivial and expensive to compute. * Update IO.org * Added Named Binary CSV Input and Doc * Fixed NB write and detection, Added write options and FCA Output * LaTeX Output for lattices supports valuation functions * Added gui I/O for layouts * More Valuations * Fixed concept probability for concepts having |extent|=|G| * Fixed concept probability ... debugging prints ;) * Added documentation * Added separation-index to the gui * Fixed syntax error * Fixed code junk found in metrics. * add read & write of json format for implications * correct read and write format for implications * add oi and oioi tests for json implication format * add json context input format * add json context output format and improve implication output format * add json concept input & output format * Add json input & output for whole process (context + concepts + implications) * rename process.clj to fca.clj and change output from triple to map * add validation for fca-schema * fix io/context tests that were failing for :json format * json format: lattices can be saved with order now * improve tests for reading / writing of implications * change fca_schema, so that objects and attributes can be of type string and number * adapt read-fca and write-fca after change of lattice format * add tests for reading / writing of fcas * add validation for context io * add validation for lattice io * add validation for implication io * refactoring of fca io * move context schema validation to read-context and add a general json validation for format detection * move schema validation to the read functions for lattice, implications and fca * Add documentation * add documentation * add myself as author * add tests for identifying context input formats * add test for writing / reading fcalgs format * add tests for identifying lattice input formats * add documentation + comments * improve documentation * minor changes in documentation * improve tests for fcas and implications * Add nix flake * Add nix flake * Updating the scene layout did not update the layout data * updating the layout cased a removal of the old value-fn * update documentation for drawing concept lattices * Triadic (#70) * triadic exploration * Triadic-Exploration: Added basic example to doc * Triadic Exploration: small fix in doc * modify :json context schema so that empty attribute columns are not dropped any more (#92) * csv reader library (#90) * use csv reader library for named-binary-csv * use csv reader library for :csv input * use csv reader library for :binary-csv input * use csv reader library for :csv output * use csv reader library for :binary-csv output * use csv reader library for :named-binary-csv output Co-authored-by: Johannes Hirth * Minor stuff in exploration * Fast fix for CORS problem using api with browser frontend * Export layout json (#95) * add json export for layouts * save valuations in :json format * don't add nil valuations to layout * code cleanup and add annotations / labels when reading :json layout * save labels with position * annotations are saved, but not read * change layout api functions * use json io functions for layout api * Bump flake (#88) * flake.lock: Update Flake lock file changes: • Updated input 'flake-compat': 'github:edolstra/flake-compat/12c64ca55c1014cdc1b16ed5a804aa8576601ff2' (2021-08-02) → 'github:edolstra/flake-compat/b4a34015c698c7793d592d66adbab377907a2be8' (2022-04-19) • Updated input 'flake-utils': 'github:numtide/flake-utils/74f7e4319258e287b0f9cb95426c9853b282730b' (2021-11-28) → 'github:numtide/flake-utils/a4b154ebbdc88c8498a5c7b01589addc9e9cb678' (2022-04-11) • Updated input 'gitignoresrc': 'github:hercules-ci/gitignore.nix/5b9e0ff9d3b551234b4f3eb3983744fa354b17f1' (2021-10-25) → 'github:hercules-ci/gitignore.nix/bff2832ec341cf30acb3a4d3e2e7f1f7b590116a' (2022-03-05) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/86453059bf8312f0f5bf1fe8a2f52da2be664489' (2021-12-20) → 'github:NixOS/nixpkgs/3a9e0f239d80fa134e8fcbdee4dfc793902da37e' (2022-04-25) * Nix: Update hash for dependencies * Nix: fix shell.nix compatibility wrapper * Ignore .lsp * Nix: Drop conexp-clj from devShell PATH Remove `conexp-clj` from the devShell PATH, so that `nix develop` works even if the current checkout fails to build. A shell with `conexp-clj` in PATH is still available using `nix shell`. * Poset layout (#96) * rename "lattice" to "poset" in layouts * fix some errors * renaming and fixing errors * handle inf-irreducible and sup-irreducible layout functions for posets * compute context for poset * move Poset type to separate file and add change some layout tests * add more tests for posets and move neighbor functions from lattices to posets * add tests for posets * fix some errors and add tests for posets in layout functions * add to-inf-additive-layout for Posets * improve to-inf-additive-layout function for Posets * add tests for order-ideal and order-filter * add tests for poset and dim-draw layout * add tests for poset and freese layout * force layout does not work for posets, as inf-irreducibles are needed * fix failing tests * add GUI frame title for Poset * when constructing a poset from positions and connections in Layout, save it as Lattice if it has lattice order * add tests for concept-lattice-annotation with poset * add wrapper function for poset layouts * use wrapper function for to-inf-additive-layout with posets * add error handling for valuations in GUI * change order in annotation function * remove 2nd condition from annotation function for layouts, as it is never reached * minor changes in documentation * Prefer clj-nix over fixed-output derivations for nix flake (#98) * Bump json-schema to 0.3.4 json-schema-0.3.3 still refers to com.github.everit-org.json-schema/org.everit.json.schema, which is a redirect -- this breaks clj-nix. * Prefer clj-nix over fixed-output mvn build * Expose unit tests as a flake app * Add workflows for tests and dependency locking * Enable test workflow for clj-nix branch * Update deps-lock.json * Revert "Enable test workflow for clj-nix branch" This reverts commit 0f7f1aa95e0d365c530ea9a405115a93396162c2. Co-authored-by: mmarx * Fixed some of the deprecated code in latdraw. * Protoconcepts (#99) * add Protoconcept data type * add preconcepts? and protoconcepts? function * add protoconcept functions and tests * fix preconcepts? tests * add semiconcept? function * add protoconcepts order and visualization * add tests for protoconcept-layout * use preconcepts? as defined by R. Wille * add draw-protoconcepts function * add print method for protoconcepts * fix errors * enable draw for protoconcepts * add more protoconcepts functions and tests * remove functions that are not needed any more fore protoconcepts * add draw functions for posets and protoconcepts * improve documentation * add documentation for protoconcepts * minor changes in documentation * minor changes in documentation * minor changes in documentation * Protoconcepts (#100) * add Protoconcept data type * add preconcepts? and protoconcepts? function * add protoconcept functions and tests * fix preconcepts? tests * add semiconcept? function * add protoconcepts order and visualization * add tests for protoconcept-layout * use preconcepts? as defined by R. Wille * add draw-protoconcepts function * add print method for protoconcepts * fix errors * enable draw for protoconcepts * add more protoconcepts functions and tests * remove functions that are not needed any more fore protoconcepts * add draw functions for posets and protoconcepts * improve documentation * add documentation for protoconcepts * minor changes in documentation * minor changes in documentation * minor changes in documentation * add protoconcepts function in gui * Updated copyright notice and added reference request. * Update Common-FCA-File-Formats-for-Formal-Contexts.org (#102) * New context randomization (#103) * added edge swapping and edge rewiring for contexts * added tests for swapping and rewiring * fixed function aliases * Incomplete Contexts (#105) * generate random context that does not contain the maximal contranominal scale (for the generated size of context) * fixed bug in dimdraw greedy * Incomplete Contexts and exploration with multiple experts * added brief doc --------- Co-authored-by: Tom Hanika * Match api and json I/O formats (#106) * modify :json context schema so that empty attribute columns are not dropped any more * add layouts to fca json schema * add schema and example for mv-context * add IO of fca schema in api and update context schema * adapt io :json schema to api for lattices * adapt api for implications to :json schema * change api handler tests for lattices and layouts - still failing * remove all UnsupportedOperationExceptions from context io tests * update lattice schema so that it can process both basic and concept-lattices * code cleanup in io/lattices * add json schema for mv-contexts * fix errors in IO for mv-contexts and update tests * add tests for mv-contexts automatic format detection * fix failing test * Fix draw-lattice (#107) * add IO of fca schema in api and update context schema * make draw-lattice work without args * add tests for draw-lattice and draw-protoconcepts * rename file * fix failing tests * Version bump * Feature/fix flake (#108) * Fix `buildInputs` in flake * flake.lock: Update Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/9bdbbaa634aa666eb6a27096bdcb991c59181244' (2022-09-21) → 'github:NixOS/nixpkgs/0874168639713f547c05947c76124f78441ea46c' (2023-01-01) * Updated depdencies * Update deps-lock.json (#111) Co-authored-by: tomhanika * add lein-aot-order plugin (#112) * Update flake (#113) * add lein-aot-order plugin * Bump flake to nixpkgs-23.05 * Update conexp version in flake * Force `lein-aot-order` into deps.lock The `lein-aot-order` plugin won't be picked up unless it's part of the `main` profile, and is thus not part of the lock file, which breaks the nix package. --------- Co-authored-by: Jana * Fix build status badge in README (#114) * Explain the flake in the documentation (#115) * Add overlay to flake * Add documentation on the nix flake * fix error for drawing lattices with valuations (#117) * Scale Measures (#118) * Added many-valued context functions and quality of life improvements * Conexp now with pq-cores * moved files * renamed namespaces * moved subconcept function * fixed comments and removed two duplicate functions * Added Author Field * base functionality for smeasures * Added naive Object Clustering * smeasures now with clustering - Made async fast concepts compuatation usable for other methods - Moved transformation algorithms into their own ns - Implemented Cluster Scale Measure Algorithms that compute valid clusterings given an initial set * Bugfix and updated tests * Added documentation * added missing test * Fixed bug in object clustering * Added tests * Added interactive scale exploration * added draft for genetic algorithm * Terminal output * Terminal output smeasure * added genetic algorithm * genetic 2d subctx * removed debug * refectoring * repair cluster method * prepared helper methods for genetic clustering * Added Tikz support for measures * Added lattice tikz smeasure export * Now with genetic clustering * bugfix * genetic part 1 works now * genetic works Part 2 * Code Refactoring * smart cluster in explor. and decode for genetic * Fixed and improved Cluster Repair Algorithm * bigfix * Added Cluster Validity checkers * Scale-Measure init restructure * Scale-Measures Added logical attributes * Scale-Measures Moved Logical derivations to context.clj * Added Scale-Measures Framework * Scale-Measure Apposition * Recommend based on an importance measure * Added scale-measure exploration and recommendation by importance measures * Only meet-irreducible scale-measure attributes * More intuitive questionary + Minor Bug fix * smeasures: updated questionary * Scaling error * Resolved Merge * Added comment for cover method * meet and join operator * Bugfix * Added a relative scaling errors * Compute all concepts containing g and their covering * Removed prints * add test for smeasure? * test "make-smeasure" functions * test original-extents * test smeasure-by-exts * test for canonical-smeasure-representation * test meet-smeasure * fix error in canonical-smeasure-representation * test error-in-smeasure * fix and test invalid-attributes * fix & test conceptual-scaling-error + attribute-scaling-error * change 'make-smeasure' tests * test smeasure-valid-attr and smeasure-invalid-attr * test conjunctive normalform * add test for scale-apposition * test smeasure-valid-exts and smeasure-invalid-exts * test rename-scale for objects and attributes * test pre-image-measure * add test for join-complement * add test for recommend-by-importance * add tests for conceptual-navigation * add tests for conceptual-navigation * add tests for exploration-of-scales-iteration * add test for exploration-of-scales * add some minor test cases * add test for concept-lattice-filter+covering-concepts --------- Co-authored-by: Johannes Hirth Co-authored-by: Johannes Hirth Co-authored-by: De Narm Co-authored-by: hirthjo <58222089+hirthjo@users.noreply.github.com> Co-authored-by: Tom Hanika --------- Co-authored-by: Johannes Hirth Co-authored-by: Maximilian Felde Co-authored-by: hirthjo <58222089+hirthjo@users.noreply.github.com> Co-authored-by: Daniel Borchmann <517659+exot@users.noreply.github.com> Co-authored-by: Jana Co-authored-by: Maximilian Marx Co-authored-by: maximilian-felde <43608455+maximilian-felde@users.noreply.github.com> Co-authored-by: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Co-authored-by: mmarx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: tomhanika Co-authored-by: Johannes Hirth Co-authored-by: De Narm --- .envrc | 1 + .github/workflows/run-tests.yaml | 17 + .github/workflows/update-deps-lock.yaml | 22 + .gitignore | 2 + AUTHORS.md | 5 +- README.md | 34 +- deps-lock.json | 1976 +++ ...n-FCA-File-Formats-for-Formal-Contexts.org | 58 +- doc/Concept-Lattices.org | 19 +- doc/Getting-Started.org | 55 + doc/IO.org | 52 + doc/IncompleteContexts.org | 72 + doc/Protoconcepts.org | 91 + doc/Triadic-Exploration.org | 151 + doc/code/trace-context.clj | 2 +- .../concept-lattice-conditional-theories.png | Bin 0 -> 29629 bytes doc/images/draw-lattice-02.png | Bin 0 -> 53429 bytes doc/images/protoconcept-lattice-dimdraw.png | Bin 0 -> 88507 bytes doc/images/protoconcept-lattice.png | Bin 0 -> 92565 bytes flake.lock | 133 + flake.nix | 93 + project.clj | 47 +- src/main/clojure/conexp/api.clj | 19 +- src/main/clojure/conexp/api/handler.clj | 59 +- src/main/clojure/conexp/base.clj | 9 +- .../clojure/conexp/fca/concept_transform.clj | 103 + src/main/clojure/conexp/fca/contexts.clj | 57 +- src/main/clojure/conexp/fca/cover.clj | 25 +- src/main/clojure/conexp/fca/fast.clj | 73 +- src/main/clojure/conexp/fca/graph.clj | 23 +- .../incomplete_contexts/conexp_interop.clj | 83 + .../conexp/fca/incomplete_contexts/draw.clj | 50 + .../fca/incomplete_contexts/experts.clj | 524 + .../exploration_shared_implications.clj | 570 + .../incomplete_contexts.clj | 605 + .../incomplete_contexts_exploration.clj | 336 + .../incomplete_contexts/random_experts.clj | 117 + src/main/clojure/conexp/fca/lattices.clj | 38 +- src/main/clojure/conexp/fca/metrics.clj | 76 +- src/main/clojure/conexp/fca/posets.clj | 163 + src/main/clojure/conexp/fca/pqcores.clj | 67 - src/main/clojure/conexp/fca/protoconcepts.clj | 93 + .../clojure/conexp/fca/random_contexts.clj | 164 +- src/main/clojure/conexp/fca/smeasure.clj | 705 + .../conexp/fca/triadic_exploration.clj | 380 + src/main/clojure/conexp/gui/draw.clj | 62 +- .../conexp/gui/draw/control/dim_draw.clj | 6 +- .../conexp/gui/draw/control/file_exporter.clj | 28 +- .../conexp/gui/draw/control/freese.clj | 2 +- .../conexp/gui/draw/control/parameters.clj | 50 +- .../clojure/conexp/gui/draw/scene_layouts.clj | 59 +- .../clojure/conexp/gui/editors/contexts.clj | 16 +- .../clojure/conexp/gui/editors/lattices.clj | 4 +- src/main/clojure/conexp/io/contexts.clj | 232 +- src/main/clojure/conexp/io/fcas.clj | 71 + src/main/clojure/conexp/io/implications.clj | 68 + .../clojure/conexp/io/incomplete_contexts.clj | 203 + src/main/clojure/conexp/io/json.clj | 42 + src/main/clojure/conexp/io/latex.clj | 49 +- src/main/clojure/conexp/io/lattices.clj | 61 +- src/main/clojure/conexp/io/layouts.clj | 118 +- .../conexp/io/many_valued_contexts.clj | 48 +- src/main/clojure/conexp/io/smeasure.clj | 323 + src/main/clojure/conexp/io/util.clj | 7 +- src/main/clojure/conexp/layouts/base.clj | 135 +- src/main/clojure/conexp/layouts/common.clj | 46 +- src/main/clojure/conexp/layouts/dim_draw.clj | 22 +- src/main/clojure/conexp/layouts/force.clj | 31 +- src/main/clojure/conexp/layouts/freese.clj | 20 +- src/main/clojure/conexp/layouts/layered.clj | 27 +- src/main/clojure/conexp/layouts/util.clj | 89 +- src/main/clojure/conexp/main.clj | 3 + src/main/clojure/conexp/math/algebra.clj | 80 +- .../orderedset/ChainDecomposition.java | 4 +- .../org/latdraw/orderedset/OrderedSet.java | 2 +- .../org/latdraw/partition/BasicPartition.java | 10 +- .../org/latdraw/partition/ReadPartitions.java | 2 +- .../java/org/latdraw/util/SimpleList.java | 8 +- .../schemas/context_schema_v1.0.json | 54 + .../schemas/context_schema_v1.1.json | 48 + .../resources/schemas/fca_schema_v1.0.json | 65 + .../schemas/implications_schema_v1.0.json | 105 + .../schemas/lattice_schema_v1.0.json | 71 + .../schemas/lattice_schema_v1.1.json | 71 + .../resources/schemas/layout_schema_v1.0.json | 74 + .../schemas/mv-context_schema_v1.0.json | 45 + src/test/clojure/conexp/api/handler_test.clj | 150 +- .../conexp/fca/concept_transform_test.clj | 25 + src/test/clojure/conexp/fca/contexts_test.clj | 2 +- src/test/clojure/conexp/fca/cover_test.clj | 9 +- src/test/clojure/conexp/fca/lattices_test.clj | 13 +- src/test/clojure/conexp/fca/posets_test.clj | 142 + src/test/clojure/conexp/fca/pqcores_test.clj | 13 - .../clojure/conexp/fca/protoconcepts_test.clj | 158 + .../conexp/fca/random_contexts_test.clj | 26 + src/test/clojure/conexp/fca/smeasure_test.clj | 516 + src/test/clojure/conexp/gui/draw_test.clj | 42 + src/test/clojure/conexp/io/contexts_test.clj | 163 +- src/test/clojure/conexp/io/fcas_test.clj | 191 + .../clojure/conexp/io/implications_test.clj | 78 + src/test/clojure/conexp/io/lattices_test.clj | 24 + src/test/clojure/conexp/io/layouts_test.clj | 29 +- .../conexp/io/many_valued_contexts_test.clj | 43 +- src/test/clojure/conexp/io/smeasure_test.clj | 50 + src/test/clojure/conexp/io/util_test.clj | 18 +- src/test/clojure/conexp/layouts/base_test.clj | 172 +- .../clojure/conexp/layouts/common_test.clj | 105 +- .../clojure/conexp/layouts/dim_draw_test.clj | 25 +- .../clojure/conexp/layouts/force_test.clj | 25 +- .../clojure/conexp/layouts/freese_test.clj | 12 +- .../clojure/conexp/layouts/layered_test.clj | 45 +- src/test/clojure/conexp/layouts/util_test.clj | 62 +- src/test/clojure/conexp/math/algebra_test.clj | 52 +- .../clojure/conexp/math/sampling_test.clj | 1 + testing-data/digits-context.json | 12 + testing-data/digits-context1.1.json | 13 + testing-data/digits-fca-2.json | 591 + testing-data/digits-fca.json | 14318 ++++++++++++++++ testing-data/digits-implication.json | 1 + testing-data/digits-lattice.json | 1 + testing-data/digits-lattice1.1.json | 1 + testing-data/latex/lattice-smeasure1-2.tex | 80 + testing-data/latex/lattice-smeasure1-3.tex | 87 + testing-data/latex/lattice-smeasure3-1.tex | 96 + testing-data/latex/tikz-example.tex | 33 +- testing-data/latex/tikz-smeasure1-2.tex | 48 + testing-data/latex/tikz-smeasure1-3.tex | 60 + testing-data/latex/tikz-smeasure3-1.tex | 65 + testing-data/mv-context.json | 1 + testing-data/small-fca.json | 1 + 130 files changed, 26383 insertions(+), 846 deletions(-) create mode 100644 .envrc create mode 100644 .github/workflows/run-tests.yaml create mode 100644 .github/workflows/update-deps-lock.yaml create mode 100644 deps-lock.json create mode 100644 doc/IncompleteContexts.org create mode 100644 doc/Protoconcepts.org create mode 100644 doc/Triadic-Exploration.org create mode 100644 doc/images/concept-lattice-conditional-theories.png create mode 100644 doc/images/draw-lattice-02.png create mode 100644 doc/images/protoconcept-lattice-dimdraw.png create mode 100644 doc/images/protoconcept-lattice.png create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/main/clojure/conexp/fca/concept_transform.clj create mode 100644 src/main/clojure/conexp/fca/incomplete_contexts/conexp_interop.clj create mode 100644 src/main/clojure/conexp/fca/incomplete_contexts/draw.clj create mode 100644 src/main/clojure/conexp/fca/incomplete_contexts/experts.clj create mode 100644 src/main/clojure/conexp/fca/incomplete_contexts/exploration_shared_implications.clj create mode 100644 src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts.clj create mode 100644 src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts_exploration.clj create mode 100644 src/main/clojure/conexp/fca/incomplete_contexts/random_experts.clj create mode 100644 src/main/clojure/conexp/fca/posets.clj create mode 100644 src/main/clojure/conexp/fca/protoconcepts.clj create mode 100644 src/main/clojure/conexp/fca/smeasure.clj create mode 100644 src/main/clojure/conexp/fca/triadic_exploration.clj create mode 100644 src/main/clojure/conexp/io/fcas.clj create mode 100644 src/main/clojure/conexp/io/implications.clj create mode 100644 src/main/clojure/conexp/io/incomplete_contexts.clj create mode 100644 src/main/clojure/conexp/io/json.clj create mode 100644 src/main/clojure/conexp/io/smeasure.clj create mode 100644 src/main/resources/schemas/context_schema_v1.0.json create mode 100644 src/main/resources/schemas/context_schema_v1.1.json create mode 100644 src/main/resources/schemas/fca_schema_v1.0.json create mode 100644 src/main/resources/schemas/implications_schema_v1.0.json create mode 100644 src/main/resources/schemas/lattice_schema_v1.0.json create mode 100644 src/main/resources/schemas/lattice_schema_v1.1.json create mode 100644 src/main/resources/schemas/layout_schema_v1.0.json create mode 100644 src/main/resources/schemas/mv-context_schema_v1.0.json create mode 100644 src/test/clojure/conexp/fca/concept_transform_test.clj create mode 100644 src/test/clojure/conexp/fca/posets_test.clj create mode 100644 src/test/clojure/conexp/fca/protoconcepts_test.clj create mode 100644 src/test/clojure/conexp/fca/smeasure_test.clj create mode 100644 src/test/clojure/conexp/gui/draw_test.clj create mode 100644 src/test/clojure/conexp/io/fcas_test.clj create mode 100644 src/test/clojure/conexp/io/implications_test.clj create mode 100644 src/test/clojure/conexp/io/smeasure_test.clj create mode 100644 testing-data/digits-context.json create mode 100644 testing-data/digits-context1.1.json create mode 100644 testing-data/digits-fca-2.json create mode 100644 testing-data/digits-fca.json create mode 100644 testing-data/digits-implication.json create mode 100644 testing-data/digits-lattice.json create mode 100644 testing-data/digits-lattice1.1.json create mode 100644 testing-data/latex/lattice-smeasure1-2.tex create mode 100644 testing-data/latex/lattice-smeasure1-3.tex create mode 100644 testing-data/latex/lattice-smeasure3-1.tex create mode 100644 testing-data/latex/tikz-smeasure1-2.tex create mode 100644 testing-data/latex/tikz-smeasure1-3.tex create mode 100644 testing-data/latex/tikz-smeasure3-1.tex create mode 100644 testing-data/mv-context.json create mode 100644 testing-data/small-fca.json diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..3550a30f2 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml new file mode 100644 index 000000000..0ab0b6c91 --- /dev/null +++ b/.github/workflows/run-tests.yaml @@ -0,0 +1,17 @@ +name: "conexp-clj Tests" +on: + pull_request: + branches: + - dev + push: + branches: + - dev + +jobs: + unit-tests: + name: "conexp-clj Unit Tests" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v17 + - run: nix run .#test diff --git a/.github/workflows/update-deps-lock.yaml b/.github/workflows/update-deps-lock.yaml new file mode 100644 index 000000000..abe9eb791 --- /dev/null +++ b/.github/workflows/update-deps-lock.yaml @@ -0,0 +1,22 @@ +name: "Update deps-lock.json" +on: + push: + paths: + - "**/project.clj" + +jobs: + update-lock: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v17 + - name: Update deps-lock + run: "nix run .#deps-lock" + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v4.0.3 + with: + commit-message: Update deps-lock.json + title: Update deps-lock.json + branch: update-deps-lock diff --git a/.gitignore b/.gitignore index ffb97da5c..d38ac958b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ /doc/icfca-2013-tutorial/icfca2013-tutorial-talk.toc /doc/icfca-2013-tutorial/icfca2013-tutorial-talk.vrb /doc/tutorials/icfca-2013/icfca2013-tutorial-live.html +/.lsp/ +/.direnv/ \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index 986815f56..332175d75 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -11,12 +11,11 @@ Additional Contributors are * Immanuel Albrecht (Context Editor Plugin for the GUI) * Sebastian Benner (API) * Stefan Borgwardt (Shared Intents) +* Jana Fischer (json format) * Tom Hanika (Concept Probability) -* Johannes Hirth (pq-cores) +* Johannes Hirth (pq-cores, scale-measures) * Gleb Kanterov (interval-scale) * Maximilian Marx (Wikidata) * Maximilian Stubbemann (concept-robustness) * Anselm von Wangenheim (DimDraw) * Johannes Wollbold (bug reports, feature requests) - - diff --git a/README.md b/README.md index 6f9442c0c..f5925b81a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# conexp-clj [![Build Status](https://travis-ci.org/tomhanika/conexp-clj.svg?branch=dev)](https://travis-ci.org/tomhanika/conexp-clj) +# conexp-clj [![Build Status](https://img.shields.io/github/actions/workflow/status/tomhanika/conexp-clj/run-tests.yaml?branch=dev&label=build)](https://github.com/tomhanika/conexp-clj/actions/workflows/run-tests.yaml) [![built with nix](https://img.shields.io/static/v1?logo=nixos&logoColor=white&label=&message=Built%20with%20Nix&color=41439a)](https://builtwithnix.org) This is conexp-clj, a general purpose software tool for [Formal Concept Analysis](http://www.upriss.org.uk/fca/fca.html). Its main purpose is to @@ -35,6 +35,9 @@ much more. 5. Advanced Topics 1. [pq-cores](doc/pq-cores-in-Formal-Contexts.md) 2. [REST-API Usage](doc/REST-API-usage.md) + 3. [triadic-exploration](doc/Triadic-Exploration.org) + 4. [protoconcepts](doc/Protoconcepts.org) + 5. [Incomplete Contexts](doc/IncompleteContexts.org) 6. [API documentation](doc/API.md) 7. [Development](doc/Development.org) @@ -60,10 +63,37 @@ software](http://www.upriss.org.uk/fca/fcasoftware.html). See [AUTHORS.md](AUTHORS.md). +## How to cite `conexp-clj`? +If you have used `conexp-clj` for your scientific work, the developers +would appreciate if you use the following reference. + +``` +@inproceedings{DBLP:conf/icfca/HanikaH19, + author = {Tom Hanika and + Johannes Hirth}, + editor = {Diana Cristea and + Florence Le Ber and + Rokia Missaoui and + L{\'{e}}onard Kwuida and + Baris Sertkaya}, + title = {Conexp-Clj - {A} Research Tool for {FCA}}, + booktitle = {Supplementary Proceedings of {ICFCA} 2019 Conference and Workshops, + Frankfurt, Germany, June 25-28, 2019}, + series = {{CEUR} Workshop Proceedings}, + volume = {2378}, + pages = {70--75}, + publisher = {CEUR-WS.org}, + year = {2019}, + url = {http://ceur-ws.org/Vol-2378/shortAT8.pdf}, + timestamp = {Wed, 12 Feb 2020 16:44:55 +0100}, + biburl = {https://dblp.org/rec/conf/icfca/HanikaH19.bib}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} +``` ## License -Copyright ⓒ 2009—2018 Daniel Borchmann, 2018–2020 Tom Hanika +Copyright ⓒ 2009—2018 Daniel Borchmann, 2018–2023 Tom Hanika Distributed under the Eclipse Public License. diff --git a/deps-lock.json b/deps-lock.json new file mode 100644 index 000000000..32c5b33c0 --- /dev/null +++ b/deps-lock.json @@ -0,0 +1,1976 @@ +{ + "lock-version": 3, + "git-deps": [], + "mvn-deps": [ + { + "mvn-path": "aysylu/loom/1.0.2/loom-1.0.2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-SIXsyrt3R7ZhkS99WFr25ANHL/a7dJ0/kfTUTREMkdY=" + }, + { + "mvn-path": "aysylu/loom/1.0.2/loom-1.0.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-lIYWiawdZ+UH7ryGfP+CLmSB5jojpcgU4xfhHCO2IZY=" + }, + { + "mvn-path": "better-cond/better-cond/2.1.0/better-cond-2.1.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-xu/E7NU0+oVCiFIXtX/hsyX7ei6vozTKwjUBfMtuQdE=" + }, + { + "mvn-path": "better-cond/better-cond/2.1.0/better-cond-2.1.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-s7nq7nG2p2PmnQJCpwAHR5w6wVz10Pl4Y3TSH2DB3wQ=" + }, + { + "mvn-path": "cheshire/cheshire/5.10.0/cheshire-5.10.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-c6zJFpWQejJApipvINBXBlDGDlNf9vehb6Na5lvcT9g=" + }, + { + "mvn-path": "cheshire/cheshire/5.10.0/cheshire-5.10.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-l3PHeIYLYeUXTVeUfJQIGwaJY6aaL7ABmhGpGuN9l8w=" + }, + { + "mvn-path": "cheshire/cheshire/5.11.0/cheshire-5.11.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-63Enn5Ak0AUpOBdVZKScWG8P/QAnWaVNPmIPS891Leo=" + }, + { + "mvn-path": "cheshire/cheshire/5.8.1/cheshire-5.8.1.pom", + "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/", + "hash": "sha256-OJ0pdhKo6KzGbF0NZKbJO99lDe1iXG9q4Wk7kzaKkaU=" + }, + { + "mvn-path": "clj-http/clj-http/3.12.3/clj-http-3.12.3.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Jx0VRYS9iV/S4w9jEVAE8ce4xySq6tLLKpl8XFWEBfA=" + }, + { + "mvn-path": "clj-http/clj-http/3.9.1/clj-http-3.9.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-auQ1vOjy7MN5WZIjmb4xpuJ3k8xTZEUQBkIaUuyOKFE=" + }, + { + "mvn-path": "clj-stacktrace/clj-stacktrace/0.2.8/clj-stacktrace-0.2.8.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Kr0ARCxfN1I92ZgWggw0RUVsxahotJuiNLt7LFDjSYA=" + }, + { + "mvn-path": "clj-stacktrace/clj-stacktrace/0.2.8/clj-stacktrace-0.2.8.pom", + "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/", + "hash": "sha256-mvUCJmgV0+Yzk/30ZKhSkSP2OgGqU8lK7kcAjNbtOq0=" + }, + { + "mvn-path": "clj-tuple/clj-tuple/0.2.2/clj-tuple-0.2.2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-WNv3Pai8403EmN5sNH+HLvmPMBo6KdyVeX1k+A5ra0c=" + }, + { + "mvn-path": "clj-tuple/clj-tuple/0.2.2/clj-tuple-0.2.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ahZELYfCkwwDImtKo9KYkVfVi5TtPnyNZ2aLDrZYOJs=" + }, + { + "mvn-path": "clojure-complete/clojure-complete/0.2.5/clojure-complete-0.2.5.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-QxrPI234bOCWgzIeiqgFQXNUusqHS4H6572H47Ntaqs=" + }, + { + "mvn-path": "clojure-complete/clojure-complete/0.2.5/clojure-complete-0.2.5.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ZIsMIEuk8EDcQ8KfguRHHstmdWCK+wuzGp7BxCYTenQ=" + }, + { + "mvn-path": "clout/clout/2.2.1/clout-2.2.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-nzdGxROb7zUJmjOuuDMQCnWOJXKv1CM+ynAM3XLWhws=" + }, + { + "mvn-path": "clout/clout/2.2.1/clout-2.2.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-BNzQl/YWeodReE3AkISRm68YmBfPQcvt4sQs7gSMh6c=" + }, + { + "mvn-path": "com/damnhandy/handy-uri-templates/2.1.8/handy-uri-templates-2.1.8.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-a4OEby/2HQqqZpl7ZLiD7HtlzxO1Ck1/WCUJltQpvi4=" + }, + { + "mvn-path": "com/damnhandy/handy-uri-templates/2.1.8/handy-uri-templates-2.1.8.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-oDZXy9TnBD1AcQjaP4cFmkyBr/dBueFWp01Sjci3Ma4=" + }, + { + "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.10.2/jackson-core-2.10.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-TEHyKkj267KHUrrrbSW/CbpP8K2L+4JlDd5EiSi52k8=" + }, + { + "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.10.2/jackson-core-2.10.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Mlo8lNkkrw9s6sykDp6IehiIZMFT+fw0zpxPAT2ogo4=" + }, + { + "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.13.3/jackson-core-2.13.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-PTyORDm/LbQ3YKYSB9/JV62ZpZt1LMhHaZC0neAM3vw=" + }, + { + "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.9.6/jackson-core-2.9.6.pom", + "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/", + "hash": "sha256-fB5excnYR4BBvQV4Ncjyhyz4OOe8w84bWUoX7pWdxTQ=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.10.2/jackson-dataformat-cbor-2.10.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Do9DC1WFpvbgxEIJCu9aWFfwz0HY6NptCYcI9s/SEZs=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.13.3/jackson-dataformat-cbor-2.13.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-KEaSxtltGPDKwjXCipsWe9y5IKEOt2suFX3l4WTq3Aw=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.9.6/jackson-dataformat-cbor-2.9.6.pom", + "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/", + "hash": "sha256-xKawR9uF1XiUo+tFpiv4MvOThtDvZbH4kvZqvQ1ifro=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.10.2/jackson-dataformat-smile-2.10.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-UQrdLrPqpS5nWh86riCWsSziu7AuJrjyifFTgjNeBU8=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.13.3/jackson-dataformat-smile-2.13.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-YkR6KghMq4L5Yb4fPulnJu+Wj+Q9qbb7rhPKJXEMzfw=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.9.6/jackson-dataformat-smile-2.9.6.pom", + "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/", + "hash": "sha256-9wOYydK1e1piCIbVmEC0x8KgzQ8+aw4N5U/CtLxDZkw=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformats-binary/2.13.3/jackson-dataformats-binary-2.13.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-ukW6RRSBY0/H0kJoGuof5ANzhFPc5LYBJ+tkMSo1KTY=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformats-binary/2.9.6/jackson-dataformats-binary-2.9.6.pom", + "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/", + "hash": "sha256-fj71bOHtu3I/f8NxL2yGifS8BvVkElx1ptZY0CO97FY=" + }, + { + "mvn-path": "com/fasterxml/jackson/jackson-base/2.13.3/jackson-base-2.13.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-ctZykYdsY+GJa8fY/3mQM9OkuQKQIBEEiK+clzFe2Tk=" + }, + { + "mvn-path": "com/fasterxml/jackson/jackson-base/2.9.6/jackson-base-2.9.6.pom", + "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/", + "hash": "sha256-olDkpgIFTfr57pZVr7fWA1OSJslzJ78jfs2LPhcuuQ8=" + }, + { + "mvn-path": "com/fasterxml/jackson/jackson-bom/2.13.3/jackson-bom-2.13.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-32dbg7bKunYC+0fXXUu1E8KvTAphVdfjl8DsDzQRLHU=" + }, + { + "mvn-path": "com/fasterxml/jackson/jackson-bom/2.9.6/jackson-bom-2.9.6.pom", + "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/", + "hash": "sha256-pQ24CCnE+JfG0OfpVHLLtDsOvs4TWmjjnCe4pv4z5IE=" + }, + { + "mvn-path": "com/fasterxml/jackson/jackson-parent/2.13/jackson-parent-2.13.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-K7qJl4Fyrx7/y00UPQmSGj8wgspNzxIrHe2Yv1WyrVc=" + }, + { + "mvn-path": "com/fasterxml/jackson/jackson-parent/2.9.1.1/jackson-parent-2.9.1.1.pom", + "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/", + "hash": "sha256-yD+PRd/cqNC2s2YcYLP4R4D2cbEuBvka1dHBodH5Zug=" + }, + { + "mvn-path": "com/fasterxml/oss-parent/43/oss-parent-43.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5VhcwcNwebLjgXqJl5RXNvFYgxhE1Z0OTTpFsnYR+SY=" + }, + { + "mvn-path": "com/fifesoft/rsyntaxtextarea/2.5.6/rsyntaxtextarea-2.5.6.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-pz2ONAAtaqlbzW7sSUK3EJABS6C4VThlJgWlYARBRVM=" + }, + { + "mvn-path": "com/fifesoft/rsyntaxtextarea/2.5.6/rsyntaxtextarea-2.5.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-ngtNeDGxrM4KT2ejNpUAFVGrRIPfECewJU8oAp9ig/k=" + }, + { + "mvn-path": "com/github/erosb/everit-json-schema/1.14.1/everit-json-schema-1.14.1.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-EgG9+SC3fZoyOAliWOdj6zJmIktwEt/oxN68PcI4gUU=" + }, + { + "mvn-path": "com/github/erosb/everit-json-schema/1.14.1/everit-json-schema-1.14.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-KaOHMqFp9jYtygLvp6dkDWB9WzQKPAfNTc7qpQSxyW8=" + }, + { + "mvn-path": "com/google/javascript/closure-compiler-parent/v20151015/closure-compiler-parent-v20151015.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-eh5qrD+iZYd0Xf1OFk4/RUxAkuy/Aw1Y7cmDKsUttww=" + }, + { + "mvn-path": "com/google/javascript/closure-compiler/v20151015/closure-compiler-v20151015.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Ej5vvBbbUY9F8wV/toFir36TgcCST7rgNYxESc68m2Q=" + }, + { + "mvn-path": "com/google/javascript/closure-compiler/v20151015/closure-compiler-v20151015.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-iTlQOgZcokBSHex7MdnOMOdZubffBiGbeCDHmNAAcEs=" + }, + { + "mvn-path": "com/google/re2j/re2j/1.6/re2j-1.6.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-yLXDRy1NtZSoZbLkf4NdB/uLFBXuulWdzPsKaUXwM80=" + }, + { + "mvn-path": "com/google/re2j/re2j/1.6/re2j-1.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-DL7CO8gvuQDrXrYsX8LLXiObeKDrdl67CObwX6YVgDo=" + }, + { + "mvn-path": "com/jgoodies/forms/1.2.1/forms-1.2.1.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-34AeTRztI/ur1w0C1oUXm/R4lKkwV4ftOk2rW1jQMKk=" + }, + { + "mvn-path": "com/jgoodies/forms/1.2.1/forms-1.2.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Rya6hKIwmKG9F6/xHe6waTOPmywM/X/Xuv4wZYCZsII=" + }, + { + "mvn-path": "com/miglayout/miglayout/3.7.4/miglayout-3.7.4.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-879atAQXZ9pTLhHnikmhg9pJnRpUulOSMtgzt1+WVmc=" + }, + { + "mvn-path": "com/miglayout/miglayout/3.7.4/miglayout-3.7.4.pom", + "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/", + "hash": "sha256-vbjbcBLREqbj6o/bfFELMA2Z7/CBnSfd26nEM5fqTPs=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.11/commons-codec-1.11.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-wecUDR3qj981KLwePFRErAtUEpcxH0X5gGwhPsPumhA=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.15/commons-codec-1.15.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-s+n21jp5AQm/DQVmEfvtHPaQVYJt7+uYlKcTadJG7WM=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.15/commons-codec-1.15.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-yG7hmKNaNxVIeGD0Gcv2Qufk2ehxR3eUfb5qTjogq1g=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.6/commons-codec-1.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-oG410//zprgT2UiU6/PkmPlUDIZMWzmueDkH46bHKIk=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.9/commons-codec-1.9.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5e/PA5zZCWiMIB3FR5sUT9bwHw5AJSt/xefS4bXAeZA=" + }, + { + "mvn-path": "commons-collections/commons-collections/3.2.2/commons-collections-3.2.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-7urpF5FxRKaKdB1MDf9mqlxcX9hVk/8he87T/Iyng7g=" + }, + { + "mvn-path": "commons-collections/commons-collections/3.2.2/commons-collections-3.2.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-1dgfzCiMDYxxHDAgB8raSqmiJu0aES1LqmTLHWMiFws=" + }, + { + "mvn-path": "commons-digester/commons-digester/2.1/commons-digester-2.1.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-4LK5gKhPxlM8XOKR8ZF7MsUH9ivK1kGY//RDaMIZaj0=" + }, + { + "mvn-path": "commons-digester/commons-digester/2.1/commons-digester-2.1.pom", + "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/", + "hash": "sha256-zjST9PzEbftnF+KKcyJH4Qwjr81uGd99lXGRQTPxPMg=" + }, + { + "mvn-path": "commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-iFF8Wh+NYqr0uLPiMSYVucJ5XbhuaVvzcuNiZ0zz9gA=" + }, + { + "mvn-path": "commons-fileupload/commons-fileupload/1.5/commons-fileupload-1.5.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Ufez3LTlDHZimU2i9HIxUZ/5lwelx/t7BfTE06FyjBQ=" + }, + { + "mvn-path": "commons-fileupload/commons-fileupload/1.5/commons-fileupload-1.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-zHIPHgWDV52f5Tk8iE7kbQ10+Z0fm6AXsXKzHqGJ4rE=" + }, + { + "mvn-path": "commons-io/commons-io/2.11.0/commons-io-2.11.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-LgFv1+MkS18sIKytg02TqkeQSG7h5FZGQTYaPoMe71k=" + }, + { + "mvn-path": "commons-io/commons-io/2.2/commons-io-2.2.pom", + "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/", + "hash": "sha256-DCOGOJOiKR9aev29jRWSOzlIr9h+Vj+jQc3Pbq4zimA=" + }, + { + "mvn-path": "commons-io/commons-io/2.8.0/commons-io-2.8.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-AvKR5dEkPcFDSW48u7QKHO1Hqljy1jPT44eAzQaNUHQ=" + }, + { + "mvn-path": "commons-io/commons-io/2.8.0/commons-io-2.8.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-18hkGjfW5282+56B/BQg4moJ1j+jLwD3R2TeBnyoNH0=" + }, + { + "mvn-path": "commons-logging/commons-logging/1.2/commons-logging-1.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-2t3qHqC+D1aXirMAa4rJKDSv7vvZt+TmMW/KV98PpjY=" + }, + { + "mvn-path": "commons-logging/commons-logging/1.2/commons-logging-1.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-yRq1qlcNhvb9B8wVjsa8LFAIBAKXLukXn+JBAHOfuyA=" + }, + { + "mvn-path": "commons-validator/commons-validator/1.7/commons-validator-1.7.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-TXT0zk+2iyYX7a0Ibfbe/fkzhGfSN30sYuaQOOHE8C8=" + }, + { + "mvn-path": "commons-validator/commons-validator/1.7/commons-validator-1.7.pom", + "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/", + "hash": "sha256-XKGO+MsCoGcHmjhu1FbRZMeW5yGnkL5fLKStLANs+Uw=" + }, + { + "mvn-path": "crypto-equality/crypto-equality/1.0.1/crypto-equality-1.0.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-7hXMC3bc9z+PyG0C6e/+1xYKimtAsvDp3nLj3SXtS98=" + }, + { + "mvn-path": "crypto-equality/crypto-equality/1.0.1/crypto-equality-1.0.1.pom", + "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/", + "hash": "sha256-zaRhSsyyvvEgXhdFBuTjGyo+5BF3P04ymZ+8SWX8jKU=" + }, + { + "mvn-path": "crypto-random/crypto-random/1.2.1/crypto-random-1.2.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-XBKIRte2bVPF6XJnc7hy9+AoRP9lGplqXjRSoVI65Sw=" + }, + { + "mvn-path": "crypto-random/crypto-random/1.2.1/crypto-random-1.2.1.pom", + "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/", + "hash": "sha256-D9Sn5HRh29rnILaEsZcWTrtpO7Y1jI4UZ/+nhwdsB/w=" + }, + { + "mvn-path": "hiccup/hiccup/1.0.5/hiccup-1.0.5.pom", + "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/", + "hash": "sha256-wUyefSAkSIDL7oATYbOEo/HeIfX3556wSAOskuW0qjQ=" + }, + { + "mvn-path": "http-kit/http-kit/2.6.0/http-kit-2.6.0.pom", + "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/", + "hash": "sha256-R3uKB53JEvQgT8VZsdXWoChxHpvrnLwPF7wmwzt+LUc=" + }, + { + "mvn-path": "j18n/j18n/1.0.2/j18n-1.0.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-kSkLspknV5blvER3mtH37RdAR9K7Jn0tRpBTPTNETbc=" + }, + { + "mvn-path": "javax/servlet/servlet-api/2.5/servlet-api-2.5.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-xljqNgpw+u6ttm+zyQpwLkFCoKt3aPmumChnjg2a1Nw=" + }, + { + "mvn-path": "javax/servlet/servlet-api/2.5/servlet-api-2.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-aXcVBvPNhDIlLWpsqHCHxmlJf+v8oCr7mnL8vZQ/qh4=" + }, + { + "mvn-path": "jline/jline/2.14.6/jline-2.14.6.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-l9GsqsgkCb5C5iLXpU066dCFF+iu/eo9K6l5EVDC8C0=" + }, + { + "mvn-path": "jline/jline/2.14.6/jline-2.14.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Hfe28I37gaJo1rk7ZZpqnjicXYwyvfB1Qji6ZIOk6YI=" + }, + { + "mvn-path": "joda-time/joda-time/2.10.2/joda-time-2.10.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-09n3j2atjo+9y0tKQ0gerJXzzbzA/gZFQt++M2w6DhA=" + }, + { + "mvn-path": "joda-time/joda-time/2.10.2/joda-time-2.10.2.pom", + "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/", + "hash": "sha256-/D20nRP2Bh7bFXdK1aKnsnnqUaqQCXCHmIsFqAbez/8=" + }, + { + "mvn-path": "lein-aot-order/lein-aot-order/0.1.0/lein-aot-order-0.1.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-PGcHu8j/oxbKfp+ycSeV+pLl0DrwEEaIwabHV4GLaYk=" + }, + { + "mvn-path": "lein-aot-order/lein-aot-order/0.1.0/lein-aot-order-0.1.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Hpa5ikINI0zKEBDuoCGWToy8mAK1ohdqp5nvBDioFD8=" + }, + { + "mvn-path": "luposlip/json-schema/0.4.1/json-schema-0.4.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-pgkJvS1IZEGc0g3FEJpu3eI+Uldyh2cLP4cFXlk9ftg=" + }, + { + "mvn-path": "luposlip/json-schema/0.4.1/json-schema-0.4.1.pom", + "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/", + "hash": "sha256-yd+9FfmaGkMoj3/XEq7jyvlr0X8MswfkY0sM6qjS3tg=" + }, + { + "mvn-path": "net/cgrand/parsley/0.9.2/parsley-0.9.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-wvP/wJtzuvdaidrFz4kS/XMm4kFLuub1Aq5837TGcJ8=" + }, + { + "mvn-path": "net/cgrand/regex/1.1.0/regex-1.1.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-OXrjgEdaN/S7mAmcLn02wYTxyFzvoc7CCmIDM5xDA7o=" + }, + { + "mvn-path": "net/cgrand/regex/1.1.0/regex-1.1.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Mye/UfB0JW7rEViJWNZPzRYys3T6ZyDKB40IWY0snMg=" + }, + { + "mvn-path": "net/java/jvnet-parent/1/jvnet-parent-1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-KBRAgRJo5l2eJms8yJgpfiFOBPCXQNA4bO60qJI9Y78=" + }, + { + "mvn-path": "nrepl/drawbridge/0.2.1/drawbridge-0.2.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-zYIIl7rNL2G5HYG/FtCabOymorXYsGIMWUvTt0RfYgA=" + }, + { + "mvn-path": "nrepl/drawbridge/0.2.1/drawbridge-0.2.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-tJzj0EJ0EacF6t2pVZpq0YHweEBGuD2RWEU++HguYao=" + }, + { + "mvn-path": "nrepl/nrepl/0.6.0/nrepl-0.6.0.pom", + "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/", + "hash": "sha256-GKe1bWh2slPlItP9+wTBq4ieNiDr9Znyic2iDSOUdD0=" + }, + { + "mvn-path": "nrepl/nrepl/1.0.0/nrepl-1.0.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-owuXNP8uY582Xw11U7SHcwbypUPPAh4pnRW/HqvWpbs=" + }, + { + "mvn-path": "nrepl/nrepl/1.0.0/nrepl-1.0.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-dHN5LZGfjodYUES5nySqLqFVNCBgRI2opHhGvkXz2nI=" + }, + { + "mvn-path": "ns-tracker/ns-tracker/0.4.0/ns-tracker-0.4.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-EnsLigxHxoaCRNMi3ShhFRW0B50rHIdjmniaZBNSmaU=" + }, + { + "mvn-path": "ns-tracker/ns-tracker/0.4.0/ns-tracker-0.4.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-TQnZeNHKD219n/HzLg6zCXB5mM8rX1grRnv7Vg5m+JQ=" + }, + { + "mvn-path": "org/apache/apache/13/apache-13.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/1E9sDYf1BI3vvR4SWi8FarkeNTsCpSW+BEHLMrzhB0=" + }, + { + "mvn-path": "org/apache/apache/15/apache-15.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-NsLy+XmsZ7RQwMtIDk6br2tA86aB8iupaSKH0ROa1JQ=" + }, + { + "mvn-path": "org/apache/apache/16/apache-16.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-n4X/L9fWyzCXqkf7QZ7n8OvoaRCfmKup9Oyj9J50pA4=" + }, + { + "mvn-path": "org/apache/apache/18/apache-18.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-eDEwcoX9R1u8NrIK4454gvEcMVOx1ZMPhS1E7ajzPBc=" + }, + { + "mvn-path": "org/apache/apache/19/apache-19.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-kfejMJbqabrCy69tAf65NMrAAsSNjIz6nCQLQPHsId8=" + }, + { + "mvn-path": "org/apache/apache/21/apache-21.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-rxDBCNoBTxfK+se1KytLWjocGCZfoq+XoyXZFDU3s4A=" + }, + { + "mvn-path": "org/apache/apache/23/apache-23.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-vBBiTgYj82V3+sVjnKKTbTJA7RUvttjVM6tNJwVDSRw=" + }, + { + "mvn-path": "org/apache/apache/29/apache-29.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-PkkDcXSCC70N9jQgqXclWIY5iVTCoGKR+mH3J6w1s3c=" + }, + { + "mvn-path": "org/apache/apache/7/apache-7.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-E5fOHbQzrcnyI9vwdJbRM2gUSHUfSuKeWPaOePtLbCU=" + }, + { + "mvn-path": "org/apache/apache/9/apache-9.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-SUbmClR8jtpp87wjxbbw2tz4Rp6kmx0dp940rs/PGN0=" + }, + { + "mvn-path": "org/apache/commons/commons-math/2.2/commons-math-2.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-FZk7sqPPUPMpG0D8mAoxZqCYTntfGrvlIyFR/ZSVRYQ=" + }, + { + "mvn-path": "org/apache/commons/commons-math/2.2/commons-math-2.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-WzDBZBSY7onOrNxW/PtST2K3JCt3gHRN/GGld1O+8Kc=" + }, + { + "mvn-path": "org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-HlbXsFjSi2Wr0la4RY44hbZ0wdWI+kPNfRy7nH7yswg=" + }, + { + "mvn-path": "org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-+tcjNup9fdBtoQMUTjdA21CPpLF9nFTXhHc37cJKfmA=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/17/commons-parent-17.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-lucYuvU0h07mLOTULeJl8t2s2IORpUDgMNWdmPp8RAg=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/18/commons-parent-18.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-qlKBSXsVknTuOULf4s8Qo2jCT0khUVCkzAmKsoVFmyg=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/22/commons-parent-22.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-+4xeVeMKet20/yEIWKDo0klO1nV7vhkBLamdUVhsPLs=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/24/commons-parent-24.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-qjkoILXpVvV/jY9bCHj8uJILOWH/uhU8tpID6WpxPM0=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/32/commons-parent-32.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5NJYr4sv9AMhSNQVN53veHB4mmAD6AV28VBLEPJrS+g=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/34/commons-parent-34.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Oi5p0G1kHR87KTEm3J4uTqZWO/jDbIfgq2+kKS0Et5w=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/35/commons-parent-35.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-cJihq4M27NTJ3CHLvKyGn4LGb2S4rE95iNQbT8tE5Jo=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/39/commons-parent-39.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-h80n4aAqXD622FBZzphpa7G0TCuLZQ8FZ8ht9g+mHac=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/41/commons-parent-41.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-sod8gBb4sokkyOkN1a5AzRHzKNAqHemNgN4iV0qzbsc=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/42/commons-parent-42.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-zTE0lMZwtIPsJWlyrxaYszDlmPgHACNU63ZUefYEsJw=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/47/commons-parent-47.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-io7LVwVTv58f+uIRqNTKnuYwwXr+WSkzaPunvZtC/Lc=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/51/commons-parent-51.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-m3edGLItjeVZYFVY57sKCjGz8Awqu5yHgRfDmKrKvso=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/52/commons-parent-52.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-ddvo806Y5MP/QtquSi+etMvNO18QR9VEYKzpBtu0UC4=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/56/commons-parent-56.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-VgxwUd3HaOE3LkCHlwdk5MATkDxdxutSwph3Nw2uJpQ=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.3/httpasyncclient-4.1.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-PaAuVYrN/VVGzpb3k4287Bvjjg54PuJMYqz9Slc1Ysk=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.4/httpasyncclient-4.1.4.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-UOmBqOVnoW69rRBGBbFWVAqGNFn6EnuLpkfzEN/IPvg=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.4/httpasyncclient-4.1.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-NPJO+Ya4nVG4CgkDh6o9DIJNQPCHrlzPrUf/PCYsLkc=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient-cache/4.5.13/httpclient-cache-4.5.13.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Zs797nR1mFJWr2gL86581dfULo/euTmmJ3ki4b3u1Do=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient-cache/4.5.13/httpclient-cache-4.5.13.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-o1h75UcWw7gKMonBHfhlqWK8cr5HiRReQgxpSL9FpKQ=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient-cache/4.5.5/httpclient-cache-4.5.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5fDcW+r/uQjJuhuA9X3aTJDpHWiV2DIXsYGuq+Zd11M=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-b+kCalZsalABYIzz/DIZZkH2weXhmG0QN8zb1fMe90M=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-eOua2nSSn81j0HrcT0kjaEGkXMKdX4F79FgB9RP9fmw=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient/4.5.3/httpclient-4.5.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-zeV1AwnAe7rBQNDZgWlyQ9PFjC7lJcExm5uoh9ifLJw=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient/4.5.5/httpclient-4.5.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-2zsBmOEfOqX6UTEMkVuBjBNKjLy4L8gd35W6IxOGJiY=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient/4.5.6/httpclient-4.5.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-fvwSQec+f7smi/0zJC0R69PKBwYdfYXyli3DKg8LiFU=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-asyncclient/4.1.3/httpcomponents-asyncclient-4.1.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-heaQHESTgHNAodUcJWVc/6/KMQbTtb1Gf64gq8E56xY=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-asyncclient/4.1.4/httpcomponents-asyncclient-4.1.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-6WXrNprx2LWmi31LeGHVOlJhaugD4TFi6N+EImFwAYA=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.13/httpcomponents-client-4.5.13.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-nLpZTAjbcnHQwg6YRdYiuznmlYORC0Xn1d+C9gWNTdk=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.3/httpcomponents-client-4.5.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Q8fSl1oRJbsAzwWKOHf1SaHxiU+LitR+LD5G4/xGI9w=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.5/httpcomponents-client-4.5.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-FEXQEhWPlBcxpgYsfqt0AJPqJ0W0a1TeI2s/d4fpm/M=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.6/httpcomponents-client-4.5.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-sEK0HyOR7bANNff05Qmu0hI2SMHSRs5Y0Pe5Bcn+H3M=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.10/httpcomponents-core-4.4.10.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-YelCfUvjJsMHp/FrqCjRyzsUcTybBPyLqZKljzdsMTY=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.13/httpcomponents-core-4.4.13.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-xVTnAI5FF8fvVOAFzIt09Mh6VKDqLG9Xvl0Fad9Rk2s=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.14/httpcomponents-core-4.4.14.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-IJ7ZMctXmYJS3+AnyqnAOtpiBhNkIylnkTEWX4scutE=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.6/httpcomponents-core-4.4.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Kjy1MtDdY/Pz8IAwft1f+E9GJSU79rVk63Jzd1Pdlr4=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.9/httpcomponents-core-4.4.9.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-MuZglakZRW/HahDHhl5wyaFMYru6hHAmQgoFVlI2axg=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-parent/10/httpcomponents-parent-10.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-yq+WfZSvshdT82CCxghiBr0fSIJf9ZaTLM66crZdOfo=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-parent/11/httpcomponents-parent-11.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-qQH4exFcVQcMfuQ+//Y+IOewLTCvJEOuKSvx9OUy06o=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-parent/9/httpcomponents-parent-9.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-JlbH5Avb5rb5WHmPfWkYtQtUTfDiO1LONzG5zMILX4w=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore-nio/4.4.10/httpcore-nio-4.4.10.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-3r7n6VcsAqFs4Mqk9WWp7OsSkNM816HjKXCHvUZ9r/Q=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore-nio/4.4.10/httpcore-nio-4.4.10.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-pMmVtzjRBLdcyLEWTbYAUzFWwEfsy0yO5dknshoX7HM=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore-nio/4.4.6/httpcore-nio-4.4.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Slb+Yua6ijj46jhdhEM5IXm5XQ9WKMjpGJ2cwkj5enw=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.10/httpcore-4.4.10.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-xcEgZt8rO4iomiyGArgeqaYWJ+l25RKe6hiZ67rqOSs=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-j4Etn6e3Kj1Kp/glJ4kypd80S0Km2DmJBYeUMaG/mpc=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-+VYgnkUMsdDFF3bfvSPlPp3Y25oSmO1itwvwlEumOyg=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-VXFjmKl48QID+eJciu/AWA2vfwkHxu0K6tgexftrf9g=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.6/httpcore-4.4.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-OYKFz+9VT7VbgfIMlPVL8OKLBGK6kvo5cOkzlFwI9lU=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.9/httpcore-4.4.9.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-bpS9d3vu3v+bXncM9lS1MDJXgQNLJ0bGMrEx7HStUTw=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpmime/4.5.13/httpmime-4.5.13.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-BudU2ZJFuY3MKGDctD0g5zfWUNor8gd6EF9orMvVxcw=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpmime/4.5.13/httpmime-4.5.13.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-k0GN8hCu7VBQJUjbzysXwPHZFEMDDnL+++7RZSscKN0=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpmime/4.5.5/httpmime-4.5.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5qZxutaVb3QY/hCRYuho60VZxk+gnu9G6+RxqEqt6lw=" + }, + { + "mvn-path": "org/apache/httpcomponents/project/7/project-7.pom", + "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/", + "hash": "sha256-dXcUiX+LDjqoUl/23ESW2dGRI6qXKl9mg5B6WosaC1s=" + }, + { + "mvn-path": "org/clojars/trptcolin/sjacket/0.1.1.1/sjacket-0.1.1.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-PMP2YNEnMFxecW50TPbfkp9u/TUVQk/GkSRf3mfcDgQ=" + }, + { + "mvn-path": "org/clojure/algo.generic/0.1.3/algo.generic-0.1.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-39kqKQba+lfi6hS6TMIjgsRHrID8TxKXxtAFMWxk/6o=" + }, + { + "mvn-path": "org/clojure/algo.generic/0.1.3/algo.generic-0.1.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-hOSMGjYRNiqj4f7glDMiknw8cBn8pUyq2v76iVuIHto=" + }, + { + "mvn-path": "org/clojure/clojure/1.10.0/clojure-1.10.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-IA0uyoZlJAy8uu5IwAcyS3tE02YIDpWN8dIujd4kg4w=" + }, + { + "mvn-path": "org/clojure/clojure/1.10.1/clojure-1.10.1.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-1Pb5kf2e0qWefqR3kBCzsGmiuQXzRjE2xCIBEGtK0ho=" + }, + { + "mvn-path": "org/clojure/clojure/1.10.1/clojure-1.10.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-EPevJPoORzOE7RqAPgCpB0KzwA+Q3jW2uxXosW3YOow=" + }, + { + "mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-fxJHLa7Y9rUXSYqqKrE6ViR1w+31FHjkWBzHYemJeaM=" + }, + { + "mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-GJwAxDNAdJai+7DsyzeQjJSVXZHq0b5IFWdE7MGBbZQ=" + }, + { + "mvn-path": "org/clojure/clojure/1.11.0/clojure-1.11.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-PiH6daB+yd278bK1A1bPGAcQ0DmN6qT0TpHNYwRVWUc=" + }, + { + "mvn-path": "org/clojure/clojure/1.11.0/clojure-1.11.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-SQjMS0yeYsmoFJb5PLWsb2lBd8xkXc87jOXkkavOHro=" + }, + { + "mvn-path": "org/clojure/clojure/1.11.1/clojure-1.11.1.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-I4G26UI6tGUVFFWUSQPROlYkPWAGuRlK/Bv0+HEMtN4=" + }, + { + "mvn-path": "org/clojure/clojure/1.11.1/clojure-1.11.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-IMRaGr7b2L4grvk2BQrjGgjBZ0CzL4dAuIOM3pb/y4o=" + }, + { + "mvn-path": "org/clojure/clojure/1.2.0/clojure-1.2.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-eYmoFfHv4aQ82mDQHBmPW4pzNZnX42jlAGfZa0uX+i8=" + }, + { + "mvn-path": "org/clojure/clojure/1.2.1/clojure-1.2.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-+gbs9R2SeSGzckdfLp0E8IZs+BIy8+WxuoPZfxb/vI8=" + }, + { + "mvn-path": "org/clojure/clojure/1.3.0/clojure-1.3.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Z1wrqAzN8w47VMhNaZ5dZT5wfz8xPElsJagMuTx/e6o=" + }, + { + "mvn-path": "org/clojure/clojure/1.4.0/clojure-1.4.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-TF5AycYSlMrYPnz2fsZhT2C4PcA3zrVFRu4QN8Jr1sg=" + }, + { + "mvn-path": "org/clojure/clojure/1.4.0/clojure-1.4.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/5umkvBz16TfvI+otOQvbuPYyAsYBD5yx75dPOrTyyE=" + }, + { + "mvn-path": "org/clojure/clojure/1.5.1/clojure-1.5.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-fXjomnJLgCMQOe2BvzkyQikA801AmlMQzWXXjJJvWaw=" + }, + { + "mvn-path": "org/clojure/clojure/1.6.0/clojure-1.6.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-zZNQkTItNxosYSo8dPj4WnH4iMV2PhjInRqcJxHJn7U=" + }, + { + "mvn-path": "org/clojure/clojure/1.7.0/clojure-1.7.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-X3KW7VH/cOurbSg/gDJCmQvXvadBn5QTLs9AajXJS38=" + }, + { + "mvn-path": "org/clojure/clojure/1.8.0/clojure-1.8.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/70SvB18eBvurz0rxfNBYNv0UCyUSjdjE0u6+XEAFuQ=" + }, + { + "mvn-path": "org/clojure/clojure/1.9.0/clojure-1.9.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-KH5voGjq/VIBDjadloqdqGTgz+/uICDzchf1OX+v/As=" + }, + { + "mvn-path": "org/clojure/clojurescript/1.7.170/clojurescript-1.7.170.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5COqAXCx08KOsl/kji+KVzxLK19HH7jrphbLW1yPVVY=" + }, + { + "mvn-path": "org/clojure/clojurescript/1.7.170/clojurescript-1.7.170.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-3NxQZHI5f265GqOAiE+3EtqBrtaqUyvAzh5o5Zp8wwI=" + }, + { + "mvn-path": "org/clojure/core.async/1.6.673/core.async-1.6.673.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-FoHdGIjHVAH0RLURyDU/vaPOsadgiBCiPd0l0QRfkHo=" + }, + { + "mvn-path": "org/clojure/core.async/1.6.673/core.async-1.6.673.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-S8rQJfFQpWa3+vdJPQSEy1momBySO3jFC88ORiHr3jg=" + }, + { + "mvn-path": "org/clojure/core.cache/1.0.225/core.cache-1.0.225.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-wVOqlH7aXNvYqTiCyPur1QN9StcxGAK0vNgBVGn2pbE=" + }, + { + "mvn-path": "org/clojure/core.cache/1.0.225/core.cache-1.0.225.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-OeNB9nv+85PkeDkNSYjxGad5ykSQZssNM/gLQv8E9D0=" + }, + { + "mvn-path": "org/clojure/core.memoize/1.0.253/core.memoize-1.0.253.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-SpEFhRgqsybB0KINNDFb4VY7WlhDfUHAId1/6ZEeHtY=" + }, + { + "mvn-path": "org/clojure/core.memoize/1.0.253/core.memoize-1.0.253.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-hML6t6Mso8HkDEGm7Mm9U26UezBYDne41dwjKjSSXqw=" + }, + { + "mvn-path": "org/clojure/core.specs.alpha/0.1.24/core.specs.alpha-0.1.24.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-02cX+eu4IrMZezUSn9EwYc1xq3/Dbw9AOGx35qCPn1k=" + }, + { + "mvn-path": "org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Ox7E1vDo5Bv3aEJwkIO+s7Vq3zyC+aTxdMPadHdLOBw=" + }, + { + "mvn-path": "org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-jdN7ZsnY5T8RcC75U8k3HFqPIzm+RHFQhvl7VFeZpoY=" + }, + { + "mvn-path": "org/clojure/core.specs.alpha/0.2.56/core.specs.alpha-0.2.56.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/PRCveArBKhj8vzFjuaiowxM8Mlw99q4VjTwq3ERZrY=" + }, + { + "mvn-path": "org/clojure/core.specs.alpha/0.2.56/core.specs.alpha-0.2.56.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-AarxdIP/HHSCySoHKV1+e8bjszIt9EsptXONAg/wB0A=" + }, + { + "mvn-path": "org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Bu6owHC75FwVhWfkQ0OWgbyMRukSNBT4G/oyukLWy8g=" + }, + { + "mvn-path": "org/clojure/core.specs.alpha/0.2.62/core.specs.alpha-0.2.62.pom", + "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/", + "hash": "sha256-gTpWu971A7Y1CswhrG9/YKLF2wi+wQpF60uRfmbtE04=" + }, + { + "mvn-path": "org/clojure/data.csv/1.0.1/data.csv-1.0.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-1cESYLiumGM6l3FThM8ENeAknrIhTi7VPaTMGMkToAE=" + }, + { + "mvn-path": "org/clojure/data.int-map/1.2.1/data.int-map-1.2.1.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-EEdYYvpJt4MszXZeV34rlcIYLgKR/N88IwAkLyi6sRQ=" + }, + { + "mvn-path": "org/clojure/data.int-map/1.2.1/data.int-map-1.2.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-bO0gykWPpQ4MVVm9tG5lmIiAHT8jHJESk2j44dtGZT8=" + }, + { + "mvn-path": "org/clojure/data.json/0.2.6/data.json-0.2.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/Fe/UH6xwY8Hg0QgCh5Z6BDeZOz5Oa65s1tC/NMyt60=" + }, + { + "mvn-path": "org/clojure/data.json/2.4.0/data.json-2.4.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-7D8vmU4e7dQgMTxFK6VRjF9cl75RUt/tVlC8ZhFIat8=" + }, + { + "mvn-path": "org/clojure/data.json/2.4.0/data.json-2.4.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-pC6nDxe1F2Zq2EkqG/qRfeXe+se0fFFvbQ1NicJ4DPQ=" + }, + { + "mvn-path": "org/clojure/data.priority-map/0.0.5/data.priority-map-0.0.5.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-oPW/DsJNXwioXkfdsIOKzg/c1682pap9QMfZJeZLeYA=" + }, + { + "mvn-path": "org/clojure/data.priority-map/0.0.5/data.priority-map-0.0.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-OuRzO1o2KIhJOJMC+4SXnXxSqtiOWINYMsf/jTIf/Sg=" + }, + { + "mvn-path": "org/clojure/data.priority-map/1.1.0/data.priority-map-1.1.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-RlIA+U9W2IaOD9eqC+zGL/sCz69CCkmtEXkQ5jr13/4=" + }, + { + "mvn-path": "org/clojure/data.xml/0.0.8/data.xml-0.0.8.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-dMpOhrqASGN52URcTX7F66RzBbMIv44OpfUcUD+jUt4=" + }, + { + "mvn-path": "org/clojure/data.xml/0.0.8/data.xml-0.0.8.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-tDl+uiVvWCphQJov8hGpooZmw1ePmjag0Yb7hehYOLM=" + }, + { + "mvn-path": "org/clojure/google-closure-library-third-party/0.0-20151016-61277aea/google-closure-library-third-party-0.0-20151016-61277aea.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-G7ZzWZPiNv91x+e0rz5Bi1xeI8DT0PDYk7dGOd7+Y9U=" + }, + { + "mvn-path": "org/clojure/google-closure-library-third-party/0.0-20151016-61277aea/google-closure-library-third-party-0.0-20151016-61277aea.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Vbyb67d6vebfYWrgolFFo287DMJn5zFnABLpyj8Q1HQ=" + }, + { + "mvn-path": "org/clojure/google-closure-library/0.0-20151016-61277aea/google-closure-library-0.0-20151016-61277aea.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-KX1xOZsZgmdBlhaEX4p3YP6o6rQkX4/b/heVtsnrVWw=" + }, + { + "mvn-path": "org/clojure/google-closure-library/0.0-20151016-61277aea/google-closure-library-0.0-20151016-61277aea.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-4cCY6XzbWh+9Bu9zMtMDl5MlaQM4YRbZj0ZINxikzMU=" + }, + { + "mvn-path": "org/clojure/java.classpath/0.2.3/java.classpath-0.2.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-nsZDMTCnJVv0I5Fzsd82LB5ic15cDIPdgbTrrzfwKDE=" + }, + { + "mvn-path": "org/clojure/java.classpath/0.2.3/java.classpath-0.2.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-d27PvZLMmNCM+ZeN5iclDYhti10RlCIh0dZ5siiA59A=" + }, + { + "mvn-path": "org/clojure/java.classpath/0.3.0/java.classpath-0.3.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-IXb2vtNMWGdd99VNgUZloJEY+yZfGuZm3hlg88uxUQ4=" + }, + { + "mvn-path": "org/clojure/java.classpath/0.3.0/java.classpath-0.3.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-3HT8YeCMPpABhqeoyvAAQj9EG9Q9XQ6aiRccOFj79qg=" + }, + { + "mvn-path": "org/clojure/math.combinatorics/0.2.0/math.combinatorics-0.2.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-sIzH3pFeSK/V2mGUuYgXxHqlCvZ3RyHMv7lKn0vWbWM=" + }, + { + "mvn-path": "org/clojure/math.combinatorics/0.2.0/math.combinatorics-0.2.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-9jURjWWI35hOA+nLRKjWuuL9x7nGrnub7QAqkoOex+U=" + }, + { + "mvn-path": "org/clojure/math.numeric-tower/0.0.5/math.numeric-tower-0.0.5.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5NIRNXlWkT5DvcbD/csjExqWLjHX6G16GnTTFRm9+/8=" + }, + { + "mvn-path": "org/clojure/math.numeric-tower/0.0.5/math.numeric-tower-0.0.5.pom", + "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/", + "hash": "sha256-RoC9g43MuowXwlgXE0fxb1uq5rXft4Grc4K8Y4X/gAY=" + }, + { + "mvn-path": "org/clojure/pom.contrib/0.2.2/pom.contrib-0.2.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-4OoifEnFw+MHVM0m/MV75+Telz/kOqXMZmdAHsXBAyM=" + }, + { + "mvn-path": "org/clojure/pom.contrib/0.3.0/pom.contrib-0.3.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-fxgrOypUPgV0YL+T/8XpzvasUn3xoTdqfZki6+ee8Rk=" + }, + { + "mvn-path": "org/clojure/pom.contrib/1.1.0/pom.contrib-1.1.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-EOzku1+YKQENwWVh9C67g7ry9HYFtR+RBbkvPKoIlxU=" + }, + { + "mvn-path": "org/clojure/spec.alpha/0.1.143/spec.alpha-0.1.143.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-CC36KSqOCpQKuRq+IKuWsjKTSbBAHlScNI5P+qUBOis=" + }, + { + "mvn-path": "org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/E6W7P803dKrf9BQ50rhN5NC7gnapgKNpSAkxd6DbMQ=" + }, + { + "mvn-path": "org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-yZzAR3tYTMi3apzWagPlv8kLGdQ5dX8YMhA7avggspI=" + }, + { + "mvn-path": "org/clojure/spec.alpha/0.2.194/spec.alpha-0.2.194.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-z2iZ+YUpjGSxPqEplGrZAo3uja3w6rmuGORVAn04JJw=" + }, + { + "mvn-path": "org/clojure/spec.alpha/0.2.194/spec.alpha-0.2.194.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-WhHw4eizwFLmUcSYxpRbRNs1Nb8sGHGf3PZd8fiLE+Y=" + }, + { + "mvn-path": "org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Z+yJjrVcZqlXpVJ53YXRN2u5lL2HZosrDeHrO5foquA=" + }, + { + "mvn-path": "org/clojure/spec.alpha/0.3.218/spec.alpha-0.3.218.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-bY3hTDrIdXYMX/kJVi/5hzB3AxxquTnxyxOeFp/pB1g=" + }, + { + "mvn-path": "org/clojure/tools.analyzer.jvm/1.2.2/tools.analyzer.jvm-1.2.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-kQz/AjiTHtiIYstmWmd+ldk+hIDyIzIAiG0zHX7QDl4=" + }, + { + "mvn-path": "org/clojure/tools.analyzer.jvm/1.2.2/tools.analyzer.jvm-1.2.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-EOGi60Q6PFfsGd7e8ylC63SbrmnyFZiI/lYLpnuwj0c=" + }, + { + "mvn-path": "org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-E2i2vDvd98OY1XhNEFSPRMTtLXwB6hBawO/enPXg3yE=" + }, + { + "mvn-path": "org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-NyBxL7knYaNclNDuQV1r8VhB70afBzZGd2h1553JtwY=" + }, + { + "mvn-path": "org/clojure/tools.cli/0.3.1/tools.cli-0.3.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5Ra0m3LZ+cymDu5ojQR8aCD1YGNUEdVWZ5Juv2b6buM=" + }, + { + "mvn-path": "org/clojure/tools.cli/1.0.219/tools.cli-1.0.219.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-lGADrC8iiODAxYhhYk3z75gRHJ6Id+tAHKUozDd/l6k=" + }, + { + "mvn-path": "org/clojure/tools.cli/1.0.219/tools.cli-1.0.219.pom", + "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/", + "hash": "sha256-DF8L5wYU7fWwdy0VfeBnxPJ2wfftJ6PNILwGVK7Yra4=" + }, + { + "mvn-path": "org/clojure/tools.namespace/0.2.11/tools.namespace-0.2.11.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-I2Pi1bhg6NlXcr0z2mIO8+Ww+/OCHDSSQT9ftqJI3b4=" + }, + { + "mvn-path": "org/clojure/tools.namespace/0.3.0-alpha3/tools.namespace-0.3.0-alpha3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-6ok9330fVndKPbgRmD3g4LQOPh+lrkEhxWplKJEJEzE=" + }, + { + "mvn-path": "org/clojure/tools.namespace/0.3.0-alpha3/tools.namespace-0.3.0-alpha3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-I43i5Sa45yr56RCN2P5NjwVEGwqy3A/kLU8lutFy61M=" + }, + { + "mvn-path": "org/clojure/tools.reader/0.10.0-alpha3/tools.reader-0.10.0-alpha3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-EE60zx3DmRcTb/FT1KevfEmmjxK73667naNvOG3+MJo=" + }, + { + "mvn-path": "org/clojure/tools.reader/0.10.0/tools.reader-0.10.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-N9qaFTfvZ83Ew12Ok9M2xUmEwgVPy7uQxQuzOR2jqBI=" + }, + { + "mvn-path": "org/clojure/tools.reader/0.10.0/tools.reader-0.10.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-0gGx38KTjKfQqx3pFpp5IgjA3kVjehbEvxAFfjiqzuw=" + }, + { + "mvn-path": "org/clojure/tools.reader/1.3.6/tools.reader-1.3.6.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-EdGzHyxlwzVbKSu5tEuPyv2lS0TaY+NKuXt5qKs7uOA=" + }, + { + "mvn-path": "org/clojure/tools.reader/1.3.6/tools.reader-1.3.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-rvXugot8sUocWPRbn4oQ/zQMV2mSXqDvXDXR5J2SC+o=" + }, + { + "mvn-path": "org/json/json/20230227/json-20230227.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-ntJnkdwthin9+KIH8a663LUNZBvmN2ZDEO9RwPc+Jps=" + }, + { + "mvn-path": "org/json/json/20230227/json-20230227.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-w5JpSWHKzw6oY/WCyHTfHYbrWRu/tSaqBY7/eULakHA=" + }, + { + "mvn-path": "org/junit/junit-bom/5.7.2/junit-bom-5.7.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-zRSqqGmZH4ICHFhdVw0x/zQry6WLtEIztwGTdxuWSHs=" + }, + { + "mvn-path": "org/junit/junit-bom/5.9.1/junit-bom-5.9.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-sWPBz8j8H9WLRXoA1YbATEbphtdZBOnKVMA6l9ZbSWw=" + }, + { + "mvn-path": "org/mozilla/rhino/1.7R5/rhino-1.7R5.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-4A4Jpxq8RnfhfdjUKwdVtZqemrCbYP2LGrtF5MgECcA=" + }, + { + "mvn-path": "org/mozilla/rhino/1.7R5/rhino-1.7R5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-28mHxqtE9dzKokjgA/ge/HVEVY+OegVM6xBY+0nRzTs=" + }, + { + "mvn-path": "org/nrepl/incomplete/0.1.0/incomplete-0.1.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-beLz9UBjtMi6E63xC2IdP4muwTFwM2SPLSSzlRB3ouM=" + }, + { + "mvn-path": "org/nrepl/incomplete/0.1.0/incomplete-0.1.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-1LvcRQ5FaR2KfmrAGBqOr5amU4spUZZWo/zO6JVNY0s=" + }, + { + "mvn-path": "org/ow2/asm/asm/9.2/asm-9.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-udT+TXGTjfOIOfDspCqqpkz4sxPWeNoDbwyzyhmbR/U=" + }, + { + "mvn-path": "org/ow2/asm/asm/9.2/asm-9.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-37EqGyJL8Bvh/WBAIEZviUJBvLZF3M45Xt2M1vilDfQ=" + }, + { + "mvn-path": "org/ow2/ow2/1.5/ow2-1.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-D4obEW52C4/mOJxRuE5LB6cPwRCC1Pk25FO1g91QtDs=" + }, + { + "mvn-path": "org/ow2/sat4j/org.ow2.sat4j.core/2.3.6/org.ow2.sat4j.core-2.3.6.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Dnqs3Zdt2VgpHmpVmb2uDUAkb+Ene3tjqA2w/JLUadc=" + }, + { + "mvn-path": "org/ow2/sat4j/org.ow2.sat4j.core/2.3.6/org.ow2.sat4j.core-2.3.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-X6UiKuspIeDBmsYcM3x4WjeIWcUZktBLr4Ob+yoOiGk=" + }, + { + "mvn-path": "org/ow2/sat4j/org.ow2.sat4j.pom/2.3.6/org.ow2.sat4j.pom-2.3.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-oKFkiu2kvKpd0AvsWq77eLrZCreLstrwloU5M3haXDI=" + }, + { + "mvn-path": "org/sonatype/oss/oss-parent/5/oss-parent-5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-FnjUEgpYXYpjATGu7ExSTZKDmFg7fqthbufVqH9SDT0=" + }, + { + "mvn-path": "org/sonatype/oss/oss-parent/7/oss-parent-7.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-tR+IZ8kranIkmVV/w6H96ne9+e9XRyL+kM5DailVlFQ=" + }, + { + "mvn-path": "org/sonatype/oss/oss-parent/9/oss-parent-9.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-+0AmX5glSCEv+C42LllzKyGH7G8NgBgohcFO8fmCgno=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-action/1.6.3/swingx-action-1.6.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-W/zftwqqj8RuWo0Q4T6XJkcRrE6S8nqg/rq84+8Xafs=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-action/1.6.3/swingx-action-1.6.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-PbB68P5Rg7vx2suM9/C4KtuIYG0iJ/O/RUURK2qrpTU=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-autocomplete/1.6.3/swingx-autocomplete-1.6.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-2TM8FBBmGrHSyYsyfsr1mRL7ohvVtyATZpUif5li51w=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-autocomplete/1.6.3/swingx-autocomplete-1.6.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-LzeSIRcvUpqWxeK5Iu/ckugW5+82+PsxVoX87Vlks2k=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-common/1.6.3/swingx-common-1.6.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-705XyRNv6DvEN4WdnxaxTGr7zGWh8uAG/xvMhbugSs0=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-common/1.6.3/swingx-common-1.6.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-e0HQnmjbABDTj7HG65+VWiQ1WLHYPrWLqVoL+E/lk5w=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-core/1.6.3/swingx-core-1.6.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-JgTuS/ji490NHy0FXnNCZFYU6cguBsQyY/PR8J6A6lg=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-core/1.6.3/swingx-core-1.6.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/0u/MezGuX8DV+LvV6T8PvtY03m4hMe0FWJwyPHOFfo=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-painters/1.6.3/swingx-painters-1.6.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-TPMEQCNH30/6ZLk1uKx8qvAZnxFC+mL45QZj1HIlIMI=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-painters/1.6.3/swingx-painters-1.6.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-+/Wt/pDLOECHzYeVkGbDlw76Y1lqmPQUrXz6YMeFHow=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-plaf/1.6.3/swingx-plaf-1.6.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-jIn7gFf5+ocGrX99hiST8hwWGyFOcgbDn6BZ5sFf8E4=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-plaf/1.6.3/swingx-plaf-1.6.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-ZzvS/DPnwJ5iLsRxL8TEt8fifWknzcD1BLxsYJvpGZE=" + }, + { + "mvn-path": "org/swinglabs/swingx/swingx-project/1.6.3/swingx-project-1.6.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Mf7YCTza2mFHjDCUFFYiZIndn6yw5mC2KPK8Fgp8KiU=" + }, + { + "mvn-path": "potemkin/potemkin/0.4.5/potemkin-0.4.5.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-KzZsg02Hy26mMbpoaXvDdNDRr4H23rQsKECUTaMgvZk=" + }, + { + "mvn-path": "potemkin/potemkin/0.4.5/potemkin-0.4.5.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-3tL5YlDzDlqPmI60YeMvKzDzbBy0Qz+6qHu82kJRTDo=" + }, + { + "mvn-path": "reply/reply/0.5.1/reply-0.5.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-UkcQm+0oBiDtjWoL0AAQPUl/hMDnL17DOG3mxaqFJtk=" + }, + { + "mvn-path": "reply/reply/0.5.1/reply-0.5.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-qkKSptY8RMcRBq+9gLw83Om5XLlP/7wgpEszkAqvicQ=" + }, + { + "mvn-path": "riddley/riddley/0.1.12/riddley-0.1.12.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-OY9h+kgluPhskWrlgMfhM7fEd9C3Kn07KY04EDJ0C64=" + }, + { + "mvn-path": "riddley/riddley/0.1.12/riddley-0.1.12.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-At+3ryDvgcJTZQVfYCjoscwpBdCyaLuJzEKM2nIwo2U=" + }, + { + "mvn-path": "ring-cors/ring-cors/0.1.13/ring-cors-0.1.13.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ykElZhlNMX9s7clO4Fmwl6h3B6e+mzOMn1MheihvxIw=" + }, + { + "mvn-path": "ring-cors/ring-cors/0.1.13/ring-cors-0.1.13.pom", + "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/", + "hash": "sha256-/iYsnPUyUprMi/VMioni1S7XjGJxe2n2iH06u+3HBE0=" + }, + { + "mvn-path": "ring/ring-codec/1.1.3/ring-codec-1.1.3.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ex+T/d9XCNCWYSwQwJJ8zQP/QTXR/p7Fba0hucnWTFI=" + }, + { + "mvn-path": "ring/ring-codec/1.2.0/ring-codec-1.2.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-b9itBQBiBVtXtzgRBnH2j0NXdEQ9GCbmL07GTSxFZcI=" + }, + { + "mvn-path": "ring/ring-codec/1.2.0/ring-codec-1.2.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-de3pMoKzj49m+yTFILdNGDfQsbtdpUIW+AOglmzp2s4=" + }, + { + "mvn-path": "ring/ring-core/1.10.0/ring-core-1.10.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-890nOHNp+7xifHrtBMLP3Vllo0ni6/tU6Lm/lisgxYo=" + }, + { + "mvn-path": "ring/ring-core/1.10.0/ring-core-1.10.0.pom", + "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/", + "hash": "sha256-MZUZuyMA+z2xtjry80cfyc3yrSJWqg2OzrXEQoPeKaU=" + }, + { + "mvn-path": "ring/ring-core/1.9.2/ring-core-1.9.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-03k0Epji4KSB/e+zKMZluPCsiLT5GlevXiO3+JIXNDU=" + }, + { + "mvn-path": "ring/ring-devel/1.10.0/ring-devel-1.10.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-PR0RDPdBzvmbv/xLilmJZ7WvQmGMcTAm1qrZXmBUaVo=" + }, + { + "mvn-path": "ring/ring-devel/1.10.0/ring-devel-1.10.0.pom", + "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/", + "hash": "sha256-Eh/gK+ecm0Y1kRtmaJzYNGsRZhhwIlAbIUtm7EFRHq0=" + }, + { + "mvn-path": "ring/ring-json/0.5.1/ring-json-0.5.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-FQ/7MYrIR0ASYuGFMEt4Tlf3jg2uczPX1DjQgnTZLr8=" + }, + { + "mvn-path": "ring/ring-mock/0.4.0/ring-mock-0.4.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-iUGAYV7PllpUB+sx2gZPmbaemYEbRoX9U9hNxPq9oxc=" + }, + { + "mvn-path": "ring/ring-mock/0.4.0/ring-mock-0.4.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-I/fFTWW8mmul1f3XtJNX92RvkEd0idYFjLJdnMlXVJE=" + }, + { + "mvn-path": "rolling-stones/rolling-stones/1.0.3/rolling-stones-1.0.3.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-i4oCrGaiCCSWjFiJ4JYaCSViCuX3ME6eyclGBQz2Sck=" + }, + { + "mvn-path": "rolling-stones/rolling-stones/1.0.3/rolling-stones-1.0.3.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Rv0cpeJy2+7DSwTKboUhjHbPLqFO1wy3p/YM1UD7wZ4=" + }, + { + "mvn-path": "seesaw/seesaw/1.5.0/seesaw-1.5.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-yO/ICpBemxj9rjduXEw3QL5TIGtNzLWO+raIoZ5w/LU=" + }, + { + "mvn-path": "seesaw/seesaw/1.5.0/seesaw-1.5.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Zr57ZKhCO9jNfh4juQC7s86HfCbKn8KK/c2JkkyqZfU=" + }, + { + "mvn-path": "slingshot/slingshot/0.12.2/slingshot-0.12.2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-porCK/LqPNVM4023D9aYRNYx71SfZFDCeMMOb3nfY/M=" + }, + { + "mvn-path": "slingshot/slingshot/0.12.2/slingshot-0.12.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-SrxOK5ppxzvTc+gy0/AOWQZ4Q/+DUe/V7rsfOCTbnFE=" + }, + { + "mvn-path": "tailrecursion/cljs-priority-map/1.2.1/cljs-priority-map-1.2.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-f5Q0lkn/vOWhV3T2PLAffWmyZvmccslMic169l7ugi0=" + }, + { + "mvn-path": "tailrecursion/cljs-priority-map/1.2.1/cljs-priority-map-1.2.1.pom", + "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/", + "hash": "sha256-twS8BMUI6zeJ0p2vg5CvQ/7NY3YBfFJXmhPlBMubhD8=" + }, + { + "mvn-path": "tigris/tigris/0.1.2/tigris-0.1.2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-SapkjttsFOVwlaEbOR6u5gZXgyP7eXVfkjMaxjAPl6A=" + }, + { + "mvn-path": "tigris/tigris/0.1.2/tigris-0.1.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-H9VZA1l1INzUrnbmoz7/XjWmFUIrutKo7ZrDMqr75KA=" + }, + { + "mvn-path": "trptcolin/versioneer/0.1.1/versioneer-0.1.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-1rcXvKcIWT/bt195NSXtpPp72qNH5i2Fv8L0lp+xoAs=" + }, + { + "mvn-path": "trptcolin/versioneer/0.1.1/versioneer-0.1.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-0TIP4Oyo+xWhXj+lk1OP80Oy9JnsXe3QjJyfwWe8crg=" + } + ] +} diff --git a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org index fb4f57a12..af45c44eb 100644 --- a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org +++ b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org @@ -144,15 +144,18 @@ Used by Galicia and Galicia2. This file format is specific to ~conexp-clj~ and it's main purpose is to provide some idea of an unrestricted anonymous format for formal contexts. Essentially, -one leaves out in the Burmeister format the name of the context, and names of +one leaves out in the Burmeister format the name of the context, the names of the objects, and the names of the attributes. *** Example #+begin_src text A +3 +2 .X XX +.X #+end_src ** FCALGS @@ -163,3 +166,56 @@ XX 2 1 2 #+end_src +** Python Pandas +[[https://pandas.pydata.org/][Pandas]] is a python framework to manage tabular data structures and is +often used in data analysis. A common I/O format of conexp-clj and +pandas is CSV and Named-CSV. + +*** Example +context +#+begin_src text + | 1 2 +---+----- + a | . x + b | x x +#+end_src +Named-Binary-CSV format +#+begin_src text +objects,1,2 +a,0,1 +b,1,1 +#+end_src + +Conexp-clj I/O + +#+BEGIN_SRC clojure +(write-context :named-binary-csv context "path/to/context.csv") +(read-context "path/to/context.csv" :named-binary-csv) +#+END_SRC + +#+BEGIN_SRC python +context = pandas.read_table("path/to/context.csv",index_col=0,delimiter=",") +context.to_csv("path/to/context.csv") +#+END_SRC + +** JSON + +The detailed structure of the json format is given at [[../src/main/resources/schemas/context_schema_v1.0.json][context_schema_v1.0.json]]. + +*** Example +#+begin_src json +{ + "formal_context":[ + { + "object":"a", + "attributes":[2] + }, + { + "object":"b", + "attributes":[1,2] + } + ] +} +#+end_src + +There are also formats for lattices ([[../src/main/resources/schemas/lattice_schema_v1.0.json][lattice_schema_v1.0.json]]), implication sets ([[../src/main/resources/schemas/implications_schema_v1.0.json][implications_schema_v1.0.json]]) and an fca ([[../src/main/resources/schemas/fca_schema_v1.0.json][fca_schema_v1.0.json]]) that can contain all context, lattice and implication sets. diff --git a/doc/Concept-Lattices.org b/doc/Concept-Lattices.org index b8b34bb02..ecefa0671 100644 --- a/doc/Concept-Lattices.org +++ b/doc/Concept-Lattices.org @@ -155,6 +155,23 @@ layouts can be provided to ~draw-lattice~ as an additional argument :layout-fn inf-additive-layout) #+end_src +The layout supports annotation by valuation functions. These can be +provided by the ~:value-fn~ argument. The following example annotates +the extent size. + +#+begin_src clojure :results silent +(draw-lattice (concept-lattice (adiag-context 2)) + :layout-fn inf-additive-layout + :value-fn (comp count first)) +#+end_src + +The evaluations defined via ~:value-fn~ are displayed after the labels are +switched on. + +#+caption: Lattice Editor Example +[[./images/draw-lattice-02.png]] + Other interesting functions are ~draw-layout~, which implements the drawing of layouts, and ~draw-concept-lattice~, which draws the concept-lattice of a given -formal context. +formal context. With ~draw-poset~ and ~draw-protoconcepts~, Ordered Sets and +Protoconcepts can be drawn as well. diff --git a/doc/Getting-Started.org b/doc/Getting-Started.org index 5dff78a04..6173c63b2 100644 --- a/doc/Getting-Started.org +++ b/doc/Getting-Started.org @@ -59,6 +59,61 @@ This will give you a command prompt as in the previous case. If you want a more sophisticated repl, you may try [[https://github.com/clojure-emacs/cider][Emacs Cider]]. +** Running conexp-clj via nix + +Using the [[https://nixos.org/manual/nix/stable/][nix]] package manager, you can directly run ~conexp-clj~ without +installing it: + +#+begin_src shell :eval never +nix run github:tomhanika/conexp-clj +#+end_src + +Instead of running it directly, you can also just start a shell that +has ~conexp-clj~ in its path (again without installing it): + +#+begin_src shell :eval never +nix shell github:tomhanika/conexp-clj +#+end_src + +We also provide ~conexp-clj~ as a *package* in the [[https://nixos.wiki/wiki/Flakes][nix flake]], so you can +add it to, e.g., a system configuration or use it from within a +[[https://github.com/nix-community/home-manager/][home-manager]] configuration. First, add ~conexp-clj~ as a flake input: + +#+begin_src nix :eval never +{ + inputs = { + nixpkgs.url = "…"; + conexp-clj = { + url = "github:tomhanika/conexp-clj"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; +} +#+end_src + +Then, you can, e.g., add ~conexp-clj~ to a system configuration to have +it installed permanently, where ~system~ is the appropriate system +type, e.g., ~x86_64-linux~. + +#+begin_src nix :eval never + environment.systemPackages = [ conexp-clj.packages."system".conexp-clj ]; +#+end_src + +There is also an *overlay* than can be applied to ~nixpkgs~: + +#+begin_src nix :eval never + nixpkgs.overlays = [ conexp-clj.overlays.default ]; + environment.systemPackages = [ pkgs.conexp-clj ]; +#+end_src + +Lastly, for development on ~conexp-clj~, the ~nix~ flake provides a +*devshell* that has ~leiningen~ and ~clojure-lsp~ in its path. From the +source directory, run: + +#+begin_src shell :eval never +nix develop +#+end_src + ** Loading Code from a File Typing in long sequences of commands is tedious, and ~conexp-clj~ inherits from diff --git a/doc/IO.org b/doc/IO.org index e1c4e5f01..1eb2ce5e8 100644 --- a/doc/IO.org +++ b/doc/IO.org @@ -30,6 +30,8 @@ formal contexts on success. Supported formats for input are - Galicia (~:galicia~) - Anonymous Burmeister (~:anonymous-burmeister~) - GraphML (~:graphml~) +- Named Binary CSV (~:named-binary-csv~) Python Pandas compatible +- JSON (~:json~) See [[Common-FCA-File-Formats-for-Formal-Contexts.org][Common FCA File Formats for Formal Contexts]] for somewhat more details on those formats. @@ -76,3 +78,53 @@ See [[../testing-data/house-votes-84.data][house-votes-84.data]] and [[../testin If the following lines have an entry more then the first line, the first entry will become the name of the object. Otherwise, objects will be given arbitrary names. + + +** Store a complete FCA data study: context, lattice and implication sets + +The FCA data format contains the context and can contain the corresponding lattice +and several implication sets as well. The I/O functions are ~read-fca~ and ~write-fca~. The +standard format is the ~:json~ format. See [[../testing-data/digits-fca.json][digits-fca.json]] for an example +of the format. The json schema of the format is given at [[../../src/main/resources/schemas/fca_schema_v1.0.json][fca_schema_v1.0.json]]. + +The FCA json format can be read using + +#+begin_src clojure +(read-fca "testing-data/small-fca.json") +#+end_src + +or + +#+begin_src clojure +(read-fca "testing-data/small-fca.json" :json) +#+end_src + +The reading function returns a map containing the keys ~:context~, ~:lattice~ +(optional) and ~implication-sets~ (optional), e.g.: + +#+begin_src clojure +{:context + |1 2 + --+---- + a |. x + b |x x , + :lattice Lattice on 2 elements., + :implication-sets (((#{} ⟶ #{2}))) +} +#+end_src + +A context consists of objects, attributes and the incidence relations. +A concept lattice consists of the concepts and an order on those to display the corresponding +lattice. Further information on how to work with concept lattices is given at [[Concept-Lattices.org][Concept Lattices]]. +Implication sets are vectors of implications. Further information on implications can be found +at [[Implications.org][Implications]]. It is possible that an FCA data study contains more than one implication set. + +Writing an FCA to a file works with a map as well. Suppose you have given a context +~ctx~, a corresponding lattice ~lat~ and a set of implications ~impl~, e.g. the +canonical base of ~ctx~. Writing all information into one file works with + +#+begin_src clojure +(write-fca :json {:context ctx :lattice lat :implication-sets [impl]} "path/to/file.json") +#+end_src + +While the ~:context~ is always required, ~:lattice~ and ~:implication-sets~ are optional. diff --git a/doc/IncompleteContexts.org b/doc/IncompleteContexts.org new file mode 100644 index 000000000..fd2faaaaf --- /dev/null +++ b/doc/IncompleteContexts.org @@ -0,0 +1,72 @@ +#+property: header-args :wrap src text +#+property: header-args:text :eval never + +* Incomplete Contexts in ~conexp-clj~ + +Incomplete Contexts are used to represent contexts where some relations are unknown. +See papers by Burmeister and Holzer for an introduction. + +#+begin_src clojure :exports both + (def K (make-incomplete-context-from-matrix + [1 2 3 4] + [:a :b :c] + [0 1 "?" "?" "?" 0 1 1 0 "?" "?" 1])) + + K +#+end_src + +#+RESULTS: +#+begin_src text + |:a :b :c + --+--------- + 1 |. x ? + 2 |? ? . + 3 |x x . + 4 |? ? x +#+end_src + + +We can compute possible and certain derivations: +#+begin_src clojure :exports both +(possible-attribute-derivation K [:a]) +#+end_src + +#+RESULTS: +#+begin_src text +#{4 3 2} +#+end_src + +#+begin_src clojure :exports both +(certain-attribute-derivation K [:a]) +#+end_src + +#+RESULTS: +#+begin_src text +#{3} +#+end_src + +and we can test if an implication is satisfiable in an incomplete context: +#+begin_src clojure :exports both +(satisfiable? (make-implication [:a] [:b]) K) +#+end_src + +#+RESULTS: +#+begin_src text +true +#+end_src + +#+begin_src clojure :exports both +(satisfiable? (make-implication [:a] [:b :c]) K) +#+end_src + +#+RESULTS: +#+begin_src text +false +#+end_src + + +There are some convenience functions to convert between formal and incomplete contexts, e.g., ~to-incomplete-context~, ~to-formal-context~, ~incomplete-context->possible-incidences-context~, ~incomplete-context->certain-incidences-context~, and to draw the (certain/possible) concept lattice for an incomplete context, ~draw-certain-concept-lattice~, ~draw-possible-concept-lattice~. + +Also, there is support for attribute exploration with incomplete information. +Additionally there is support for attribute exploration with multiple experts that have compatible views (collaboration strategies) and incompatible views (shared implications). + diff --git a/doc/Protoconcepts.org b/doc/Protoconcepts.org new file mode 100644 index 000000000..86ba2edff --- /dev/null +++ b/doc/Protoconcepts.org @@ -0,0 +1,91 @@ +#+property: header-args :wrap src text +#+property: header-args:text :eval never + +* Protoconcepts in ~conexp-clj~ + +Protoconcepts are defined as $(A, B)$, $A \subseteq G$ and $B \subseteq M$ with +$A' = B''$ or $B' = A''$. + +** Computing Protoconcepts + +The protoconcepts of a given context can be computed. First, the respective +namespace needs to be loaded. +#+begin_src clojure :results silent +(use 'conexp.fca.protoconcepts) +#+end_src + +Let the context be +#+begin_src clojure :exports both +(def ctx + (make-context-from-matrix [1 2 3 4] + ['A 'B 'C] + [1 1 0 + 1 0 0 + 0 1 0 + 0 1 0])) +#+end_src + +#+RESULTS: +#+begin_src text + |A B C +--+------ +1 |x x . +2 |x . . +3 |. x . +4 |. x . +#+end_src + +The protoconcepts can be computed with the ~protoconcepts~ function. +#+begin_src clojure :exports both +(protoconcepts ctx) +#+end_src + +#+RESULTS: +#+begin_src clojure +#{[#{4 2} #{}] [#{1 3 2} #{}] [#{1 2} #{A}] [#{1} #{A B}] + [#{} #{B C}] [#{} #{C}] [#{1 4 2} #{}] [#{4} #{B}] [#{1 4 3 2} #{}] + [#{4 3 2} #{}] [#{1 3} #{B}] [#{3 2} #{}] [#{1 4 3} #{B}] + [#{3} #{B}] [#{4 3} #{B}] [#{2} #{A}] [#{1 4} #{B}] [#{} #{A C}] + [#{} #{A B C}]} +#+end_src + +To compute the protoconcepts as an ordered set, the ~protoconcepts-order~ function can be used. +#+begin_src clojure :exports both +(protoconcepts-order ctx) +#+end_src + +#+RESULTS: +#+begin_src clojure +Protoconcepts on 19 elements. +#+end_src + +** Draw Protoconcepts + +Drawing must be enabled via +#+begin_src clojure :results silent +(use 'conexp.gui.draw) +#+end_src + +To draw the ordered protoconcepts of a context, the ~draw-protoconcepts~ function can be used. +#+begin_src clojure :results silent +(draw-protoconcepts (protoconcepts-order ctx)) +#+end_src + +The protoconcepts graph is shown in an additional window. + +#+caption: Protoconcept example +[[./images/protoconcept-lattice.png]] + +Like for [lattices](./Concept-Lattices.org), the layout function can be specified for +protoconcepts. + +#+begin_src clojure :results silent +(draw-protoconcepts (protoconcepts-order ctx) + :layout-fn conexp.layouts.dim-draw/dim-draw-layout) +#+end_src + +#+caption: Protoconcept example with DimDraw layout +[[./images/protoconcept-lattice-dimdraw.png]] + +Notice that the protoconcept orders do not necessarily are a lattice. As many +of the value functions only work on lattices, they cannot be applied to protoconcepts. diff --git a/doc/Triadic-Exploration.org b/doc/Triadic-Exploration.org new file mode 100644 index 000000000..8b719c173 --- /dev/null +++ b/doc/Triadic-Exploration.org @@ -0,0 +1,151 @@ +The triadic-exploration namespace provides a range of functionality to deal with the exploration of triadic contexts. + +A tradic context $(G,M,B,Y)$ can be represented as a collection of contexts $\{\mathbb{K}_1,\mathbb{K}_2,\ldots,\mathbb{K}_i\}$ on the same sets of objects $G$ and attributes $M$. + +The following example shows the situation of public transport at the train station Bf. Wilhelmshöhe with direction to the city center in Kassel. +This example is part of the paper [[https://arxiv.org/abs/2102.02654][Triadic Exploration and Exploration with Multiple Experts]], see https://arxiv.org/abs/2102.02654. +From Bf. Wilhelmshöhe you can travel by one of four bus lines (52, 55, 100 and 500), four tram lines (1, 3, 4 and 7), one night tram (N3) and one regional tram (RT5) to the city center. +These are the objects of our context. +The buses and trams leave the station at different times throughout each day of the week. +The attributes of our context are the aggregated the leave-times, more specifically, we have split each day in five distinct time slots: early morning from 4:00 to 7:00, working hours from 7:00 to 19:00, evening from 19:00 to 21:00, late evening from 21:00 to 24:00 and night from 0:00 to 4:00. +A bus or tram has a cross the formal context if there is at least one leave-time in the time slot. +Furthermore, we have split the days of the week into Monday to Friday, Saturday and Sunday as conditions. + +The respective contexts are part of the triadic-exploration namespace. + + +First load the respective namespace to deal with triadic contexts and to use the drawing functionality provided by conexp-clj. +#+begin_src clojure :eval never +(use 'conexp.fca.triadic-exploration) +(use 'conexp.gui.draw) +#+end_src + + +To view the context family from the example use +#+begin_src clojure :eval never +cxt-family-paper + ;; => + ;; {:Mo-Fr + ;; |:early-morning :evening :late-evening :night :working-hours + ;; -----+------------------------------------------------------------ + ;; :1 |x x x . x + ;; :100 |x x x . x + ;; :3 |x x x . x + ;; :4 |x x x . x + ;; :500 |x x x x x + ;; :52 |x x x . x + ;; :55 |x . . . x + ;; :7 |x x . . x + ;; :N3 |. . . . . + ;; :RT5 |x x x . x + ;; , + ;; :Sat + ;; |:early-morning :evening :late-evening :night :working-hours + ;; -----+------------------------------------------------------------ + ;; :1 |. x x . x + ;; :100 |x x x . x + ;; :3 |x x x . x + ;; :4 |x x x . x + ;; :500 |. x x . x + ;; :52 |x . . . x + ;; :55 |. . . . . + ;; :7 |. . . . x + ;; :N3 |. . . x . + ;; :RT5 |x x x . x + ;; , + ;; :Sun + ;; |:early-morning :evening :late-evening :night :working-hours + ;; -----+------------------------------------------------------------ + ;; :1 |. x x . x + ;; :100 |x x x . x + ;; :3 |x x x . x + ;; :4 |x x x . x + ;; :500 |. x x . x + ;; :52 |. . . . . + ;; :55 |. . . . . + ;; :7 |. . . . . + ;; :N3 |. . . x . + ;; :RT5 |. x x . x + ;; } +#+end_src + +Such a family of formal contexts can be transformed into a triadic context using +#+begin_src clojure :eval never +(def tcxt-paper (context-family->triadic-context cxt-family-paper)) +tcxt-paper + ;; {:objects #{:RT5 :52 :500 :4 :100 :7 :55 :1 :3 :N3}, + ;; :attributes + ;; #{:working-hours :late-evening :early-morning :night :evening}, + ;; :conditions (:Mo-Fr :Sat :Sun), + ;; :incidence + ;; #{[:1 :early-morning :Mo-Fr] [:100 :evening :Sat] + ;; [:RT5 :working-hours :Mo-Fr] [:55 :early-morning :Mo-Fr] + ;; [:7 :working-hours :Mo-Fr] [:100 :early-morning :Mo-Fr] + ;; [:500 :working-hours :Sun] [:3 :working-hours :Sat] + ;; [:500 :evening :Mo-Fr] [:4 :late-evening :Mo-Fr] + ;; [:1 :late-evening :Sat] [:3 :evening :Sun] + ;; [:500 :late-evening :Mo-Fr] [:RT5 :working-hours :Sat] + ;; [:100 :early-morning :Sun] [:100 :working-hours :Sun] + ;; [:3 :working-hours :Sun] [:RT5 :late-evening :Sat] + ;; [:100 :working-hours :Sat] [:1 :evening :Mo-Fr] + ;; [:500 :late-evening :Sun] [:3 :late-evening :Sun] + ;; [:100 :evening :Sun] [:4 :evening :Mo-Fr] + ;; [:500 :working-hours :Sat] [:1 :evening :Sat] + ;; [:100 :early-morning :Sat] [:3 :evening :Sat] + ;; [:1 :late-evening :Mo-Fr] [:500 :evening :Sat] + ;; [:RT5 :early-morning :Sat] [:500 :night :Mo-Fr] + ;; [:500 :evening :Sun] [:52 :working-hours :Mo-Fr] + ;; [:RT5 :evening :Sun] [:100 :late-evening :Sat] + ;; [:4 :early-morning :Mo-Fr] [:3 :early-morning :Sat] + ;; [:4 :working-hours :Mo-Fr] [:1 :evening :Sun] + ;; [:52 :early-morning :Sat] [:52 :early-morning :Mo-Fr] + ;; [:3 :late-evening :Sat] [:7 :early-morning :Mo-Fr] + ;; [:100 :late-evening :Sun] [:4 :evening :Sat] + ;; [:4 :working-hours :Sat] [:3 :early-morning :Sun] + ;; [:52 :late-evening :Mo-Fr] [:RT5 :working-hours :Sun] + ;; [:100 :late-evening :Mo-Fr] [:RT5 :late-evening :Sun] + ;; [:55 :working-hours :Mo-Fr] [:7 :evening :Mo-Fr] + ;; [:100 :evening :Mo-Fr] [:1 :working-hours :Sun] + ;; [:3 :early-morning :Mo-Fr] [:52 :evening :Mo-Fr] + ;; [:4 :late-evening :Sun] [:4 :early-morning :Sat] [:N3 :night :Sun] + ;; [:1 :late-evening :Sun] [:RT5 :late-evening :Mo-Fr] + ;; [:1 :working-hours :Sat] [:3 :evening :Mo-Fr] + ;; [:500 :working-hours :Mo-Fr] [:RT5 :evening :Mo-Fr] + ;; [:4 :working-hours :Sun] [:4 :late-evening :Sat] + ;; [:7 :working-hours :Sat] [:4 :early-morning :Sun] + ;; [:RT5 :early-morning :Mo-Fr] [:N3 :night :Sat] + ;; [:RT5 :evening :Sat] [:100 :working-hours :Mo-Fr] + ;; [:4 :evening :Sun] [:500 :late-evening :Sat] + ;; [:500 :early-morning :Mo-Fr] [:3 :working-hours :Mo-Fr] + ;; [:3 :late-evening :Mo-Fr] [:52 :working-hours :Sat] + ;; [:1 :working-hours :Mo-Fr]}} +#+end_src + + +To compute all conditional theories use +#+begin_src clojure :eval never +(def all-conditional-implications (compute-all-conditional-theories tcxt-paper)) +all-conditional-implications + ;; |:Mo-Fr :Sat :Sun + ;; -----------------------------------------------------------------------+----------------- + ;; (#{:early-morning} ⟶ #{:working-hours}) |x x x + ;; (#{:working-hours :night} ⟶ #{:late-evening :early-morning :evening}) |x x x + ;; (#{} ⟶ #{:working-hours :late-evening :evening}) |. . . + ;; (#{:late-evening} ⟶ #{:working-hours :evening}) |x x x + ;; (#{} ⟶ #{:working-hours :late-evening :early-morning :night :evening}) |. . . + ;; (#{:working-hours} ⟶ #{:early-morning}) |x . . + ;; (#{:night} ⟶ #{:working-hours :late-evening :early-morning :evening}) |x . . + ;; (#{:evening} ⟶ #{:working-hours :late-evening}) |. x x + ;; (#{:evening} ⟶ #{:working-hours}) |x x x + ;; (#{:working-hours :evening} ⟶ #{:early-morning}) |x . . + ;; (#{:working-hours :late-evening :early-morning :evening} ⟶ #{:night}) |. . . + ;; (#{:working-hours} ⟶ #{:evening}) |. . x +#+end_src + + +And to draw the corresponding concept lattice to see which implications hold under which conditions use +#+begin_src clojure :eval never +(draw-concept-lattice all-conditional-implications) +#+end_src + +[[./images/concept-lattice-conditional-theories.png]] diff --git a/doc/code/trace-context.clj b/doc/code/trace-context.clj index 69b22cde6..78f647df6 100644 --- a/doc/code/trace-context.clj +++ b/doc/code/trace-context.clj @@ -40,7 +40,7 @@ layout (standard-layout (concept-lattice trace-ctx)), ann (concept-lattice-annotation layout), label #(apply str (interpose ", " %))] - (make-layout (lattice layout) + (make-layout (poset layout) (positions layout) (connections layout) (fn [x] diff --git a/doc/images/concept-lattice-conditional-theories.png b/doc/images/concept-lattice-conditional-theories.png new file mode 100644 index 0000000000000000000000000000000000000000..8f1cd465ee381e83ddd1a4ce4e7d85e82bd54468 GIT binary patch literal 29629 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 V*ZeHE`4;3@()EpYa&{aE{1*X$*?#~4 literal 0 HcmV?d00001 diff --git a/doc/images/draw-lattice-02.png b/doc/images/draw-lattice-02.png new file mode 100644 index 0000000000000000000000000000000000000000..1d8e1f68bc5bc50322be15bbbcef78f9825ef76d GIT binary patch literal 53429 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

y%f6ae3fEGszu2aIuJ-TUv6>NWYbw*=D8 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 z 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

wxcE(xy&E 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 QF5t&%>Tl29cIfQ?0f$RcegFUf literal 0 HcmV?d00001 diff --git a/doc/images/protoconcept-lattice.png b/doc/images/protoconcept-lattice.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c8507526851d72af6ddde678b2126f1c8775d4 GIT binary patch literal 92565 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≪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 #'handler - (wrap-reload) - (wrap-json-body {:keywords? true}) - (wrap-json-response {:pretty true})) - (-> handler - (wrap-json-body {:keywords? true}) - (wrap-json-response {:pretty false})))) + (wrap-reload) + (wrap-cors :access-control-allow-origin [#".+"] + :access-control-allow-methods [:get :post] + :access-control-allow-headers ["Content-Type" "Authorization"]) + (wrap-json-body {:keywords? true}) + (wrap-json-response {:pretty true})) + (-> handler + (wrap-cors :access-control-allow-origin [#".+"] + :access-control-allow-methods [:get :post] + :access-control-allow-headers ["Content-Type" "Authorization"]) + (wrap-json-body {:keywords? true}) + (wrap-json-response {:pretty false})))) (defonce server (atom nil)) diff --git a/src/main/clojure/conexp/api/handler.clj b/src/main/clojure/conexp/api/handler.clj index 71606ba97..7016bf286 100644 --- a/src/main/clojure/conexp/api/handler.clj +++ b/src/main/clojure/conexp/api/handler.clj @@ -30,32 +30,20 @@ (condp = (:type data) ;; remove colons from map "map" (if (some? raw) - (into {} (for [[k v] raw] [(edn/read-string (name k)) v]))) - "context" (make-context - (:objects raw) - (:attributes raw) - (:incidence raw)) + (into {} (for [[k v] raw] [(edn/read-string (name k)) + (if (= (type v) clojure.lang.PersistentArrayMap) + (read-data v) + v)]))) + "context" (json->ctx raw) ;; casting its content to char-array is the same as using the filename "context_file" (read-context (char-array raw)) - "mv_context" (make-mv-context - (:objects raw) - (:attributes raw) - (read-data {:type "map" :data (:incidence raw)})) + "mv_context" (json->mv-context raw) "mv_context_file" (read-mv-context (char-array raw)) - "lattice" (make-lattice - (:nodes raw) - (:edges raw)) - "implication" (apply make-implication raw) - "implication_set" (map #(apply make-implication %) raw) - "layout" (apply make-layout - (filter - some? - (list - (read-data {:type "lattice" :data (:lattice raw)}) - (read-data {:type "map" :data (:positions raw)}) - (:connections raw) - (read-data {:type "map" :data (:upper-labels raw)}) - (read-data {:type "map" :data (:lower-labels raw)})))) + "lattice" (json->lattice raw) + "implication" (json->implication raw) + "implication_set" (json->implications raw) + "layout" (json->layout raw) + "method" (resolve (symbol raw)) raw))) (defn write-data @@ -67,23 +55,13 @@ (if (and (coll? data)(not (map? data))) (mapv write-data data) (condp instance? data - Formal-Context {:objects (objects data) - :attributes (attributes data) - :incidence (incidence data)} - Many-Valued-Context {:objects (objects data) - :attributes (attributes data) - :incidence (incidence data)} - Lattice {:nodes (base-set data) - :edges (set-of [x y] - [x (base-set data) - y (base-set data) - :when ((order data) [x y])])} - Implication [(premise data)(conclusion data)] - Layout {:lattice (write-data (.lattice data)) - :positions (.positions data) - :connections (.connections data) - :upper-labels (.upper-labels data) - :lower-labels (.lower-labels data)} + Formal-Context (ctx->json data) + Many-Valued-Context (mv-context->json data) + Lattice (lattice->json data) + Implication (implication->json data) + Layout (layout->json data) + clojure.lang.PersistentArrayMap (into {} (mapv #(vector (key %) + (write-data (val %))) data)) data))) ;;; Process functions @@ -172,6 +150,7 @@ data (into {} (for [[k v] body :when (not (some #{(:type v)} fn-types))] [k (read-data v)])) + ;; each name from function types is run as an acutal function results (process-functions (filter (fn [a](some #{(:type (val a))} fn-types)) body) diff --git a/src/main/clojure/conexp/base.clj b/src/main/clojure/conexp/base.clj index 64d9b0544..eb7a25b39 100644 --- a/src/main/clojure/conexp/base.clj +++ b/src/main/clojure/conexp/base.clj @@ -803,7 +803,6 @@ metadata (as provided by def) merged into the metadata of the original." [base-set] (map set (comb/subsets (seq base-set)))) - ;;; Next Closure (defn lectic-<_i @@ -1162,5 +1161,11 @@ metadata (as provided by def) merged into the metadata of the original." (search (list [edges #{} elements 'beginning]))))) ;;; - +(defn lift-map + "This set lifts a map m:A->B to the power sets, m:P(A)->P(B)." + [m] + (let [make-set (fn [c] (if (set? c) c #{c})) + reducer-fn (fn [c d] (union (make-set c) (make-set d)))] + (fn [A] (reduce reducer-fn #{} (map m A))))) +;;; nil diff --git a/src/main/clojure/conexp/fca/concept_transform.clj b/src/main/clojure/conexp/fca/concept_transform.clj new file mode 100644 index 000000000..e127ac323 --- /dev/null +++ b/src/main/clojure/conexp/fca/concept_transform.clj @@ -0,0 +1,103 @@ +;; 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.concept-transform + (:require [conexp.base :refer :all] + [conexp.fca.contexts :refer :all] + [conexp.fca.cover :refer :all])) + + + +;;; transformer algorithms using the cover structure + +(defn transform-bv-cover + "Transforms the concept lattice of ctx1 to that of ctx2 using the + algorithm presented in 'Knowledge Cores in Large Formal Contexts' + The methods input are the two contexts and the concept lattice of + ctx1 as cover relation generated by cover.clj. For ctx1 and ctx2 it + required that there exists a super context ctx such that ctx1 and + ctx2 are induced subcontexts of ctx. + + This method is useful for larger contexts ctx1 and ctx2 which have + many objects and attributes in common." + [ctx1 ctx2 bv1] + (let [;; update the set of attributes of the ctx and bv first + shared-attributes (intersection (attributes ctx2) (attributes ctx1)) + remove-ctx1-attributes-bv (attribute-deletion-cover bv1 ctx1 (difference (attributes ctx1) shared-attributes)) + ctx2-only-attributes (difference (attributes ctx2) (attributes ctx1)) + attr-intermediate-ctx (make-context (objects ctx1) (attributes ctx2) + (fn [a b] (if (contains? (objects ctx2) a) + ((incidence ctx2) [a b]) + ((incidence ctx1) [a b])))) + insert-ctx2-attributes-bv (attribute-insertion-cover remove-ctx1-attributes-bv attr-intermediate-ctx ctx2-only-attributes) + ;; To update all objects, we update the attributes of the dual + ;; context + dual-bv (dual-concept-cover insert-ctx2-attributes-bv) + shared-objects (intersection (objects ctx1) (objects ctx2)) + dual-ctx (dual-context attr-intermediate-ctx) + remove-ctx1-objects-bv (attribute-deletion-cover + dual-bv dual-ctx + (difference (objects ctx1) shared-objects)) + ctx2-only-objects (difference (objects ctx2) (objects ctx1)) + insert-ctx2-objects-bv (attribute-insertion-cover remove-ctx1-objects-bv (dual-context ctx2) ctx2-only-objects)] + (dual-concept-cover insert-ctx2-objects-bv))) + +(defn transform-bv-subctx-cover + "Transforms the concept lattice of ctx1 to that of ctx2 using the + algorithm presented in 'Knowledge Cores in Large Formal Contexts' + The methods input are the two contexts and the concept lattice of + ctx1 as cover relation generated by cover.clj. For ctx1 and ctx2 it + required that ctx2 is a subcontext of ctx1 + + This method is useful for larger contexts ctx1 and ctx2 which have + many objects and attributes in common." + [ctx1 ctx2 bv1] + (let [;; update the set of attributes of the ctx and bv first + shared-attributes (attributes ctx2) + remove-ctx1-attributes-bv (attribute-intersection-cover bv1 shared-attributes) + + ;; To update all objects, we update the attributes of the dual + ;; context + dual-bv (dual-concept-cover remove-ctx1-attributes-bv) + shared-objects (objects ctx2) + remove-ctx1-objects-bv (attribute-intersection-cover dual-bv shared-objects)] + (dual-concept-cover remove-ctx1-objects-bv))) + +(defn transform-bv-intents-cores-cover + "Transforms the intent lattice of ctx1 to that of ctx2 using the + algorithm presented in 'Knowledge Cores in Large Formal Contexts' + The methods input are the two contexts and the concept lattice of + ctx1 as cover relation generated by cover.clj. For ctx1 and ctx2 it + required that ctx2 is a pkcore of ctx1. + + This method is useful for larger contexts ctx1 and ctx2 which have + many objects and attributes in common." + [ctx1 core bv1 p] + (cover-reducer ctx1 core p)) + +;;; general transformer + +(defn cover-to-concepts + "This method converts the cover structure back to the list of concepts." + [cover] + (map #(vec [% (get-in cover [% :extent])]) (keys cover))) + +(defn transform-bv + "Transforms the set of concepts of ctx1 to that of ctx2 using the + algorithm presented in 'Knowledge Cores in Large Formal Contexts' + The methods input are the two contexts and the set of concepts + ctx1. For ctx1 and ctx2 it required that there exists a super + context ctx such that ctx1 and ctx2 are induced subcontexts of ctx. + + This method is useful for larger contexts ctx1 and ctx2 which have + many objects and attributes in common." + [ctx1 ctx2 concepts1] + (let [bv1 (generate-concept-cover concepts1) + bv2 (dual-concept-cover (transform-bv-cover ctx1 ctx2 bv1))] + (cover-to-concepts bv2))) + diff --git a/src/main/clojure/conexp/fca/contexts.clj b/src/main/clojure/conexp/fca/contexts.clj index 184e2e1ce..36e6b43ed 100644 --- a/src/main/clojure/conexp/fca/contexts.clj +++ b/src/main/clojure/conexp/fca/contexts.clj @@ -767,7 +767,11 @@ (defn direct-upper-concepts "Computes the set of direct upper neighbours of the concept [A B] in - the concept lattice of ctx. Uses Lindig's Algorithm for that." + the concept lattice of ctx. Uses Lindig's Algorithm for that. + + Lindig, C.: Fast concept analysis. In: Working with Conceptual + Structures – Contributions to ICCS 2000. pp. 152--161. Shaker + Verlag (2000). " [ctx [A B]] (assert (concept? ctx [A B]) "Given pair must a concept in the given context") @@ -791,7 +795,11 @@ (defn direct-lower-concepts "Computes the set of direct upper neighbours of the concept [A B] in - the concept lattice of ctx. Uses Lindig's Algorithm for that." + the concept lattice of ctx. Uses Lindig's Algorithm for that. + + Lindig, C.: Fast concept analysis. In: Working with Conceptual + Structures – Contributions to ICCS 2000. pp. 152--161. Shaker + Verlag (2000). " [ctx [A B]] (assert (concept? ctx [A B]) "Given pair must a concept in the given context") @@ -856,6 +864,51 @@ (for [[G-H N] (concepts compatible-ctx)] (make-context-nc (difference (objects ctx) G-H) N (incidence ctx))))) +;;; logical derivations +; syntax checker +(defn- valid-formula-level? + "Checks that every level of the formula has valid syntax." + [ctx kind level] + (let [ops (set (filter keyword? level))] + (and (= 1 (count ops)) + (if (= :not (first ops)) + (and (= 2 (count level)) + (= :not (first level))) ; (:not attr) + (and (-> level count (> 0)) (-> level count even? not) + (->> level rest (take-nth 2) set (= ops)) ; every uneven is the operator + (->> level (take-nth 2) (every? #(or (vector? %) (contains? (kind ctx) %))))))))); every even is either a formula or attributes + +(defn formula-syntax-checker + "Syntex checker for propositional logic. Expected Format is a list [A :or [B :and C] [:not D]], where A,B,C,D are Attributes or Objects of ctx. + Kind is either the 'objects' or 'attributes' function." + [formula ctx kind] + (if (-> formula vector? not) false + (loop [level [formula]] + (if (empty? level) true + (if (every? (partial valid-formula-level? ctx kind) level) + (recur (reduce into [] (map (partial filter vector?) level)) ) + false))))) +; derivations +(defn logical-object-derivation + "Returns the derivation of a propositional formula of objects. Expected Format is a list [A :or [B :and C] [:not D]], where A,B,C,D are Objects of ctx." + [ctx formula] + (assert (formula-syntax-checker formula ctx objects) "Expected Format is a list [A :or [B :and C] [:not D]], where A,B,C,D are Objects of the context.") + (let [incidence-ops {:or union :and intersection :not #(difference (attributes ctx) %)} + op (->> formula (filter keyword?) first (get incidence-ops))] + (let [[f & other] (map #(if (list? %) % (object-derivation ctx #{%})) (filter (comp not keyword?) formula))] + (reduce (fn [a b] (op a (if (list? b) (logical-object-derivation ctx b) b))) + (-> (if (list? f) (logical-object-derivation ctx f) f) op) other)))) + +(defn logical-attribute-derivation + "Returns the derivation of a propositional formula of objects. Expected Format is a list [A :or [B :and C] [:not D]], where A,B,C,D are Attributes of ctx." + [ctx formula] + (assert (formula-syntax-checker formula ctx attributes) "Expected Format is a list [A :or [B :and C] [:not D]], where A,B,C,D are Attributes of the context.") + (let [incidence-ops {:or union :and intersection :not #(difference (objects ctx) %)} + op (->> formula (filter keyword?) first (get incidence-ops))] + (let [[f & other] (map #(if (list? %) % (attribute-derivation ctx #{%})) (filter (comp not keyword?) formula))] + (reduce (fn [a b] (op a (if (list? b) (logical-attribute-derivation ctx b) b))) + (-> (if (list? f) (logical-attribute-derivation ctx f) f) op) other)))) + ;;; true diff --git a/src/main/clojure/conexp/fca/cover.clj b/src/main/clojure/conexp/fca/cover.clj index f23f4a8bc..5907d801b 100644 --- a/src/main/clojure/conexp/fca/cover.clj +++ b/src/main/clojure/conexp/fca/cover.clj @@ -7,21 +7,25 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.fca.cover - (:require [conexp.base :exclude [next-closed-set]] + (:require [conexp.base :refer :all + :exclude [next-closed-set]] [conexp.fca.contexts :refer :all] [conexp.fca.fast :refer - [next-closed-set to-hashset to-binary-context - bitwise-attribute-derivation forall-in-bitset - bitwise-object-derivation to-binary-matrix + [next-intent-async + next-closed-set + to-hashset to-binary-context + bitwise-attribute-derivation + bitwise-object-derivation + to-binary-matrix bitwise-context-attribute-closure]] - [clojure.core.reducers :as r] - [clojure.set :refer :all]) + [clojure.core.reducers :as r] + [clojure.core.async :refer [! concepts :fin) + (do + (r/>! concepts (to-hashset attr-order bin-next)) + (recur (next-intent-iterator bin-ctx bin-next))))) + concepts)) + ([ctx] + (next-intent-async ctx #{}))) + +(defn next-extent-async +"This method computes all closed sets starting with 'start (inclusive + start) using next-closure. All closed sets are computed asynchron + and are put into the return channel. The used lectic order has + 'start as lowest elements, such that all computed closed sets + contain at least one element of 'start. + (Read closed sets with lattice - -(defn lattice->graph - "Converts a lattice to a directed graph. +(defn poset->graph + "Converts an ordered set to a directed graph. For concepts u,v, there will be an edge u->v iff v <= u. (This implies that the only loops will be u->u for all u.)" - [lat] - (make-digraph-from-condition (alg/base-set lat) (alg/order lat))) + [poset] + (make-digraph-from-condition (alg/base-set poset) (alg/order poset))) + +(defalias lattice->graph poset->graph) (defn graph->lattice-nc "Converts a directed graph to a lattice. @@ -37,9 +38,9 @@ "Given a set and a relation, generates a graph of comparable elements. For elements u,v, there will be an edge u<->v iff (u,v) or (v,u) in relation. Note: If the relation is reflexive, u<->u for all u in the set." - ([lattice] (comparability - (alg/base-set lattice) - (alg/order lattice))) + ([poset] (comparability + (alg/base-set poset) + (alg/order poset))) ([base-set relation] (make-graph-from-condition base-set relation))) @@ -48,9 +49,9 @@ For elements u,v, there will be an edge u<->v iff neither (u,v) nor (v,u) in relation. Note: If the relation not reflexive, u<->u for all u in the set." - ([lattice] (co-comparability - (alg/base-set lattice) - (alg/order lattice))) + ([poset] (co-comparability + (alg/base-set poset) + (alg/order poset))) ([base-set relation] (make-graph-from-condition base-set #(and (not (relation %1 %2)) (not (relation %2 %1)))))) diff --git a/src/main/clojure/conexp/fca/incomplete_contexts/conexp_interop.clj b/src/main/clojure/conexp/fca/incomplete_contexts/conexp_interop.clj new file mode 100644 index 000000000..811b72aac --- /dev/null +++ b/src/main/clojure/conexp/fca/incomplete_contexts/conexp_interop.clj @@ -0,0 +1,83 @@ +(ns conexp.fca.incomplete-contexts.conexp-interop + (:require [conexp.fca.contexts :as cxt] + [conexp.fca.implications :as impls] + [conexp.base :refer [cross-product defalias]] + [conexp.fca.incomplete-contexts.incomplete-contexts :as icxt] + )) + + +(defn incomplete-context-from-formal-context + "convert a formal context to an incomplete context" + [formal-context] + (let [objs (cxt/objects formal-context) + atts (cxt/attributes formal-context) + inz (cxt/incidence formal-context) + fn (fn [g m inz] (if (contains? inz [g m]) icxt/known-true icxt/known-false)) + new-inz (loop [hash (transient {}) + items (cross-product objs atts)] + (if (empty? items) + (persistent! hash) + (let [[g m] (first items)] + (recur (if (and (contains? objs g) + (contains? atts m)) + (assoc! hash [g m] (fn g m inz)) + hash) + (rest items)))))] + (icxt/make-incomplete-context objs atts new-inz))) + +(defalias formal-context->incomplete-context incomplete-context-from-formal-context) + +(defn formal-context-from-incomplete-context + "" + [partial-context] + {:pre [(empty? (filter #(= (second %) icxt/unknown) (icxt/incidence partial-context)))]} + (let [objs (icxt/objects partial-context) + atts (icxt/attributes partial-context) + inz (icxt/incidence partial-context) + new-inz (->> inz + (filter #(= (second %) icxt/known-true)) + (map #(first %)) + (into #{}))] + (cxt/make-context objs atts new-inz))) + +(defalias incomplete-context->formal-context formal-context-from-incomplete-context) + + +(defn incomplete-context->certain-incidences-context + "Given an incomplete context (G,M,V,I) return the formal context (G,M,J) where (g,m) in J iff I(g,m) = x" + [cxt] + (let [objs (icxt/objects cxt) + atts (icxt/attributes cxt)] + (cxt/make-context objs atts (map first (icxt/true-incidence cxt))))) + +(defn incomplete-context->possible-incidences-context + "Given an incomplete context (G,M,V,I) return the formal context (G,M,J) where (g,m) in J iff I(g,m) in {x,?}" + [cxt] + (let [objs (icxt/objects cxt) + atts (icxt/attributes cxt)] + (cxt/make-context objs atts (map first (concat (icxt/true-incidence cxt) + (icxt/unknown-incidence-set cxt)))))) + + +(defn to-incomplete-context + [cxt] + (cond + (satisfies? icxt/Incomplete-Context-Protocol cxt) + cxt + (satisfies? cxt/Context cxt) + (formal-context->incomplete-context cxt) + true + (throw (Exception. "input was neither an incomplete context nor a formal context"))) + ) + +(defn to-formal-context + [cxt] + (cond + (satisfies? icxt/Incomplete-Context-Protocol cxt) + (incomplete-context->formal-context cxt) + (satisfies? cxt/Context cxt) + cxt + true + (throw (Exception. "input was neither an incomplete context nor a formal context"))) + ) + diff --git a/src/main/clojure/conexp/fca/incomplete_contexts/draw.clj b/src/main/clojure/conexp/fca/incomplete_contexts/draw.clj new file mode 100644 index 000000000..96aab5aa6 --- /dev/null +++ b/src/main/clojure/conexp/fca/incomplete_contexts/draw.clj @@ -0,0 +1,50 @@ +(ns conexp.fca.incomplete-contexts.draw + (:gen-class) + (:require [clojure.set :refer :all] + [conexp.fca.contexts :as cxts :refer [Context make-context]] + [conexp.io.layouts :refer :all] + [conexp.fca.lattices :refer [concept-lattice]] + [conexp.gui.draw :as draw] + [conexp.layouts.dim-draw :refer :all] + [conexp.fca.incomplete-contexts.incomplete-contexts :refer :all] + [conexp.fca.incomplete-contexts.conexp-interop :refer :all] +)) + + +(defn draw-concept-lattice + "draw concept lattice of a formal or complete incomplete context" + [cxt] + (cond + (satisfies? Context cxt) + (draw-concept-lattice cxt) + (and (satisfies? Incomplete-Context-Protocol cxt) + (is-complete-incomplete-context? cxt)) + (draw/draw-concept-lattice (incomplete-context->formal-context cxt)) + true + (throw (Exception. "input is neither a formal context nor a complete incomplete context")) + )) + +(defn draw-certain-concept-lattice + "draw the concept lattice of an incomplete context where all '?' are replaced by '.'" + [cxt] + (cond + (satisfies? Context cxt) + (draw-concept-lattice cxt) + (satisfies? Incomplete-Context-Protocol cxt) + (draw/draw-concept-lattice (incomplete-context->certain-incidences-context cxt)) + true + (throw (Exception. "input is neither a formal context nor an incomplete context")) + )) + + +(defn draw-possible-concept-lattice + "draw the concept lattice of an incomplete context where all '?' are replaced by 'x'" + [cxt] + (cond + (satisfies? Context cxt) + (draw-concept-lattice cxt) + (satisfies? Incomplete-Context-Protocol cxt) + (draw/draw-concept-lattice (incomplete-context->possible-incidences-context cxt)) + true + (throw (Exception. "input is neither a formal context nor an incomplete context")) + )) diff --git a/src/main/clojure/conexp/fca/incomplete_contexts/experts.clj b/src/main/clojure/conexp/fca/incomplete_contexts/experts.clj new file mode 100644 index 000000000..020fbf10f --- /dev/null +++ b/src/main/clojure/conexp/fca/incomplete_contexts/experts.clj @@ -0,0 +1,524 @@ +(ns conexp.fca.incomplete-contexts.experts + (:require [clojure.set :refer [subset? intersection difference union]] + [conexp.fca.implications :refer :all] + [conexp.base :refer [with-str-out cross-product hash-combine-hash defalias]] + [conexp.fca.incomplete-contexts.incomplete-contexts :refer :all] + [conexp.fca.incomplete-contexts.incomplete-contexts-exploration :refer :all])) + + + +;;;;;;;; Define Expert and build a handler +(defprotocol Expert-Knowledge + (known-implications [this]) + (known-examples [this])) + +(defprotocol Expert-Interactions + (valid-implication? [this implication]) + (counterexample [this implication]) + (unknown-for? [this implication])) + +(defprotocol Expert-Answer + (holds-in-view? [this implication]) + (holds-in-view?-inc-counter [this implication]) + (extended-holds-in-view? [this implication]) + (extended-holds-in-view?-inc-counter [this implication]) + (interaction-counter [this]) + (inc-interaction-counter [this]) + (reset-interaction-counter [this]) + ) + +(declare find-counterexample) +(declare find-counterexamples) +(defrecord Expert [known-implications known-examples name] + Object + #_(toString [this] (str "" name)) + (toString [this] (str "E_" name)) + + ;; TODO: implement equality test (test implications and cxt) + + Expert-Knowledge + (known-implications [expert] known-implications) + (known-examples [expert] known-examples) + + ;;; This is used for the expert handlers and used to fit the answers to the attribute exploration with incomplete information + Expert-Interactions + (valid-implication? [expert implication] (cond + ;(follows? implication known-implications) + ((:follows-fn expert) implication) + :true + true + (let [counterexamples (find-counterexamples known-examples + implication)] + (if (not-empty counterexamples) + :false + :unknown)))) + (counterexample [expert implication] (first (find-counterexamples known-examples implication))) + (unknown-for? [expert implication] (let [pre (premise implication) + con (conclusion implication) + ;follows-from-pre (close-under-implications known-implications pre) + follows-from-pre ((:implications-clop expert) pre) + ] + (difference (into #{} con) (into #{} follows-from-pre)))) + + ;;; This is used for the exploration of shared implications and for the phiRB strategy + Expert-Answer + (holds-in-view? [expert implication] + (let [M (attributes known-examples) + prem (premise implication) + concl (conclusion implication) + ;follows-from-implications (follows? implication known-implications) + follows-from-implications ((:follows-fn expert) implication) + satisfiable-in-context (satisfiable? implication known-examples) + answer (if follows-from-implications :yes (if satisfiable-in-context :unknown :no))] + (cond + (= answer :yes) + {:follows known-true + :counterexample (make-incomplete-context #{} M #{})} + (= answer :no) + {:follows known-false + :counterexample (find-counterexample known-examples implication)} + (= answer :unknown) + {:follows unknown + :counterexample (make-single-object-incomplete-context (str "g_{" prem "-/->" concl "}") + M + prem + concl)} + ))) + + (holds-in-view?-inc-counter [expert implication] + (inc-interaction-counter expert) + (holds-in-view? expert implication)) + + (extended-holds-in-view? [expert implication] + (let [M (attributes known-examples) + prem (premise implication) + concl (conclusion implication) + ;follows-from-implications (follows? implication known-implications) + follows-from-implications ((:follows-fn expert) implication) + satisfiable-in-context (satisfiable? implication known-examples) + answer (if follows-from-implications :yes (if satisfiable-in-context :unknown :no)) + ;attributes-that-follow (close-under-implications known-implications prem) + attributes-that-follow ((:implications-clop expert) prem) + potential-counterexamples (potential-counterexamples-subcontext known-examples implication) + ] + (cond + (= answer :yes) + {:follows known-true + :attributes-that-follow attributes-that-follow + :potential-counterexamples (make-incomplete-context #{} M #{})} + (= answer :no) + {:follows known-false + :attributes-that-follow #{} + :potential-counterexamples potential-counterexamples} + (= answer :unknown) + {:follows unknown + :attributes-that-follow attributes-that-follow + :potential-counterexamples potential-counterexamples} + ))) + + (extended-holds-in-view?-inc-counter [expert implication] + (inc-interaction-counter expert) + (extended-holds-in-view? expert implication)) + + ;;; Counter for the expert interactions + (interaction-counter [expert] (deref (:interaction-counter expert))) + (inc-interaction-counter [expert] (swap! (:interaction-counter expert) inc)) + (reset-interaction-counter [expert] (reset! (:interaction-counter expert) 0)) + ) + +(defn make-expert + "constructor function for Expert record. + takes either [implications examples] + or [name implications examples]" + ([implications examples] + (make-expert implications examples (hash [implications examples]))) + ([implications examples name] + (let [clop (clop-by-implications implications)] + (->(->Expert implications examples name) + (assoc :interaction-counter (atom 0)) + (assoc :implications-clop clop) + (assoc :follows-fn (fn [implication] (subset? (conclusion implication) + (clop (premise implication)))) + ))))) + +(defn expert-to-string + "" + [expert] + (with-str-out (str expert) "{:known-implications\n" (known-implications expert) ",\n\n" ":known-examples\n" (known-examples expert))) + + +(defmethod print-method Expert [expert out] + (.write ^java.io.Writer out ^String (str expert))) + + +(defn- find-counterexample + "find a counterexample for an implication R->m with single attribute conclusion" + [cxt implication] + (let [prem (premise implication) + concl (conclusion implication) + objs (certain-extent cxt prem) + inz (incidence cxt)] + (loop [o (first objs) + remaining (rest objs)] + (if (nil? o) + (make-incomplete-context #{} (attributes cxt) #{}) + (if (subset? concl (possible-intent cxt #{o})) + (recur (first remaining) + (rest remaining)) + (incomplete-subcontext cxt #{o} (attributes cxt)) + ))))) + +(defn- find-counterexamples + "given a context and an implication all objects are checked whether they are a counterexample to the implication. A set of counterexamples is returned (or an empty set if none is found)" + [context impl] + (let [pre (premise impl) + con (conclusion impl) + violates-conclusion (fn [obj] (not (empty? (intersection con (attributes-not-had context #{obj}))))) + has-premise (fn [obj] (subset? pre (attributes-had context #{obj}))) + transform (fn [obj] [obj (into #{} (attributes-had context #{obj})) (into #{} (attributes-not-had context #{obj}))]) + ] + (->> (objects context) + (filter has-premise) + (filter violates-conclusion) + (map transform) + (into []) + ) + )) + +(defn interaction-counters [experts] + (into {} (for [e experts] [(:name e) (interaction-counter e)]))) + +(defn reset-interaction-counters [experts] + (into {} (for [e experts] [(:name e) (reset-interaction-counter e)]))) + + +(defn- uuid [] (.toString (java.util.UUID/randomUUID))) + +(defn expert-handler + "responds with all counterexamples" + ([expert] + (expert-handler expert (uuid))) + ([expert namestr] + (fn [context implications new-impl] + (inc-interaction-counter expert) + (let [expert-impls (known-implications expert) + expert-cxt (known-examples expert) + expert-clop (clop-by-implications expert-impls) + pre (premise new-impl) + con (conclusion new-impl) + known-con-for-pre (expert-clop pre) + counterexamples (find-counterexamples expert-cxt new-impl) + answer (cond + ;; is the implication valid for the expert? + (subset? con known-con-for-pre) + [:implication] + (not (empty? counterexamples)) + [:counterexample counterexamples] + true + (let [unknown (difference con known-con-for-pre) + premise-implies (intersection con known-con-for-pre)] + [:unknown {:premise-implies premise-implies :unknown unknown}]) + ) + ;; _ (println "\n" (str namestr) (latex new-impl) "???:") + ;; _ (println "\t" (first answer)) + ;; _ (if (= (first answer) :unknown) (println "\t" (second answer))) + ;; _ (if (= (first answer) :counterexample) + ;; (println (latex (incomplete-subcontext expert-cxt + ;; (into #{} (map first counterexamples)) + ;; (attributes expert-cxt))))) + ] + answer + )))) + +(defn expert-handler-single-counterexample + "responds with a single counterexamples" + ([expert] + (expert-handler-single-counterexample expert (uuid))) + ([expert namestr] + (fn [context implications new-impl] + (inc-interaction-counter expert) + (let [expert-impls (known-implications expert) + expert-cxt (known-examples expert) + expert-clop (clop-by-implications expert-impls) + pre (premise new-impl) + con (conclusion new-impl) + known-con-for-pre (expert-clop pre) + counterexamples (find-counterexamples expert-cxt new-impl) + answer (cond + ;; is the implication valid for the expert? + (subset? con known-con-for-pre) + [:implication] + (not (empty? counterexamples)) + [:counterexample (take 1 counterexamples)] + true + (let [unknown (difference con known-con-for-pre) + premise-implies (intersection con known-con-for-pre)] + [:unknown {:premise-implies premise-implies :unknown unknown}]) + ) + ] + answer + )))) + +(defn maximal-expert-knowledge + "returns an expert who has the maximal knowledge for a group of experts for a universe" + [experts] + (let [contexts (map #(known-examples %) experts) + impls (canonical-base-from-base (reduce clojure.set/union #{} (map #(known-implications %) experts))) + objects (reduce clojure.set/union #{} (map #(objects %) contexts)) + attributes (reduce clojure.set/union #{} (map #(attributes %) contexts)) + max-inz (->> + contexts + (map #(incidence %)) + (map #(filter (fn [[[g m] w]] (not (= unknown w))) %)) + (reduce clojure.set/union #{}) + (into (into {} (cross-product (cross-product objects attributes) #{unknown})))) + ] + (make-expert impls (make-incomplete-context objects attributes max-inz)))) + +(defalias maximal-expert maximal-expert-knowledge) + + +(defn expert-pool-handler-maximal-expert + "Implements the theoretically maximal expert" + [experts] + (expert-handler (maximal-expert experts))) + + +(defn expert-pool-handler-random-expert-answer + "" + [experts] + {:pre [(coll? experts) (not (empty? experts))]} + (fn [context implications new-impl] + (let [random-expert (rand-nth (into [] experts)) + random-expert-handler (expert-handler random-expert)] + (random-expert-handler context implications new-impl)))) + + + +(defn expert-pool-handler-cycle-expert-answers + "Implementation of the strategy \\phi_{iterative}" + [experts] + {:pre [(coll? experts) (not (empty? experts))]} + (let [handlers (map expert-handler experts)] + (fn [context implications new-impl] + (let [f (fn [known-to-follow handler] + (let [answer (handler context implications new-impl)] + (cond + (= (first answer) :implication) + (reduced answer) + (= (first answer) :counterexample) + (reduced answer) + true + (union known-to-follow (:premise-implies (second answer))) + ))) + result (reduce f #{} handlers)] + (if (or (= (first result) :implication) (= (first result) :counterexample)) + result + (if (subset? (conclusion new-impl) result) + [:implication] + [:unknown {:premise-implies result :unknown (difference (conclusion new-impl) result)}])) + ))) + ) + + +(defn expert-pool-handler-maximal-knowledge-repetetive-broadcast + "Implementation of the strategy \\phi_{RB}" + [experts] + {:pre [(coll? experts) (not (empty? experts))]} + (fn [context implications new-impl] + (let [M (set (attributes context)) + concl (set (conclusion new-impl))] + (loop [F (premise new-impl)] + (let [answers (map (fn [e] (extended-holds-in-view?-inc-counter e (make-implication F concl))) experts) + Kpc (reduce incomplete-context-supremum (map :potential-counterexamples answers)) + Gc (objects (counterexamples-subcontext Kpc new-impl))] + (if (not-empty Gc) + [:counterexample (find-counterexamples Kpc new-impl)] + (let [FL (into #{} (reduce union #{} (map :attributes-that-follow answers)))] + (if (or (= FL F) + (= FL M)) + (if (subset? concl FL) + [:implication] + [:unknown {:premise-implies (intersection concl FL) + :unknown (difference concl FL)}]) + (recur FL))))))))) + + + + +(comment + (do + ;; Test find-counterexample + (def fc-impl (make-implication ['a] ['b])) + (def fc-context (make-incomplete-context [1 2 3] ['a 'b] [[1 'a known-true] [1 'b known-false] + [2 'a unknown] [2 'b known-true] + [3 'a known-true] [3 'b known-true]])) + (assert (= (find-counterexamples fc-context fc-impl) [[1 #{'a} #{'b}]])) + + + ;; Test Expert and Expert Handler + (def kimpl #{(make-implication [] ['b])}) + (def kexampl (make-incomplete-context [4 5] ['a 'b 'c] {[4 'a] unknown, [4 'c] known-false, [4 'b] known-true [5 'a] known-false [5 'b] unknown [5 'c] known-false})) + (def kimpl2 #{(make-implication ['a] ['c])}) + (def kexampl2 (make-incomplete-context [2] ['a 'b 'c] [[2 'a known-true] [2 'b unknown] [2 'c known-true]])) + (def Ex1 (make-expert kimpl kexampl "name1")) + (def Ex2 (make-expert kimpl2 kexampl2 "name2")) + ;(known-implications Ex1) + ;(known-examples Ex1) + ;(maximal-expert-knowledge #{Ex1 Ex2}) + + + (def Ex1-handler (expert-handler Ex1)) + (explore-attributes :examples-context (make-incomplete-context [] ['a 'b 'c] []) + :handler Ex1-handler) + + (def maxEx1Ex2-handler (expert-handler (maximal-expert-knowledge #{Ex1 Ex2}))) + (explore-attributes :examples-context (make-incomplete-context [] ['a 'b 'c] []) + :handler maxEx1Ex2-handler) + + (def random-expert-handler (expert-pool-handler-random-expert-answer #{Ex1 Ex2})) + (explore-attributes :examples-context (make-incomplete-context [] ['a 'b 'c] []) + :handler random-expert-handler) + + (def RB-expert-handler (expert-pool-handler-maximial-knowledge-repetetive-broadcast [Ex1 Ex2])) + (explore-attributes :examples-context (make-incomplete-context [] ['a 'b 'c] []) + :handler RB-expert-handler) + ) + ) + +(comment + (do + (def Ex3 (make-expert + [(make-implication #{\a} #{\b})] + (make-incomplete-context-from-matrix [1 2 3 4] [\a \b \c \d] + [1 1 ? ? + 0 1 ? ? + ? 1 0 ? + ? ? 1 0]))) + (def Ex4 (make-expert + [(make-implication #{\d} #{\b})] + (make-incomplete-context-from-matrix [1 2 3 4] [\a \b \c \d] + [? ? 0 1 + ? 1 1 1 + ? 1 ? 1 + ? 0 ? 0]))) + + (def max34-handler (expert-handler (maximal-expert-knowledge #{Ex3 Ex4}))) + (explore-attributes :examples-context (make-incomplete-context [] #{\a \b \c \d} []) + :handler max34-handler) + + (def RB-expert-handler34 (expert-pool-handler-maximal-knowledge-repetetive-broadcast [Ex3 Ex4])) + (explore-attributes :examples-context (make-incomplete-context [] #{\a \b \c \d} []) + :handler RB-expert-handler34) + + (def Iter-expert-handler34 (expert-pool-handler-cycle-expert-answers [Ex3 Ex4])) + (explore-attributes :examples-context (make-incomplete-context [] #{\a \b \c \d} []) + :handler Iter-expert-handler34) + + + + (def living (conexp.io.contexts/json->ctx + (read-string + "{:attributes (\"dicotyledon\" \"monocotyledon\" \"can move\" \"lives in water\" \"lives on land\" \"needs water to live\" \"needs chlorophyll\" \"breast feeds\" \"has limbs\"), :adjacency-list [{:object \"dog\", :attributes (\"has limbs\" \"breast feeds\" \"needs water to live\" \"lives on land\" \"can move\")} {:object \"fish leech\", :attributes (\"needs water to live\" \"lives in water\" \"can move\")} {:object \"corn\", :attributes (\"needs chlorophyll\" \"needs water to live\" \"lives on land\" \"monocotyledon\")} {:object \"bream\", :attributes (\"has limbs\" \"needs water to live\" \"lives in water\" \"can move\")} {:object \"water weeds\", :attributes (\"needs chlorophyll\" \"needs water to live\" \"lives in water\" \"monocotyledon\")} {:object \"bean\", :attributes (\"needs chlorophyll\" \"needs water to live\" \"lives on land\" \"dicotyledon\")} {:object \"frog\", :attributes (\"has limbs\" \"needs water to live\" \"lives on land\" \"lives in water\" \"can move\")} {:object \"reed\", :attributes (\"needs chlorophyll\" \"needs water to live\" \"lives on land\" \"lives in water\" \"monocotyledon\")}]}") + )) + + + + + (require 'conexp.fca.incomplete-contexts.random-experts) + (defn- make-n-random-experts-for-universe + [K n] + (for [i (range n)] (conexp.fca.incomplete-contexts.random-experts/make-random-expert-for-universe K (str "E-" i)))) + + (defn- implication-sets-same-premises? + [L1 L2] + (let [P1 (->> L1 + (map premise) + (into #{})) + P2 (->> L2 + (map premise) + (into #{}))] + (= P1 P2))) + + (defn- implication-sets-equivalent? + [L1 L2] + (let [clop1 (clop-by-implications L1) + follows1 (fn [impl] (subset? (conclusion impl) + (clop1 (premise impl)))) + clop2 (clop-by-implications L2) + follows2 (fn [impl] (subset? (conclusion impl) + (clop2 (premise impl))))] + (every? true? (into (into [] (for [RS L1] (follows2 RS))) + (for [RS L2] (follows1 RS)))) + )) + + + (defn- test-and-log-for-n-experts + [K n] + (let [experts (make-n-random-experts-for-universe K n) + maxExpert (maximal-expert experts) + handlers (-> {} + (assoc :maxExpert (expert-handler maxExpert)) + (assoc :phiRB (expert-pool-handler-maximal-knowledge-repetetive-broadcast experts)) + (assoc :phiITER (expert-pool-handler-cycle-expert-answers experts))) + Kempty (make-empty-incomplete-context (attributes (known-examples (first experts))))] + (let [ results + (->> + (for [[hk hv] handlers] + (do + (println "start" hk) + (let [res (explore-attributes :examples-context Kempty :handler hv)] + (println (:implications res)) + (println "handler:" hk (interaction-counters experts) "maxexpert:" (interaction-counter maxExpert)) + (reset-interaction-counters experts) + (reset-interaction-counter maxExpert) + (println "end" hk) + [hk (:implications res)] + ))) + (into {}) + )] + (println "max=RB" (implication-sets-equivalent? (:maxExpert results) (:phiRB results))) + (println "max=iter?" (implication-sets-equivalent? (:maxExpert results) (:phiITER results))) + (println "RB=iter?" (implication-sets-equivalent? (:phiRB results) (:phiITER results))) + + ))) + ) + (test-and-log-for-n-experts living 5) + ) + + + +(comment + ;;; example for different bases with maxexpert, RB and iterative + (do + (def Kempty (make-empty-incomplete-context [1 2 3 4 5])) + (def e1 (make-expert [(make-implication [1] [2])] (make-empty-incomplete-context [1 2 3 4 5]))) + (def e2 (make-expert [(make-implication [2] [3])] (make-empty-incomplete-context [1 2 3 4 5]))) + (def e3 (make-expert [(make-implication [3] [4])] (make-empty-incomplete-context [1 2 3 4 5]))) + (def e4 (make-expert [(make-implication [4] [5])] (make-empty-incomplete-context [1 2 3 4 5]))) + (def e5 (make-expert [(make-implication [] [1])] (make-empty-incomplete-context [1 2 3 4 5]))) + (def eMax (maximal-expert [e1 e2 e3 e4 e5])) + (def iterative-handler (expert-pool-handler-cycle-expert-answers [e1 e2 e3 e4 e5])) + (def RB-handler (expert-pool-handler-maximal-knowledge-repetetive-broadcast [e1 e2 e3 e4 e5])) + (def eMax-handler (expert-handler eMax)) + + (iterative-handler Kempty [] (make-implication [1] [5])) + (eMax-handler Kempty [] (make-implication [1] [5])) + (def resI (explore-attributes :examples-context Kempty :handler iterative-handler)) + (def resRB (explore-attributes :examples-context Kempty :handler RB-handler)) + (def resMAX (explore-attributes :examples-context Kempty :handler eMax-handler)) + (println) + (println "I-RB same implication sets" (implication-sets-same-premises? (:implications resI) + (:implications resRB))) + (println "I-MAX same implication sets" (implication-sets-same-premises? (:implications resI) + (:implications resMAX))) + (println "RB-MAX same implication sets" (implication-sets-same-premises? (:implications resRB) + (:implications resMAX))) + (println "I-RB same implications follow" (implication-sets-equivalent? (:implications resI) + (:implications resRB))) + (println "I-MAX same implications follow" (implication-sets-equivalent? (:implications resI) + (:implications resMAX))) + (println "RB-MAX same implications follow" (implication-sets-equivalent? (:implications resRB) + (:implications resMAX))) + ) + ) diff --git a/src/main/clojure/conexp/fca/incomplete_contexts/exploration_shared_implications.clj b/src/main/clojure/conexp/fca/incomplete_contexts/exploration_shared_implications.clj new file mode 100644 index 000000000..245a0eec2 --- /dev/null +++ b/src/main/clojure/conexp/fca/incomplete_contexts/exploration_shared_implications.clj @@ -0,0 +1,570 @@ +(ns conexp.fca.incomplete-contexts.exploration-shared-implications + (:gen-class) + (:require [clojure.set :refer :all] + [clojure.core.reducers :as r] + [clojure.math.combinatorics :refer [subsets]] + [clojure.algo.generic.functor :refer [fmap]] + [clojure.string :as s] + [conexp.fca.contexts :as cxts :refer [Context make-context]] + [conexp.fca.implications :refer [holds? respects? follows? premise conclusion make-implication clop-by-implications canonical-base parallel-canonical-base-from-clop close-under-implications pseudo-close-under-implications]] + [conexp.io.contexts] + [conexp.io.layouts :refer :all] + [conexp.fca.lattices :refer [concept-lattice]] + [conexp.gui.draw :refer [draw-concept-lattice]] + [conexp.layouts.dim-draw :refer :all] + [conexp.io.incomplete-contexts :as io] + [conexp.fca.incomplete-contexts.incomplete-contexts :refer :all] + [conexp.fca.incomplete-contexts.experts :refer :all] + [conexp.fca.incomplete-contexts.conexp-interop :refer :all] +)) + + + + +(defn- next-closure-by-implications + "Given a set of attributes A from the base set M and a set of implications L on M, returns the next closed set for A" + [A M L] + (conexp.base/next-closed-set + M + (clop-by-implications L) + A)) + + +(defn- incomplete-context-supremum-generalized + [cxt1 cxt2] + (let [o1 (objects cxt1) + o2 (objects cxt2) + objs (union o1 o2) + atts1 (attributes cxt1) + atts2 (attributes cxt2) + atts (union atts1 atts2) + inz0 (into {} (for [g objs m atts] [[g m] unknown])) + inz1 (incidence cxt1) + inz2 (incidence cxt2) + inz (merge inz0 inz1 inz2 (into {} (for [g (intersection o1 o2) m (intersection atts1 atts2)] + [[g m] (information-supremum + (get inz1 [g m]) + (get inz2 [g m]))])))] + (make-incomplete-context objs atts inz))) + + + +;;; base version - version below is a lot faster by reusing Ksup +;; (defn explore-shared-implications +;; " +;; M attributes #{} +;; L0 background implications that hold for all experts (can be an empty set) #{} +;; K maps experts to their example contexts (each expert has at least an empty example context) {E1 K1 E2 K2 ..} +;; E set of experts (an expert can be asked if an implication holds in their view) #{} +;; " +;; [M L0 K E] +;; (let [L L0 +;; R #{} +;; C (make-incomplete-context #{} E #{})] +;; (loop [L L +;; R R +;; C C +;; K K] +;; (if (= R (into #{} M)) +;; [L K C] +;; (let [Ksup (subposition (vals K)) +;; RBoxDiamond (possibly-implied-attributes Ksup R)] +;; (if (= R RBoxDiamond) +;; (recur L +;; (next-closure-by-implications R M L) +;; C +;; K) +;; (let [;; only ask about the attributes that are not in the premise +;; _ (println R) +;; attributes-to-ask-about (difference RBoxDiamond R) +;; ;; ask all experts about each of these attributes (i.e. ask about the implication R->m) +;; answers (into {} (for [expert E +;; m attributes-to-ask-about] +;; [[expert m] (holds-in-view? expert (make-implication R #{m}))])) +;; ;; group the answers by attribute +;; answers-grouped-by-m (group-by (fn [[[e m] v]] m) answers) +;; ;; find the attributes that follow from R for all experts +;; ;; (i.e., check for each attribute if all experts answered with :yes it follows) +;; X ;; shared-attributes-following-from-R +;; (->> answers-grouped-by-m +;; (map (fn [[m vs]] +;; [m (every? +;; identity +;; (into [] +;; (map (fn [[k {v :follows}]] (known-true? v)) vs)))])) +;; (filter (fn [[k v]] (true? v))) +;; (map first) +;; (into #{})) + +;; ;; add R->X if X=/=R otherwise keep L unchanged +;; newL (if (= X R) +;; L +;; (union L #{(make-implication R X)})) +;; ;; collect the questions asked and the responses to add to the context C +;; new-C-objects (into #{} (for [m attributes-to-ask-about] (make-implication R #{m}))) +;; new-C-incidences (->> answers +;; (map (fn [[[e m] {v :follows}]] [[(make-implication R #{m}) e] v])) +;; (into {})) +;; ;; add the new responses to C (by merging C with an incomplete context that contains the new information) +;; newC (incomplete-context-supremum C (make-incomplete-context new-C-objects E new-C-incidences)) +;; ;; compute the next closure of R, i.e. the next premise +;; newR (next-closure-by-implications R M newL) +;; ;; group the answers by expert +;; answers-grouped-by-expert (group-by (fn [[[e m] v]] e) answers) +;; ;; collect all counterexamples given per expert in a single incomplete context +;; counterexamples-per-expert +;; (->> answers-grouped-by-expert +;; (map (fn [[e vs]] +;; [e (->> vs +;; (map (fn [[k {v :counterexample}]] v)) +;; (reduce incomplete-context-supremum +;; (make-empty-incomplete-context M)))])) +;; (into {})) +;; ;; add the newly found counterexamples to the existing examples for each expert +;; newK (into {} (for [e E] +;; [e (incomplete-context-supremum +;; (get K e) +;; (get counterexamples-per-expert e))]))] +;; (recur newL +;; newR +;; newC +;; newK))) +;; ))))) + + +(defn explore-shared-implications + " + M attributes #{} + L0 background implications that hold for all experts (can be an empty set) #{} + K maps experts to their example contexts (each expert has at least an empty example context) {E1 K1 E2 K2 ..} + E set of experts (an expert can be asked if an implication holds in their view) #{} + " + ([M E] + (explore-shared-implications M + #{} + (into {} (for [expert E] [expert (make-incomplete-context #{} M #{})])) + E)) + ([M L0 K E] + (let [L L0 + R #{} + C (make-incomplete-context #{} E #{})] + (loop [L L + R R + C C + K K] + (let [Ksup (subposition (vals K)) + Ratom (atom R)] + ;; compute the next set R that should be asked about without recomputing Ksup + (while (and (= @Ratom (possibly-implied-attributes Ksup @Ratom)) + (not= @Ratom (into #{} M))) + (swap! Ratom (fn [X] (next-closure-by-implications X M L)))) + ;; here @Ratom is the next R that should be asked about + ;; or @Ratom = M and the algorithm should terminate + (if (= @Ratom (into #{} M)) + [L K C] + (let [;; get the attributes that could follow from R to ask about them + RBoxDiamond (possibly-implied-attributes Ksup @Ratom) + ;; only ask about the attributes that are not in the premise + attributes-to-ask-about (difference RBoxDiamond @Ratom) + ;; ask all experts about each of these attributes (i.e. ask about the implication R->m) + answers (into {} (for [expert E + m attributes-to-ask-about] + [[expert m] (holds-in-view? expert (make-implication @Ratom #{m}))])) + _ (doseq [expert E] (inc-interaction-counter expert)) + ;; group the answers by attribute + answers-grouped-by-m (group-by (fn [[[e m] v]] m) answers) + ;; find the attributes that follow from R for all experts + ;; (i.e., check for each attribute if all experts answered with :yes it follows) + X ;; shared-attributes-following-from-R + (->> answers-grouped-by-m + (map (fn [[m vs]] + [m (every? + identity + (into [] + (map (fn [[k {v :follows}]] (known-true? v)) vs)))])) + (filter (fn [[k v]] (true? v))) + (map first) + (into #{})) + + ;; add R->X if X != R otherwise keep L unchanged + newL (if (or (= X @Ratom) (= X #{})) + L + (union L #{(make-implication @Ratom X)})) + ;; collect the questions asked and the responses to add to the context C + new-C-objects (into #{} (for [m attributes-to-ask-about] (make-implication @Ratom #{m}))) + new-C-incidences (->> answers + (map (fn [[[e m] {v :follows}]] [[(make-implication @Ratom #{m}) e] v])) + (into {})) + ;; add the new responses to C (by merging C with an incomplete context that contains the new information) + newC (incomplete-context-supremum C (make-incomplete-context new-C-objects E new-C-incidences)) + ;; compute the next closure of R, i.e. the next premise + newR (next-closure-by-implications @Ratom M newL) + ;; group the answers by expert + answers-grouped-by-expert (group-by (fn [[[e m] v]] e) answers) + ;; collect all counterexamples given per expert in a single incomplete context + counterexamples-per-expert + (->> answers-grouped-by-expert + (map (fn [[e vs]] + [e (->> vs + (map (fn [[k {v :counterexample}]] v)) + (reduce incomplete-context-supremum + (make-empty-incomplete-context M)))])) + (into {})) + ;; add the newly found counterexamples to the existing examples for each expert + newK (into {} (for [e E] + [e (incomplete-context-supremum + (get K e) + (get counterexamples-per-expert e))])) + ;; print out question and experts to whom it is posed + #_ (do #_(println) + (println (latex (make-implication @Ratom attributes-to-ask-about))) + #_(println) + #_(println answers-grouped-by-expert) + (println (->> answers-grouped-by-m + (map second) + (map #(map (fn [[k ans]] (if (= (:follows ans) "x") + [k (:follows ans) "-"] + [k + (:follows ans) + (->> (:counterexample ans) + (objects) + (first)) + ])) % )) + (reduce into []) + (interpose "\n"))))] + ;; start again with (possibly) enlarged L and K + (recur newL + newR + newC + newK))) + ))))) + + +;;; this version also tests if the experts previous answers during this exploration already answer the question +;;; maybe also: test if the experts answers from all previous explorations already answer the question +;;; i.e. allow to provide the context C +(defn explore-shared-implications-use-C + " + M attributes #{} + L0 background implications that hold for all experts (can be an empty set) #{} + K maps experts to their example contexts (each expert has at least an empty example context) {E1 K1 E2 K2 ..} + E set of experts (an expert can be asked if an implication holds in their view) #{} + " + ([M E] + (explore-shared-implications-use-C M + #{} + (into {} (for [expert E] [expert (make-incomplete-context #{} M #{})])) + E + (make-incomplete-context #{} E #{}))) + ([M L0 K E C] + (let [L L0 + R #{}] + (loop [L L + R R + C C + K K] + (let [Ksup (subposition (vals K)) + Ratom (atom R)] + ;; compute the next set R that should be asked about without recomputing Ksup + (while (and (= @Ratom (possibly-implied-attributes Ksup @Ratom)) + (not= @Ratom (into #{} M))) + (swap! Ratom (fn [X] (next-closure-by-implications X M L)))) + ;; here @Ratom is the next R that should be asked about + ;; or @Ratom = M and the algorithm should terminate + (if (= @Ratom (into #{} M)) + [L K C] + (let [;; get the attributes that could follow from R to ask about them + RBoxDiamond (possibly-implied-attributes Ksup @Ratom) + ;; only ask about the attributes that are not in the premise + attributes-to-ask-about (difference RBoxDiamond @Ratom) + ;; ask all experts about each of these attributes (i.e. ask about the implication R->m) + answers (into {} (for [expert E + m attributes-to-ask-about] + [[expert m] (holds-in-view? expert (make-implication @Ratom #{m}))])) + ;; add an interaction to the counter of the experts for whom the implication does not already follow from previous answers + interactedE (reduce union #{} + (for [e E] + (if (follows? (make-implication @Ratom RBoxDiamond) + (certain-attribute-derivation C #{e})) + #{} + #{e} + ))) + _ (doseq [expert interactedE] (inc-interaction-counter expert)) + ;; group the answers by attribute + answers-grouped-by-m (group-by (fn [[[e m] v]] m) answers) + ;; find the attributes that follow from R for all experts + ;; (i.e., check for each attribute if all experts answered with :yes it follows) + X ;; shared-attributes-following-from-R + (->> answers-grouped-by-m + (map (fn [[m vs]] + [m (every? + identity + (into [] + (map (fn [[k {v :follows}]] (known-true? v)) vs)))])) + (filter (fn [[k v]] (true? v))) + (map first) + (into #{})) + + ;; add R->X if X != R and X!= empty otherwise keep L unchanged + newL (if (or (= X @Ratom) (= X #{})) + L + (union L #{(make-implication @Ratom X)})) + ;; collect the questions asked and the responses to add to the context C + new-C-objects (into #{} (for [m attributes-to-ask-about] (make-implication @Ratom #{m}))) + new-C-incidences (->> answers + (map (fn [[[e m] {v :follows}]] [[(make-implication @Ratom #{m}) e] v])) + (into {})) + ;; add the new responses to C (by merging C with an incomplete context that contains the new information) + newC (incomplete-context-supremum C (make-incomplete-context new-C-objects E new-C-incidences)) + ;; compute the next closure of R, i.e. the next premise + newR (next-closure-by-implications @Ratom M newL) + ;; group the answers by expert + answers-grouped-by-expert (group-by (fn [[[e m] v]] e) answers) + ;; collect all counterexamples given per expert in a single incomplete context + counterexamples-per-expert + (->> answers-grouped-by-expert + (map (fn [[e vs]] + [e (->> vs + (map (fn [[k {v :counterexample}]] v)) + (reduce incomplete-context-supremum + (make-empty-incomplete-context M)))])) + (into {})) + ;; add the newly found counterexamples to the existing examples for each expert + newK (into {} (for [e E] + [e (incomplete-context-supremum + (get K e) + (get counterexamples-per-expert e))])) + ;; print out question and experts to whom it is posed + #_ (do #_(println) + (println (latex (make-implication @Ratom attributes-to-ask-about))) + #_(println) + #_(println answers-grouped-by-expert) + (println (->> answers-grouped-by-m + (map second) + (map #(map (fn [[k ans]] (if (= (:follows ans) "x") + [k (:follows ans) "-"] + [k + (:follows ans) + (->> (:counterexample ans) + (objects) + (first)) + ])) % )) + (reduce into []) + (interpose "\n"))))] + ;; start again with (possibly) enlarged L and K + (recur newL + newR + newC + newK))) + ))))) + + + + +(defn- incomplete-contexts-update-incidences + "given an incomplete context and a partial map of incidences to update, updates the incidences" + [cxt incidences] + (make-incomplete-context (objects cxt) (attributes cxt) (merge (into {} (incidence cxt)) incidences))) + + +(defn- has-counterexample? + "given an incomplete context and an implication check if the implication has a counterexample in the context" + [cxt impl] + (not (satisfiable? impl cxt))) + + +(defn- ?-reduce-cxt + "reduce ? in the resulting context C that occur when simply merging multiple exploration results. Implications can have a ? even though they follow from the accepted implications or are rejected by a counterexample for an expert simply because the experts were not asked about them." + [C K E] + (let [f (fn + [[[impl e] v]] + (cond + (follows? impl (certain-extent C #{e})) + [[impl e] known-true] + (has-counterexample? (get K e) impl) + [[impl e] known-false] + true + [[impl e] unknown] + ))] + (->> (unknown-incidence-set C) + (map f) + (into {}) + (incomplete-contexts-update-incidences C) + ))) + + +(defn explore-all-shared-implications + "" + ([attributes experts] + (explore-all-shared-implications + attributes + experts + (into {} (for [expert experts] [expert (make-incomplete-context #{} attributes #{})])) + (make-incomplete-context #{} experts #{}))) + ([attributes experts examples conditional-implication-context] + (let [M attributes + E experts + PE (reverse (drop 1 (sort-by count (subsets (vec E))))) + K examples + C conditional-implication-context] + (loop [S (first PE) + remaining (rest PE) + K K + C C] + (if (nil? S) + [K C] ; note: actually unknown answers are being represented as false when returning due to artificial counterexamples being part of the context of counterexamples + (let [L (certain-extent C S) + Kselected (select-keys K S) + [Lnew Knew Cnew] (explore-shared-implications M L Kselected S) + Knext (merge K + (into {} + (for [e E + :let [cxt1 (get K e) + cxt2 (get Knew e)] + :when (some? cxt2)] + [e (incomplete-context-supremum cxt1 cxt2)] + ))) + Cnext (?-reduce-cxt (incomplete-context-supremum-generalized C Cnew) Knext E)] + (recur (first remaining) + (rest remaining) + Knext + Cnext) + )))))) + + +(defn explore-all-shared-implications-use-C + "" + ([attributes experts] + (explore-all-shared-implications-use-C + attributes + experts + (into {} (for [expert experts] [expert (make-incomplete-context #{} attributes #{})])) + (make-incomplete-context #{} experts #{}))) + ([attributes experts examples conditional-implication-context] + (let [M attributes + E experts + PE (reverse (drop 1 (sort-by count (subsets (vec E))))) + K examples + C conditional-implication-context] + (loop [S (first PE) + remaining (rest PE) + K K + C C] + (if (nil? S) + [K C] ; note: actually unknown answers are being represented as false when returning due to artificial counterexamples being part of the context of counterexamples + (let [L (certain-extent C S) + Kselected (select-keys K S) + [Lnew Knew Cnew] (explore-shared-implications-use-C M L Kselected S C) + Knext (merge K + (into {} + (for [e E + :let [cxt1 (get K e) + cxt2 (get Knew e)] + :when (some? cxt2)] + [e (incomplete-context-supremum cxt1 cxt2)] + ))) + Cnext (?-reduce-cxt (incomplete-context-supremum-generalized C Cnew) Knext E)] + (recur (first remaining) + (rest remaining) + Knext + Cnext) + )))))) + + +(defn explore-all-shared-implications-no-?-reductions + "" + ([attributes experts] + (explore-all-shared-implications + attributes + experts + (into {} (for [expert experts] [expert (make-incomplete-context #{} attributes #{})])) + (make-incomplete-context #{} experts #{}))) + ([attributes experts examples conditional-implication-context] + (let [M attributes + E experts + PE (reverse (drop 1 (sort-by count (subsets (vec E))))) + K examples + C conditional-implication-context] + (loop [S (first PE) + remaining (rest PE) + K K + C C] + (if (nil? S) + [K C] ; note: actually unknown answers are being represented as false when returning due to artificial counterexamples being part of the context of counterexamples + (let [L (certain-extent C S) + Kselected (select-keys K S) + [Lnew Knew Cnew] (explore-shared-implications M L Kselected S) + Knext (merge K + (into {} + (for [e E + :let [cxt1 (get K e) + cxt2 (get Knew e)] + :when (some? cxt2)] + [e (incomplete-context-supremum cxt1 cxt2)] + ))) + Cnext (incomplete-context-supremum-generalized C Cnew)] + (recur (first remaining) + (rest remaining) + Knext + Cnext) + )))))) + + +(defn explore-all-shared-implications-in-parallel + "" + ([attributes experts] + (explore-all-shared-implications-in-parallel + attributes + experts + (into {} (for [expert experts] [expert (make-incomplete-context #{} attributes #{})])) + (make-incomplete-context #{} experts #{}))) + ([attributes experts examples conditional-implication-context] + (let [M attributes + E experts + K examples + C conditional-implication-context + redf (fn + ([] [{} (make-empty-incomplete-context #{})]) + ([[K C] [K2 C2]] + (let [Knew (merge K K2)] + [Knew (?-reduce-cxt + (incomplete-context-supremum-generalized C C2) + Knew + (keys Knew))]))) + + mapf (fn [e] (drop 1 (explore-shared-implications + M + #{} + {e (make-empty-incomplete-context M)} + #{e}))) + ] + (r/fold redf (r/map mapf experts))))) + + +(defn explore-all-shared-implications-in-parallel-no-?-reductions + "" + ([attributes experts] + (explore-all-shared-implications-in-parallel + attributes + experts + (into {} (for [expert experts] [expert (make-incomplete-context #{} attributes #{})])) + (make-incomplete-context #{} experts #{}))) + ([attributes experts examples conditional-implication-context] + (let [M attributes + E experts + K examples + C conditional-implication-context + redf (fn + ([] [{} (make-empty-incomplete-context #{})]) + ([[K C] [K2 C2]] + (let [Knew (merge K K2)] + [Knew (incomplete-context-supremum-generalized C C2)]))) + mapf (fn [e] (drop 1 (explore-shared-implications + M + #{} + {e (make-empty-incomplete-context M)} + #{e}))) + ] + (r/fold redf (r/map mapf experts))))) + + diff --git a/src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts.clj b/src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts.clj new file mode 100644 index 000000000..d3496cd20 --- /dev/null +++ b/src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts.clj @@ -0,0 +1,605 @@ +(ns conexp.fca.incomplete-contexts.incomplete-contexts + "Incomplete-Contexts" + (:require [clojure.set :refer [subset? intersection difference union]] + [conexp.base :refer [exists forall illegal-argument <=> hash-combine-hash sort-by-first map-by-fn with-str-out ensure-length ensure-seq defalias clojure-type clojure-coll clojure-fn to-set cross-product set-of disjoint-union]] + [conexp.fca.implications :refer :all] + [clojure.algo.generic.functor :refer [fmap]] + [clojure.core.reducers :as r]) + (:import [conexp.fca.implications Implication])) + + + +(defprotocol Incomplete-Context-Protocol + (objects [ctx] "Returns the objects of a context.") + (attributes [ctx] "Returns the attributes of a context.") + (incidence [ctx] "Returns a map that, given a pair [a b], returns the corresponding truth-value (known-true/known-false/unknown)")) + + + +(def known-trues #{1 \1 "X" \X "x" \x "true" :true true}) +(def known-falses #{0 \0 "O" \O "o" \o "." \. "false" :false false}) +(def unknowns #{"U" \U "u" \u "?" \? "unknown" :unknown}) +(def known-true "x") +(defalias ktrue known-true) +(def known-false ".") ;'. +(defalias kfalse known-false) +(def unknown "?") +(defalias ? unknown) +(def incomplete-context-possible-values #{known-true known-false unknown}) + +(defn map-true-false-unknown-to-x-o-? + "" + [in] + (if (contains? known-trues in) known-true + (if (contains? known-falses in) known-false + (if (contains? unknowns in) unknown + (illegal-argument "Value " in (type in) " can not be parsed as true false or unknown; the possible values are " known-trues known-falses unknowns))))) + +(defn- filter-incidences + "" + [cxt incidence-filter-values] + (filter #(contains? incidence-filter-values (second %)) (incidence cxt))) + +(defn true-incidence + "given an incomplete context returns the known true incidences" + [cxt] + (filter-incidences cxt known-trues)) + +(defn false-incidence + "given an incomplete context returns the known false incidences" + [cxt] + (filter-incidences cxt known-falses)) + +(defn unknown-incidence + "given an incomplete context returns the unknown incidences" + [cxt] + (filter-incidences cxt unknowns)) + +(defn true-or-unknown-incidences + "given an incomplete context returns the true or unknown incidences" + [cxt] + (filter-incidences cxt (union known-trues unknowns))) + +(defalias unknown-incidence-set unknown-incidence) + +(defn known-true? [v] (= v known-true)) +(defn known-false? [v] (= v known-false)) +(defn unknown? [v] (= v unknown)) + +(defn information-supremum + [v1 v2] + (cond + (= v1 v2) + (if (nil? v1) unknown v1) + (and (= v1 known-true) (or (= v2 unknown) (nil? v2))) + known-true + (and (= v1 known-false) (or (= v2 unknown) (nil? v2))) + known-false + (and (= v2 known-true) (or (= v1 unknown) (nil? v1))) + known-true + (and (= v2 known-false) (or (= v1 unknown) (nil? v1))) + known-false + (or (nil? v1) (nil? v2)) + unknown + true + (throw (IllegalArgumentException. "Input values not x,o,? or nil")))) + + +(deftype Incomplete-Context [objects attributes incidence] + Object + (equals [this other] + (and (instance? Incomplete-Context other) + (= objects (.objects ^Incomplete-Context other)) + (= attributes (.attributes ^Incomplete-Context other)) + (let [other-incidence (.incidence ^Incomplete-Context other)] + (forall [g objects, m attributes] + (<=> (incidence [g m]) + (other-incidence [g m])))))) + (hashCode [this] + (hash-combine-hash Incomplete-Context objects attributes incidence)) + ;; + Incomplete-Context-Protocol + (objects [this] objects) + (attributes [this] attributes) + (incidence [this] incidence)) + + +(defn incomplete-context-to-string + "Returns a string representing the given incomplete context inco-cxt + as a value-table." + ([inco-cxt] + (incomplete-context-to-string inco-cxt sort-by-first sort-by-first)) + ([inco-cxt order-on-objects order-on-attributes] + (let [objs (sort order-on-objects (objects inco-cxt)) + atts (sort order-on-attributes (attributes inco-cxt)) + inz (incidence inco-cxt) + + str #(if (nil? %) "nil" (str %)) + + max-obj-len (reduce #(max %1 (count (str %2))) 0 objs) + max-att-lens (loop [lens (transient (map-by-fn #(count (str %)) atts)) + values (seq inz)] + (if values + (let [[[_ m] w] (first values) + len (count (str w))] + (recur (if (> len (lens m)) + (assoc! lens m len) + lens) + (next values))) + (persistent! lens)))] + (with-str-out + (ensure-length "" max-obj-len " ") " |" (for [att atts] + [(ensure-length (print-str att) (max-att-lens att) " ") " "]) + "\n" + (ensure-length "" max-obj-len "-") "-+" (for [att atts] + (ensure-length "" (inc (max-att-lens att)) "-")) + "\n" + (for [obj objs] + [(ensure-length (str obj) max-obj-len) + " |" + (for [att atts] + [(ensure-length (str (inz [obj att])) (max-att-lens att)) + " "]) +"\n"]))))) + + +(defn print-context + "Prints the result of applying context-to-string to the given + arguments." + [ctx & args] + (print (apply incomplete-context-to-string ctx args))) + + +(defmethod print-method Incomplete-Context [incomplete-ctx out] + (.write ^java.io.Writer out ^String (incomplete-context-to-string incomplete-ctx))) + + +(defmethod print-dup Incomplete-Context [incomplete-cxt out] + (print-ctor incomplete-cxt (fn [cxt out] + (print-dup (objects cxt) out) + (.write out " ") + (print-dup (attributes cxt) out) + (.write out " ") + (print-dup (incidence cxt) out)) out)) + + + +(defn incomplete-context? + "Returns true iff thing is an incomplete context." + [thing] + (instance? Incomplete-Context thing)) + +(defalias ?-context? incomplete-context?) + + +(defn complete-incomplete-context? + "checks if an incomplete context contains any unknown relations" + [cxt] + (not (.contains (vals (incidence cxt)) unknown))) + +(defalias is-complete-incomplete-context? complete-incomplete-context?) + + +(defmulti make-incomplete-context + "Constructs a many-valued context from a set of objects, a set of + attributes and an incidence relation, given as set of triples [g m w] + or as a function from two arguments g and m to values w." + {:arglists '([objects attributes incidence])} + (fn [& args] (vec (map clojure-type args)))) + + +(defmethod make-incomplete-context [Object Object clojure-coll] + [objs atts inz] + (let [objs (to-set objs) + atts (to-set atts) + crossProductObjsAtts (cross-product objs atts) + ] + (->Incomplete-Context objs + atts + (merge (into {} (map #(vector % unknown) crossProductObjsAtts)) + (if (map? inz) + (do + (when-not (subset? (set (keys inz)) crossProductObjsAtts) + (illegal-argument "Incidence map for incomplete-context" + "must not contain additional keys.")) + + inz) + (loop [hash (transient {}) + items inz] + (if (empty? items) + (persistent! hash) + (let [[g m w] (flatten (first items))] + (recur (if (and (contains? objs g) + (contains? atts m)) + (assoc! hash [g m] (map-true-false-unknown-to-x-o-? w)) + hash) + (rest items)))))))))) + + +(defmethod make-incomplete-context [Object Object clojure-fn] + [objs atts inz] + (let [objs (to-set objs) + atts (to-set atts)] + (->Incomplete-Context objs + atts + (loop [hash (transient {}) + items (cross-product objs atts)] + (if (empty? items) + (persistent! hash) + (let [[g m] (first items)] + (recur (if (and (contains? objs g) + (contains? atts m)) + (assoc! hash [g m] (map-true-false-unknown-to-x-o-? (inz [g m]))) + hash) + (rest items)))))))) + + +(defn make-incomplete-context-from-matrix + "Given objects G and attribute M and an incidence matrix constructs + the corresponding context. G and M may also be numbers where they + represent (range G) and (range M) respectively." + [G M bits] + (let [G (ensure-seq G), + M (ensure-seq M), + m (count G), + n (count M)] + (assert (= (* m n) (count bits)) + "Number of objects and attributes does not match the number of entries.") + (->Incomplete-Context (to-set G) (to-set M) + (into {} (for [i (range (count G)) + j (range (count M))] + [[(nth G i) + (nth M j)] + (map-true-false-unknown-to-x-o-? (nth bits (+ (* n i) j)))]))))) + + +(defn make-single-object-incomplete-context + "Given a single object, a set of attributes and two subsets of attributes that the object has and does not have make an incomplete context containing this object" + [object attributes attributes-had attributes-not-had] + (let [inz1 (for [m attributes-had] [[object m] known-true]) + inz2 (for [m attributes-not-had] [[object m] known-false]) + inz3 (for [m (difference attributes attributes-had attributes-not-had)] [[object m] unknown]) + inz (into {} (union inz1 inz2 inz3))] + (make-incomplete-context #{object} attributes inz))) + + +(defn make-empty-incomplete-context + "Given a set of attributes create an empty incomplete context" + [attributes] + (make-incomplete-context #{} attributes #{})) + + +(defn incomplete-subcontext + "given an incomplete context a set of objects and a set of attributes returns the corresponding subcontext" + ([cxt attrs] + (incomplete-subcontext cxt (objects cxt) attrs)) + ([cxt objs attrs] + {:pre [[(subset? objs (objects cxt))] + [(subset? attrs (attributes cxt))]]} + (let [new-inz (reduce #(into %2 %1) {} (map #(hash-map % (get (incidence cxt) %)) (cross-product objs attrs)))] + (make-incomplete-context objs attrs new-inz)))) + + +(defn incomplete-context-subposition + "Returns context subposition of ctx-1 and ctx-2" + ([contexts] + (if (apply not= (map attributes contexts)) + (illegal-argument "Cannot do context subposition, since attribute sets are not equal.")) + (let [n (count contexts) + new-objs (apply disjoint-union (map objects contexts)) + new-inz (fn [[[g i] m]] + ((incidence (nth contexts i)) [g m]))] + (make-incomplete-context new-objs (attributes (first contexts)) new-inz)) + ) + ([cxt1 & contexts] + (incomplete-context-subposition (into [cxt1] contexts)))) + + +(defn subposition + "computes the subposition of a collection of incomplete formal contexts + incomplete-contexts: a collection of incomplete contexts" + [incomplete-contexts] + (let [M (attributes (first incomplete-contexts)) + [objs inz] (->> incomplete-contexts + (map-indexed (fn [i cxt] [i (objects cxt) (incidence cxt)])) + (map (fn [[i objs inci]] [i + (into #{} (map #(vector i %) objs)) + (into {} (map (fn [[[g m] v]] [[[i g] m] v]) inci))])) + (map (fn [[i o inz]] [o inz])) + (reduce (fn [[o1 inz1] [o2 inz2]] [(into o1 o2) (into inz1 inz2)])))] + (make-incomplete-context objs M inz) + )) + + +(defn incomplete-context-subposition-with-disjoint-objectsets + "computes the subposition of a collection of incomplete formal contexts + incomplete-contexts: a collection of incomplete contexts" + [incomplete-contexts] + (let [M (attributes (first incomplete-contexts)) + [objs inz] (->> incomplete-contexts + (map-indexed (fn [i cxt] [i (objects cxt) (incidence cxt)])) + (map (fn [[i o inz]] [o inz])) + (reduce (fn [[o1 inz1] [o2 inz2]] [(into o1 o2) (into inz1 inz2)])))] + (make-incomplete-context objs M inz) + )) + + +(defn example-with-attribute-sets->example-with-incidence-map + "Input: set of all attributes M and example [g #{atts-had} #{atts-not-had}] + Output: [g inz] where inz = {[g m1] xo?, [g m2] xo?, ...}" + [M [g atts-had atts-not-had]] + [g (into {} (for [m M] (cond (contains? atts-had m) + [[g m] known-true] + (contains? atts-not-had m) + [[g m] known-false] + true + [[g m] unknown])))]) + + +(defn add-row-to-incomplete-context + "input incomplete context and an object with a map for all object attribute combinations [g {[g m1] value1 [g m2] value2 ...}] or a triple of object, attributes had and attributes not had [g #{atts-had} #{atts-not-had}]" + [cxt row] + (cond + (= (count row) 2) + (let [[g inz] row] + (assert (not (contains? (objects cxt) g)) "The object already exists in this context.") + (assert (map? inz) "The incidence is not a map containing a mapping for each attribute for the object") + (assert (= (into #{} (attributes cxt)) (into #{} (map second (keys inz)))) "The incidence is not a map containing a mapping for each attribute for the object") + (make-incomplete-context (conj (objects cxt) g) + (attributes cxt) + (into (incidence cxt) (fmap map-true-false-unknown-to-x-o-? inz)))) + (= (count row) 3) + (let [M (attributes cxt)] + (add-row-to-incomplete-context cxt (example-with-attribute-sets->example-with-incidence-map M row))) + true + (throw (AssertionError. "Wrong Input")))) + + +(defn add-or-update-object-incomplete-context + "input incomplete context and an object with a map for all object attribute combinations [g {[g m1] value1 [g m2] value2 ...}] or a triple of object, attributes had and attributes not had [g #{atts-had} #{atts-not-had}]" + [cxt row] + (cond + (= (count row) 2) + (let [[g inz] row] + (assert (map? inz) "The incidence is not a map containing a mapping for each attribute for the object") + (assert (= (into #{} (attributes cxt)) (into #{} (map second (keys inz)))) "The incidence is not a map containing a mapping for each attribute for the object") + (make-incomplete-context (conj (objects cxt) g) + (attributes cxt) + (into (incidence cxt) (fmap map-true-false-unknown-to-x-o-? inz)))) + (= (count row) 3) + (let [M (attributes cxt)] + (add-or-update-object-incomplete-context cxt (example-with-attribute-sets->example-with-incidence-map M row))) + true + (throw (AssertionError. "Wrong Input")))) + + +(defn incomplete-context-union + "compute the union of two incomplete contexts with disjunct object sets and a common attribute set " + [cxt1 cxt2] + (let [o1 (objects cxt1) + o2 (objects cxt2) + a1 (attributes cxt1) + a2 (attributes cxt2) + i1 (incidence cxt1) + i2 (incidence cxt2)] + (assert (= a1 a2) "Attribute sets differ") + (assert (= #{} (intersection o1 o2)) "Object sets are not disjunct") + (make-incomplete-context (union o1 o2) a1 (into i1 i2)))) + + +(defn incomplete-context-supremum + "compute the supremum of two incomplete contexts with respect to the information order, i.e., x > ? and o > ? but x and o are incomparable. + Fails if the supremum of x and o is constructed" + [cxt1 cxt2] + (let [o1 (objects cxt1) + o2 (objects cxt2) + a1 (attributes cxt1) + a2 (attributes cxt2) + i1 (incidence cxt1) + i2 (incidence cxt2) + inew (into {} + (for [g (union o1 o2) + m (union a1 a2)] + [[g m] (information-supremum (get i1 [g m]) + (get i2 [g m]))]))] + (make-incomplete-context (union o1 o2) + (union a1 a2) + inew))) + + + +(defn certain-object-derivation + "Computes set of attributes certainly common to all objects" + [ctx objects] + (let [inz (incidence ctx) + atts (attributes ctx)] + (set-of m [m atts :when (forall [g objects] (= (inz [g m]) known-true))]))) + +(defalias obox certain-object-derivation) +(defalias certain-intent certain-object-derivation) +(defalias attributes-had certain-object-derivation) + + +(defn possible-object-derivation + "Computes set of attributes possibly common to all objects" + [ctx objects] + (let [inz (incidence ctx) + atts (attributes ctx)] + (set-of m [m atts :when (forall [g objects] (contains? #{known-true unknown} (inz [g m])))]))) + +(defalias odiamond possible-object-derivation) +(defalias possible-intent possible-object-derivation) + + +(defn certain-attribute-derivation + "Computes the set of objects that certainly have all attributes" + [ctx attributes] + (let [inz (incidence ctx) + objs (objects ctx)] + (set-of g [g objs :when (forall [m attributes] (= (inz [g m]) known-true))]))) + +(defalias abox certain-attribute-derivation) +(defalias certain-extent certain-attribute-derivation) + + +(defn possible-attribute-derivation + "Computes the set of objects that possibly have all attributes" + [ctx attributes] + (let [inz (incidence ctx) + objs (objects ctx)] + (set-of g [g objs :when (forall [m attributes] (contains? #{known-true unknown} (inz [g m])))]))) + +(defalias adiamond possible-attribute-derivation) +(defalias possible-extent possible-attribute-derivation) + + +(defn possible-intent-of-certain-extent + "" + [cxt attributes] + (possible-intent cxt (certain-extent cxt attributes))) + +(defalias abox->odiamond possible-intent-of-certain-extent) +(defalias possibly-implied-attributes possible-intent-of-certain-extent) +(defalias ABoxODiamond possible-intent-of-certain-extent) + + +(defn possibly-holds? + "Returns true iff impl is satisfiable in given context ctx." + [impl ctx] + (subset? (conclusion impl) (possibly-implied-attributes ctx (premise impl)))) + +(defalias satisfiable? possibly-holds?) + + + +(defn attributes-not-had + "given an object and a context returns the attributes that the object does not have" + [context objs] + (let [inz (incidence context) + attrs (attributes context)] + (set-of m [m attrs :when (forall [g objs] (= (inz [g m]) known-false))]))) + + + + +(defn is-counterexample? + "given context K, implication impl and object g + return true if g is a counterexample for impl in K + and false if it is not" + [K impl g] + (let [prem (set (premise impl)) + concl (set (conclusion impl))] + (and + (subset? prem (obox K [g])) + (not (empty? (intersection concl (difference (set (attributes K)) + (odiamond K [g])))))))) + +(defn is-potential-counterexample? + "given context K, implication impl and object g + return true if g is a potential counterexample for impl in K + and false if it is not" + [K impl g] + (let [prem (set (premise impl)) + concl (set (conclusion impl))] + (and + (subset? prem (odiamond K [g])) + (not (empty? (intersection concl (difference (set (attributes K)) + (obox K [g])))))))) + +(defn counterexamples-subcontext + "Input: incomplete context K, implication impl + Output: incomplete subcontext of counterexamples" + [K impl] + (let [counterexamples (filter (partial is-counterexample? K impl) (objects K))] + (incomplete-subcontext K counterexamples (attributes K)))) + +(defn potential-counterexamples-subcontext + "Input: incomplete context K, implication impl + Output: incomplete subcontext of potential counterexamples" + [K impl] + (let [counterexamples (filter (partial is-potential-counterexample? K impl) (objects K))] + (incomplete-subcontext K counterexamples (attributes K)))) + + + + +;;;;;;;;;;;;;;;;;;;;;;;;; + +(defprotocol LaTeX + "Implements conversion to latex code." + (latex [this] [this choice] [this choice args] "Returns a string representation of this.")) + + +(extend-type Object + LaTeX + (latex + ([this] + (.toString this)) + ([this choice] + (.toString this)) + ([this choice args] + (.toString this)))) + +(defn incidence->fcasty-out + "converts x to X; o to . and ? to ? for burmeister output" + [in] + (if (= in known-true) "x" (if (= in known-false) "." (if (= in unknown) "?" (throw "input not x,o,?"))))) + + + +(extend-type Incomplete-Context + LaTeX + (latex + ([this] + (latex this :fcasty)) + ([this choice] + (latex this choice {})) + ([this choice args] + (case choice + :plain (with-out-str + (println (str "\\begin{array}{l||*{" (count (attributes this)) "}{c|}}")) + (doseq [m (attributes this)] + (print (str "& \\text{" m "}"))) + (println "\\\\\\hline\\hline") + (doseq [g (objects this)] + (print (str g)) + (doseq [m (attributes this)] + (print (str "& " (if ((incidence this) [g m]) "\\times" "\\cdot")))) + (println "\\\\\\hline")) + (println (str "\\end{array}"))) + :fcasty (let [attrs (sort (attributes this)) + objs (sort (objects this)) + inz (incidence this)] + (with-out-str + (println "\\begin{cxt}") + (if (:name args) + (println " \\cxtName{" (:name args) "}") + (println " \\cxtName{}")) + (println " \\cxtNichtKreuz{}") + (doseq [m attrs] + (if (< (count (str m)) 4) + (println (str " \\att{" m "}")) + (println (str " \\atr{" m "}"))) + ) + (doseq [g objs] + (print " \\obj{") + (doseq [m attrs] + (print (incidence->fcasty-out (inz [g m])))) + (println (str "}{" g "}")) + ) + (println "\\end{cxt}") + )) + true (illegal-argument "Unsupported latex format " choice " for contexts."))))) + + +(extend-type Implication + LaTeX + (latex + ([this] + (str (if (empty? (premise this)) + "$\\emptyset$" + (sort (into [] (premise this)))) + " $\\rightarrow$ " (sort (into [] (conclusion this))))))) + +nil + diff --git a/src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts_exploration.clj b/src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts_exploration.clj new file mode 100644 index 000000000..fa4530af8 --- /dev/null +++ b/src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts_exploration.clj @@ -0,0 +1,336 @@ +(ns conexp.fca.incomplete-contexts.incomplete-contexts-exploration + "Incomplete-Context-Exploration" + (:require [clojure.set :refer [subset? intersection difference union]] + [clojure.core.reducers :as r] + [conexp.base :refer [ask exists next-closed-set]] + [conexp.fca.implications :refer :all] + [conexp.fca.incomplete-contexts.incomplete-contexts :refer :all])) + + +;; TODO validate counterexample +(defn- counterexample-from-expert + [implication context] + (let [pre (premise implication) + conc (conclusion implication) + attributes (attributes context) + objects (objects context) + counterexample (ask (str "Please enter a counterexample for \n\t(" pre " \u2192 " conc ")\n in the following form: \n\t\"name\" [\"attributes it has\"] [\"attributes it has not\"]\n") + #(read-string (str "[" (read-line) "]")) + #(and (not (empty? (re-find #"([^\[\]\t\n]*)(\[[^\[\]\t\n]*\])(\[[^\[\]\t\n]*\])" (str 2 " " ['a 'b] ['c 'd])))) ; check for form obj [atts][atts] + (subset? (nth % 1) attributes) + (subset? (nth % 2) attributes) + (not (contains? objects (nth % 0))) + (empty? (intersection (set (nth % 1)) (set (nth % 2))))) + "something went wrong") + out [(str (first counterexample)) (set (nth counterexample 1)) (set (nth counterexample 2))]] + out)) + + + + +(defn- unknown-attributes-from-expert + "ask the expert for the attributes from the conclusion where the implication holds." + [implication] + (let [pre (premise implication) + conc (conclusion implication) + premise-implies (ask (str "For which attributes m " \u2208 " " conc " does " pre " " \u2192 " m hold?\n Simply press RETURN if it holds for none or you do not know.\n") + #(read-string (str "#{" (read-line) "}")) + #(subset? % conc) + (str "Please enter the attributes m " \u2208 " " conc " where (" pre " " \u2192 " m) holds.")) + unknown (difference conc premise-implies)] + {:premise-implies premise-implies :unknown unknown})) + + +(defn- yes-no-unknown-abort? + "Asks string, expecting 'yes', 'no', 'unknown' or 'abort'. Returns the corresponding input." + [question] + (ask (str question) + #(read-string (str (read-line))) + #{'yes 'no 'unknown 'abort} + "Please answer 'yes', 'no' or 'unknown' (or 'abort'): \n")) + + +(defn- expert-interaction [initial-state] + "Implements expert interaction. The initial state is given by + «initial-state», from which on the expert is queried for commands that modify + this state. If eventually one command returns nil, a vector is returned + which arises from applying all functions in the sequence «result-fns» to + state, collecting the return values in the vector. In other words, + «result-fns» is a sequence of functions, which are applied to the final state + in the sequence given, and their return values (collected in a vector) + constitute the return value of the call to this function." + (let [answer (yes-no-unknown-abort? (str "Does the implication " + (print-str (:implication initial-state)) + " hold? (yes/no/unknown) "))] + (try + (cond + ;; counterexample + (= answer 'no) + (let [counterexample (counterexample-from-expert + (:implication initial-state) + (:examples-context initial-state))] + [:counterexample [counterexample]]) + ;; implication + (= answer 'yes) + [:implication] + ;; unknown + (= answer 'unknown) + (do + (let [unknowns (unknown-attributes-from-expert (:implication initial-state))] + [:unknown unknowns])) + ;; abort interaction + (= answer 'abort) + :abort + ;; failsave + true + (throw "impossible answer recieved...")) + (catch Exception e + :abort)))) + + +(defn- incomplete-counterexamples-via-repl + "Starts a repl for counterexamples, which may be incomplete." + [examples-context knowledge impl] + (expert-interaction {:examples-context examples-context + :knowledge knowledge + :implication impl})) + + +;; TODO add proper check at input + +(defn- valid-counterexample? + "Checks the given example for being valid." + [state attributes impl] + (let [new-atts (union (set (:positives state)) (set (:negatives state))) + return (atom true)] + (when-not (contains? state :object), + (println "You need to set a name for the new object.") + (reset! return false)) + (when (and (:complete-counterexamples? state) + (not= attributes new-atts)) + (println "You have not specified a complete counterexample.") + (reset! return false)) + (let [not-respected (filter (fn [impl] + (not (respects? (set (:positives state)) impl))) + (:knowledge state))] + (when-not (empty? not-respected) + (println "Your example does not respect the following confirmed implications:") + (doseq [impl not-respected] + (println impl)) + (reset! return false))) + (when-not (and (subset? (premise impl) (set (:positives state))) + (exists [m (:negatives state)] + (contains? (conclusion impl) m))) + (println "Your example does not falsify the given implication.") + (reset! return false)) + @return)) + + +;;; Exploration +(defn- incidences-from-incomplete-counterexamples + [counterexamples examples-context] + (let [atts (attributes examples-context)] + (into {} (r/map + (fn [ex] (let [ + [obj positives negatives] ex] + (r/reduce #(let [[g m w] %2] (assoc %1 [g m] w)) + {} + (r/map (fn [att] + (let [value (if (contains? positives att) known-true + (if (contains? negatives att) known-false unknown))] + [obj att value])) atts)))) + counterexamples)))) + + + +(defn- incidences-from-incomplete-counterexamples-OLD + [counterexamples examples-context] + (into {} (map (fn [ex] (let [atts (attributes examples-context) + [obj positives negatives] ex] + (reduce #(let [[g m w] %2] (assoc %1 [g m] w)) + {} + (map (fn [att] + (let [value (if (contains? positives att) known-true + (if (contains? negatives att) known-false unknown))] + [obj att value])) atts)))) + counterexamples))) + + +(defn- make-ficticious-examples + "generates for all unknown attributes m a ficticious counterexample + that has the form [\"g_(premise,m)\" premise #{m}]" + [impl unknown-attributes] + (let [pre (premise impl)] + (reduce #(into %1 [[(str "$g_{" "\\text{"(clojure.string/join "," pre) "}\\not\\rightarrow \\text{" %2 "} }$") pre #{%2}]]) [] unknown-attributes))) + + +(defn- make-known-true-incidences + "In: seq [obj #{attributes}]" + [in] + (persistent! (reduce #(assoc! %1 [(first in) %2] known-true) (transient {}) (second in)))) + + +(defn- make-incidences-for-known-?-from-objects + [objs context clop] + (reduce #(into %1 (make-known-true-incidences %2)) + {} + (map (fn [g] (let [atts (certain-intent context #{g})] + [g (clop atts)])) + objs))) + + +(defn- explore-attributes-with-unknown-and-incomplete-counterexamples + "Performs attribute exploration allowing for incomplete counterexamples" + [incomplete-ctx background-knowledge handler] + (let [M (attributes incomplete-ctx)] + (loop [implications background-knowledge + A (close-under-implications implications #{}) + examples-context incomplete-ctx + ficticious-examples #{} + qa-pairs []] + (cond + ;; exploration finished + (not A) + {:implications (difference implications background-knowledge), + :examples-context examples-context + :ficticious-examples ficticious-examples + :question-answer-pairs qa-pairs} + ;; exploration continues + true + (let [conclusion-from-A (odiamond examples-context (abox examples-context A))] + (if (= A conclusion-from-A) ;; check satisfiability + (recur implications + (next-closed-set M + (clop-by-implications implications) + A) + examples-context + ficticious-examples + qa-pairs) + (let [new-impl (make-implication A conclusion-from-A) + answer (handler examples-context implications new-impl)] + (cond + (= answer :abort) ; abort exploration + (recur implications nil examples-context ficticious-examples qa-pairs) + ;; + (= (first answer) :counterexample) ; add counterexample + (let [counterexamples (second answer) + new-objs (map first counterexamples) + new-incidences (incidences-from-incomplete-counterexamples + counterexamples examples-context) + ;; collect all counterexamples in one context + ccxtSup (reduce incomplete-context-supremum + (for [c counterexamples] + (make-single-object-incomplete-context (first c) M (second c) (last c)))) + ;; construct the subposition context from old and new examples + examples-context (incomplete-context-supremum examples-context ccxtSup) + + ;; ?-reduce the non-ficticious objects + clop (clop-by-implications implications) + ficticious-objects (into #{} (map first ficticious-examples)) + + ;; reduced-incidences (make-incidences-for-known-?-from-objects + ;; (difference (union new-objs (objects examples-context)) + ;; ficticious-objects) examples-context clop) + ;; updated-incidences (into (into (incidence examples-context) new-incidences) + ;; reduced-incidences) + + reduced-incidences (make-incidences-for-known-?-from-objects + (difference (objects examples-context) + ficticious-objects) examples-context clop) + updated-incidences (into (incidence examples-context) reduced-incidences) + + updated-objs (into (objects examples-context) new-objs) + + updated-examples-context (make-incomplete-context updated-objs + M + updated-incidences) + next-set A + ;; _ (println updated-examples-context) + ] + (recur implications + next-set + updated-examples-context + ficticious-examples + (conj qa-pairs [new-impl :false new-objs]))) + (= (first answer) :implication) ; add implication + (let [new-implications (conj implications new-impl) + new-clop (clop-by-implications new-implications) + ficticious-objects (into #{} (map first ficticious-examples)) + reduced-incidences (make-incidences-for-known-?-from-objects + (difference (objects examples-context) ficticious-objects) + examples-context new-clop) + updated-incidences (into (incidence examples-context) reduced-incidences) + updated-examples-context (make-incomplete-context (objects examples-context) + M + updated-incidences) + next-set (next-closed-set M new-clop A)] + (recur new-implications + next-set + updated-examples-context + ficticious-examples + (conj qa-pairs [new-impl :true]))) + (= (first answer) :unknown) + (let [premise-implies (:premise-implies (second answer)) + unknowns (:unknown (second answer)) + new-implx (make-implication A premise-implies) + updated-implications (if (empty? (conclusion new-implx)) + implications + (conj implications new-implx)) + new-clop (clop-by-implications updated-implications) + implied-by-A (new-clop A) + unknown-not-implied (difference unknowns implied-by-A) + updated-ficticious-examples (into ficticious-examples + (make-ficticious-examples new-impl unknown-not-implied)) + ficticious-objects (into #{} (map first updated-ficticious-examples)) + updated-objects (into (objects examples-context) ficticious-objects) + new-incidences (incidences-from-incomplete-counterexamples + updated-ficticious-examples examples-context) + reduced-incidences (make-incidences-for-known-?-from-objects + (difference (objects examples-context) ficticious-objects) + examples-context new-clop) + updated-incidences (into (into (incidence examples-context) new-incidences) + reduced-incidences) + new-examples-context (make-incomplete-context updated-objects + M + updated-incidences) + next-set (if (empty? unknown-not-implied) + (next-closed-set M new-clop A) + A) + ] + (recur updated-implications + next-set + new-examples-context + updated-ficticious-examples + (conj qa-pairs [new-impl :unknown unknown-not-implied]))))))))))) + + +(defn default-handler-for-incomplete-counterexamples + "Default handler for attribute exploration with incomplete counterexamples. Does it's + interaction on the console." + [examples-context known impl] + (incomplete-counterexamples-via-repl examples-context known impl)) + + +(defn explore-attributes + "Performs attribute exploration on the given context(s). Returns a hashmap of + implications computed and the final context, stored with keys :implications + and :context (in the case of complete counterexamples) + or :possible-context/:certain-context (in the case of incomplete counterexamples), + respectively. + + Arguments are passed as keyword arguments like so + (explore-attributes + :examples-context context + :background-knowledge set-of-implications + " + [& {:keys [examples-context background-knowledge handler]}] + ;; first check for arguments + (assert (not (nil? examples-context)) + "examples-context must be given (can be a context without objects)") + + ;; dispatch + (explore-attributes-with-unknown-and-incomplete-counterexamples + examples-context + (or background-knowledge #{}) + (or handler default-handler-for-incomplete-counterexamples))) + diff --git a/src/main/clojure/conexp/fca/incomplete_contexts/random_experts.clj b/src/main/clojure/conexp/fca/incomplete_contexts/random_experts.clj new file mode 100644 index 000000000..a25a37198 --- /dev/null +++ b/src/main/clojure/conexp/fca/incomplete_contexts/random_experts.clj @@ -0,0 +1,117 @@ +(ns conexp.fca.incomplete-contexts.random-experts + (:require [clojure.set :refer [subset? intersection difference union]] + [conexp.base :refer [with-str-out cross-product hash-combine-hash]] + [conexp.fca.implications :refer :all] + [conexp.fca.contexts :as cxt] + [conexp.fca.random-contexts :as rcxt] + [conexp.fca.incomplete-contexts.conexp-interop :refer :all] + [conexp.fca.incomplete-contexts.incomplete-contexts :as icxt :refer :all] + [conexp.fca.incomplete-contexts.experts :refer :all])) + + +(defn induce-random-questionmarks + "Given a formal or incomplete context introduce random '?'s, by generating a random context and use its crosses as incidences of the '?'" + [K] + (let [partial-cxt (to-incomplete-context K) + objs (icxt/objects partial-cxt) + atts (icxt/attributes partial-cxt) + unknown-incidences (into {} (for [[g m] (cxt/incidence-relation (rcxt/random-dirichlet-context :attributes atts :objects objs))] + [[g m] icxt/unknown])) + new-inz (-> (to-incomplete-context K) + (icxt/incidence) + (#(merge % unknown-incidences)))] + (icxt/make-incomplete-context objs atts new-inz) + )) + + +(defn- contains-maximal-contranominal-scale? + "Given a formal context K, computes whether K contains the largest contranominal scale, i.e., if there are objects such that each set of |M|-1 attributes is an intent." + [K] + (let [K (to-formal-context K) + M (set (cxt/attributes K)) + G (set (cxt/objects K))] + (loop [g (first G) + restG (rest G) + S (set (for [m M] (difference M #{m})))] + (cond (empty? S) + true + (nil? g) + false + true + (recur (first restG) + (rest restG) + (difference S #{(set (cxt/object-derivation K #{g}))})))))) + + +(defn random-satisfiable-implication-theory-for-cxt + "Given a formal or incomplete context K, create an implication theory of satisfiable implications. + This works by generating a random context on the same set of attributes, forming the subposition and computing the canonical base. + If 'retry-on-empty-result' is set to true we repeat the generation if the result would be empty because the random context contains a contranominal scale. + Note: If K already contains a contranominal scale the result will always be an empty set." + ([K] + (random-satisfiable-implication-theory-for-cxt K true)) + ([K retry-on-empty-result] + (let [K1 (incomplete-context->possible-incidences-context (to-incomplete-context K)) + K2 (if (and retry-on-empty-result (not (contains-maximal-contranominal-scale? K1))) + (loop [trys 5 + genK (rcxt/random-dirichlet-context-no-maximal-contranominal :attributes (cxt/attributes K1))] + (if (and (contains-maximal-contranominal-scale? (icxt/subposition [(to-incomplete-context K1) (to-incomplete-context genK)])) (< 0 trys)) + (recur (dec trys) + (rcxt/random-dirichlet-context-no-maximal-contranominal :attributes (cxt/attributes K1))) + genK)) + (rcxt/random-dirichlet-context :attributes (cxt/attributes K1))) + subposK (icxt/subposition [(to-incomplete-context K1) (to-incomplete-context K2)])] + (canonical-base (to-formal-context subposK)) + ))) + + +(defn make-incomplete-expert-from-cxt + "randomly decide which relations and which implications the expert knows about (the certainly valid implications ?)" + ([cxt] + (let [base (canonical-base cxt) + cxt-part (induce-random-questionmarks cxt) + impls-part (take (rand-int (inc (count base))) (shuffle base))] + (make-expert impls-part cxt-part)))) + + +(defn make-random-expert-for-universe + "make a random expert for a universe K" + ([K] + (let [K1 (induce-random-questionmarks K) + impls (random-satisfiable-implication-theory-for-cxt K)] + (make-expert impls K1))) + ([K name] + (let [K1 (induce-random-questionmarks K) + impls (random-satisfiable-implication-theory-for-cxt K)] + (make-expert impls K1 name)))) + + +(defn- rand-int-between-min-times-and-max-times-n + [n min-times max-times] + {:pre [(<= min-times max-times)]} + (Math/round (* n (+ min-times (rand (- max-times min-times)))))) + + +(defn make-random-expert-for-domain + "make a random expert for a domain + Input: either a formal context K or a pair [G M] of objects and attributes" + ([K] + (let [iK (to-incomplete-context K)] + (make-random-expert-for-domain (icxt/objects iK) (icxt/attributes iK) (java.util.UUID/randomUUID)))) + ([K name] + (let [iK (to-incomplete-context K)] + (make-random-expert-for-domain (icxt/objects iK) (icxt/attributes iK) name))) + ([G M name] + (let [newG (rand-int-between-min-times-and-max-times-n (count G) 0.5 2) + K (rcxt/random-dirichlet-context-no-maximal-contranominal :attributes M :objects newG) + K1 (induce-random-questionmarks K) + impls (random-satisfiable-implication-theory-for-cxt K)] + (make-expert impls K1 name)))) + + +(defn- valid-expert? + [expert] + (let [impls (known-implications expert) + K (known-examples expert)] + (every? true? (for [L impls] (satisfiable? L K))))) + diff --git a/src/main/clojure/conexp/fca/lattices.clj b/src/main/clojure/conexp/fca/lattices.clj index 94547c4a7..20a3bfcd2 100644 --- a/src/main/clojure/conexp/fca/lattices.clj +++ b/src/main/clojure/conexp/fca/lattices.clj @@ -10,7 +10,8 @@ "Basis datastructure and definitions for abstract lattices." (:use conexp.base conexp.math.algebra - conexp.fca.contexts)) + conexp.fca.contexts + conexp.fca.posets)) ;;; Datastructure @@ -217,27 +218,15 @@ (if (order a x) a x)) (base-set lat)))) -(defn directly-neighboured? - "Checks whether x is direct lower neighbour of y in lattice lat." - [lat x y] - (let [order (order lat)] - (and (not= x y) - (order x y) - (let [base (disj (base-set lat) x y)] - (forall [z base] - (not (and (order x z) (order z y)))))))) - (defn lattice-upper-neighbours "Returns all direct upper neighbours of x in lattice lat." [lat x] - (set-of y [y (base-set lat) - :when (directly-neighboured? lat x y)])) + (poset-upper-neighbours lat x)) (defn lattice-lower-neighbours "Returns all direct lower neighbours of y in lattice lat." [lat y] - (set-of x [x (base-set lat) - :when (directly-neighboured? lat x y)])) + (poset-lower-neighbours lat y)) (defn lattice-atoms "Returns the lattice atoms of lat." @@ -291,6 +280,25 @@ (fn [x y] ((order lat) [x y])))) +(defmethod poset-context Lattice + [lattice] + (standard-context lattice)) + +(defn extract-context-from-bv + "Extracts the objects, attributes and incidence of a concept + lattice." + [lat] + (let [c (base-set lat)] + (assert (every? (fn [[a b]] (and (set? a) (set? b))) c) "Lattice is not a concept lattice") + (let [objects (apply max-key count (map first c)) + attributes (apply max-key count (map second c)) + i-fn (fn [a b] + (some (fn [[ext int]] (and (contains? ext a) + (contains? int b))) + c))] + (make-context objects attributes i-fn)))) + + ;;; TITANIC Implementation (defn- minimum diff --git a/src/main/clojure/conexp/fca/metrics.clj b/src/main/clojure/conexp/fca/metrics.clj index 7cba84742..6068d1650 100644 --- a/src/main/clojure/conexp/fca/metrics.clj +++ b/src/main/clojure/conexp/fca/metrics.clj @@ -74,6 +74,36 @@ (/ (counter #{} extent) (expt 2 (count extent))))) +(defn separation-index + "The concept separation is an importance measure for concepts. It + computes the size AxB (c-inc) relative to uncovered incidences Ax(M-B) + and (G-A)xB (o-inc). Max value is 1. + + Klimushkin M., Obiedkov S., Roth C. (2010) Approaches to the + Selection of Relevant Concepts in the Case of Noisy Data. In: Kwuida + L., Sertkaya B. (eds) Formal Concept Analysis. ICFCA 2010. Lecture + Notes in Computer Science, vol 5986. Springer, Berlin, + Heidelberg. https://doi.org/10.1007/978-3-642-11928-6_18" + [context concept] + (assert (context? context) + "First argument must be a formal context.") + (assert (and (vector? concept) + (= 2 (count concept)) + (concept? context concept)) + "Second argument must be a formal concept of the given context.") + (let [[extent intent] concept + c-inc (* (count extent) (count intent)) + g-inc (reduce + + (map + #(count (object-derivation context #{%})) + extent)) + a-inc (reduce + + (map #(count (attribute-derivation context #{%})) + intent)) + o-inc (- (+ g-inc a-inc) + c-inc)] ; is at least c-inc large + (/ c-inc o-inc))) + (def ^:dynamic *fast-computation* "Enable computation of concept probability with floating point arithmetic instead of rationals" @@ -89,30 +119,32 @@ (let [nr_of_objects (count (objects context)) n (if *fast-computation* (double nr_of_objects) nr_of_objects) M (attributes context) - B (second concept) ; intent of concept + B (second concept) ; intent of concept P_M_B (mapv #(/ (count (attribute-derivation context #{%})) n ) (difference M B)) p_B (r/fold * (map #(/ (count (attribute-derivation context #{%})) n) B)) one_minus_p_B_n (expt (- 1 p_B) n)] - (loop [k 1 ;; since for k=0 the last term is 0, we can start with 1 - result 0 - binomial n ;; since k=0 the start binomial is n - p_B_k p_B - one_minus_p_B_k (/ one_minus_p_B_n (- 1 p_B)) - P_M_B_k P_M_B ] - (if (or (== k n) (== p_B_k 0)) ;; either done or underflowed probability (double) - result - (let [new_res - (* binomial p_B_k one_minus_p_B_k - (r/fold * (map #(- 1 %) P_M_B_k)))] - (recur - (inc k) - (+ new_res result) - (* binomial (/ (- n k) (inc k))) - (* p_B_k p_B) - (/ one_minus_p_B_k (- 1 p_B)) - (mapv (partial *) P_M_B_k P_M_B))))))) - - + (if (not= (double p_B) 1.0) ;; if concept's extent= n + (loop [k 1 ;; since for k=0 the last term is 0, we can start with 1 + result 0 + binomial n ;; since k=0 the start binomial is n + p_B_k p_B + one_minus_p_B_k (/ one_minus_p_B_n (- 1 p_B)) + P_M_B_k P_M_B ] + (if (or (== k n) (== p_B_k 0)) ;; either done or underflowed probability (double) + result + (let [new_res + (* binomial p_B_k one_minus_p_B_k + (r/fold * (map #(- 1 %) P_M_B_k)))] + (recur + (inc k) + (+ new_res result) + (* binomial (/ (- n k) (inc k))) + (* p_B_k p_B) + (/ one_minus_p_B_k (- 1 p_B)) + (mapv (partial *) P_M_B_k P_M_B))))) + 1))) + + ;;; Robustness of Concepts (defn- concept-robustness-add-next-entry @@ -660,8 +692,6 @@ (shannon-object-information-entropy-fast pctx)))) available-atts))) -(ns-unmap *ns* 'next-n-maximal-relevant-approx) - (defmulti next-n-maximal-relevant-approx "Compute next-n-maximal-relevant attributes in a consecutive manner using either Shannon or context entropy, cf diff --git a/src/main/clojure/conexp/fca/posets.clj b/src/main/clojure/conexp/fca/posets.clj new file mode 100644 index 000000000..c93be4897 --- /dev/null +++ b/src/main/clojure/conexp/fca/posets.clj @@ -0,0 +1,163 @@ +;; 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.posets + (:require [conexp.base :refer :all] + [conexp.math.algebra :refer :all] + [conexp.fca.contexts :refer :all])) + +(deftype Poset [base-set order-function] + Object + (equals [this other] + (and (= (class this) (class other)) + (= (.base-set this) (.base-set ^Poset other)) + (let [order-this (order this), + order-other (order other)] + (or (= order-this order-other) + (forall [x (.base-set this) + y (.base-set this)] + (<=> (order-this x y) + (order-other x y))))))) + (hashCode [this] + (hash-combine-hash Poset base-set)) + ;;; + Order + (base-set [this] base-set) + (order [this] + (fn order-fn + ([pair] (order-function (first pair) (second pair))) + ([x y] (order-function x y))))) + + + +(defmethod print-method Poset [^Poset poset, ^java.io.Writer out] + (.write out + ^String (str "Poset on " (count (base-set poset)) " elements."))) + +;;; Constructors + +(defn make-poset-nc + "" + [base-set order-relation] + (Poset. (to-set base-set) order-relation)) + +(defn has-partial-order? + "Given a poset checks if its order is indeed a partial order." + [poset] + (let [<= (order poset)] + (and (forall [x (base-set poset)] + (<= x x)) + (forall [x (base-set poset), + y (base-set poset)] + (=> (and (<= x y) + (<= y x)) + (= x y))) + (forall [x (base-set poset), + y (base-set poset), + z (base-set poset)] + (=> (and (<= x y) + (<= y z)) + (<= x z)))))) + +(defn make-poset + "Standard constructor for making a poset from a base set and an order + relation. + Note: This function will test the resulting poset for being one, + which may take some time. If you don't want this, use + make-poset-nc." + [base-set order-relation] + (let [poset (make-poset-nc base-set order-relation)] + (when-not (has-partial-order? poset) + (illegal-argument "Given arguments do not describe a poset.")) + poset)) + +;; Converter + +(defn poset-to-matrix + "Returns the relational matrix of the base set as one continous vector. + The function either takes only a poset and or the poset and a presorted + base set." + ([poset] + (poset-to-matrix poset (vec (base-set poset)))) + ([poset base] + (let [ord (order poset)] + (vec + (for [x base y base] + (if (ord x y) 1 0)))))) + +;; Context + +(defmulti poset-context + "Computes the context of an ordered set." + (fn [poset] (type poset))) + +(defmethod poset-context :default + [poset] + (make-context (base-set poset) + (base-set poset) + (fn [A B] + ((order poset) [A B])))) + +(defmethod poset-context Poset + [poset] + (make-context (base-set poset) + (base-set poset) + (fn [A B] + ((order poset) [A B])))) + +;; Neighbours + +(defn directly-neighboured? + "Checks whether x is direct lower neighbour of y in poset." + [poset x y] + (let [order (order poset)] + (and (not= x y) + (order x y) + (let [base (disj (base-set poset) x y)] + (forall [z base] + (not (and (order x z) (order z y)))))))) + +(defn poset-upper-neighbours + "Returns all direct upper neighbours of x in poset." + [poset x] + (set-of y [y (base-set poset) + :when (directly-neighboured? poset x y)])) + +(defn poset-lower-neighbours + "Returns all direct lower neighbours of y in poset." + [poset y] + (set-of x [x (base-set poset) + :when (directly-neighboured? poset x y)])) + +(defn order-ideal + "Returns the order ideal of a set x in poset." + [poset x] + (assert (subset? x (base-set poset)) + "x must be a subset of the ordered set.") + (loop [ideal #{} + lower-neighbours x + next-element (first (difference lower-neighbours ideal))] + (if (nil? next-element) + ideal + (recur (union ideal #{next-element}) + (union lower-neighbours (poset-lower-neighbours poset next-element)) + (first (difference lower-neighbours ideal)))))) + +(defn order-filter + "Returns the order filter of a set x in poset." + [poset x] + (assert (subset? x (base-set poset)) + "x must be a subset of the ordered set.") + (loop [filter #{} + upper-neighbours x + next-element (first (difference upper-neighbours filter))] + (if (nil? next-element) + filter + (recur (union filter #{next-element}) + (union upper-neighbours (poset-upper-neighbours poset next-element)) + (first (difference upper-neighbours filter)))))) diff --git a/src/main/clojure/conexp/fca/pqcores.clj b/src/main/clojure/conexp/fca/pqcores.clj index 5c5e5db01..ea3f70d78 100644 --- a/src/main/clojure/conexp/fca/pqcores.clj +++ b/src/main/clojure/conexp/fca/pqcores.clj @@ -14,7 +14,6 @@ [base-set]] [conexp.fca.lattices :refer [concept-lattice]] - [conexp.fca.fast :as fast] [conexp.fca.cover :refer :all] [clojure.core.reducers :as r])) @@ -213,69 +212,3 @@ [ctx k] (apply core-lattice-sizes ctx (find-size-core ctx))) -;;; general transformer - -(defn transform-bv - "Transforms the concept lattice of ctx1 to that of ctx2 using the - algorithm presented in 'Knowledge Cores in Large Formal Contexts' - The methods input are the two contexts and the concept lattice of - ctx1 as cover relation generated by cover.clj. For ctx1 and ctx2 it - required that there exists a super context ctx such that ctx1 and - ctx2 are induced subcontexts of ctx. - - This method is useful for larger contexts ctx1 and ctx2 which have - many objects and attributes in common." - [ctx1 ctx2 bv1] - (let [;; update the set of attributes of the ctx and bv first - shared-attributes (intersection (attributes ctx2) (attributes ctx1)) - remove-ctx1-attributes-bv (attribute-deletion-cover bv1 ctx1 (difference (attributes ctx1) shared-attributes)) - ctx2-only-attributes (difference (attributes ctx2) (attributes ctx1)) - attr-intermediate-ctx (make-context (objects ctx1) (attributes ctx2) - (fn [a b] (if (contains? (objects ctx2) a) - ((incidence ctx2) [a b]) - ((incidence ctx1) [a b])))) - insert-ctx2-attributes-bv (attribute-insertion-cover remove-ctx1-attributes-bv attr-intermediate-ctx ctx2-only-attributes) - ;; To update all objects, we update the attributes of the dual - ;; context - dual-bv (dual-concept-cover insert-ctx2-attributes-bv) - shared-objects (intersection (objects ctx1) (objects ctx2)) - dual-ctx (dual-context attr-intermediate-ctx) - remove-ctx1-objects-bv (attribute-deletion-cover - dual-bv (make-context shared-objects (attributes dual-ctx) (incidence dual-ctx)) - (difference (objects ctx1) shared-objects)) - ctx2-only-objects (difference (objects ctx2) (objects ctx1)) - insert-ctx2-objects-bv (attribute-insertion-cover remove-ctx1-objects-bv (dual-context ctx2) ctx2-only-objects)] - (dual-concept-cover insert-ctx2-objects-bv))) - -(defn transform-bv-subctx - "Transforms the concept lattice of ctx1 to that of ctx2 using the - algorithm presented in 'Knowledge Cores in Large Formal Contexts' - The methods input are the two contexts and the concept lattice of - ctx1 as cover relation generated by cover.clj. For ctx1 and ctx2 it - required that ctx2 is a subcontext of ctx1 - - This method is useful for larger contexts ctx1 and ctx2 which have - many objects and attributes in common." - [ctx1 ctx2 bv1] - (let [;; update the set of attributes of the ctx and bv first - shared-attributes (attributes ctx2) - remove-ctx1-attributes-bv (attribute-intersection-cover bv1 shared-attributes) - - ;; To update all objects, we update the attributes of the dual - ;; context - dual-bv (dual-concept-cover remove-ctx1-attributes-bv) - shared-objects (objects ctx2) - remove-ctx1-objects-bv (attribute-intersection-cover dual-bv shared-objects)] - (dual-concept-cover remove-ctx1-objects-bv))) - -(defn transform-bv-intents-cores - "Transforms the intent lattice of ctx1 to that of ctx2 using the - algorithm presented in 'Knowledge Cores in Large Formal Contexts' - The methods input are the two contexts and the concept lattice of - ctx1 as cover relation generated by cover.clj. For ctx1 and ctx2 it - required that ctx2 is a pkcore of ctx1. - - This method is useful for larger contexts ctx1 and ctx2 which have - many objects and attributes in common." - [ctx1 core bv1 p] - (cover-reducer ctx1 core p)) diff --git a/src/main/clojure/conexp/fca/protoconcepts.clj b/src/main/clojure/conexp/fca/protoconcepts.clj new file mode 100644 index 000000000..e6d429712 --- /dev/null +++ b/src/main/clojure/conexp/fca/protoconcepts.clj @@ -0,0 +1,93 @@ +;; 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.protoconcepts + "Basis datastructure and definitions for protoconcepts. + DOI:https://doi.org/10.1007/11528784_2" + (:require [conexp.base :refer :all] + [conexp.math.algebra :refer :all] + [conexp.fca.contexts :refer :all] + [conexp.fca.lattices :refer :all] + [conexp.fca.posets :refer :all])) + +(deftype Protoconcepts [base-set order-function] + Object + (equals [this other] + (and (= (class this) (class other)) + (= (.base-set this) (.base-set ^Protoconcepts other)) + (let [order-this (.order this), + order-other (.order other)] + (or (= order-this order-other) + (forall [x (.base-set this) + y (.base-set this)] + (<=> (order-this x y) + (order-other x y))))))) + (hashCode [this] + (hash-combine-hash Protoconcepts base-set)) + ;; + Order + (base-set [this] base-set) + (order [this] + (fn order-fn + ([pair] (order-function (first pair) (second pair))) + ([x y] (order-function x y))))) + +(defmethod print-method Protoconcepts [^Protoconcepts protoconcepts, ^java.io.Writer out] + (.write out + ^String (str "Protoconcepts on " (count (base-set protoconcepts)) " elements."))) + +(defn protoconcept? + "Tests whether given pair is protoconcept in given context ctx." + [ctx [set-of-obj set-of-att]] + (or (= (object-derivation ctx set-of-obj) + (context-attribute-closure ctx set-of-att)) + (= (context-object-closure ctx set-of-obj) + (attribute-derivation ctx set-of-att)))) + +(defn protoconcepts? + "Tests whether given tuples all are protoconcepts in given context ctx." + [ctx protoconcepts] + (every? (partial protoconcept? ctx) protoconcepts)) + +(defn protoconcepts + "Computes all protoconcepts of a context." + [ctx] + (let [object-equivalence-classes (group-by #(object-derivation ctx %) (subsets (objects ctx))) + attribute-equivalence-classes (group-by #(context-attribute-closure ctx %) (subsets (attributes ctx)))] + (into #{} (apply concat (map (fn [key] + (for [obj (get object-equivalence-classes key) + attr (get attribute-equivalence-classes key)] + [obj attr])) (keys object-equivalence-classes)))))) + +(defn make-protoconcepts-nc + "Creates a new protoconcept order from the given base-set and order-function, without any checks." + [base-set order-function] + (Protoconcepts. base-set order-function)) + +(defn make-protoconcepts + "Creates a new protoconcepts order from the given base-set and order-function. + Checks if the result has partial order, which may take some time. If you don't want this, use make-protoconcepts-nc." + ([base-set order-function] + (let [protoconcepts (make-protoconcepts-nc base-set order-function)] + (when-not (has-partial-order? protoconcepts) + (illegal-argument "Given arguments do not describe a partial order.")) + protoconcepts)) + ;; checks if result only contains protoconcepts of context ctx + ([base-set order-function ctx] + (let [protoconcepts (make-protoconcepts base-set order-function)] + (when-not (protoconcepts? ctx (base-set protoconcepts)) + (illegal-argument "Given base-set and order-function do not describe protoconcepts in given context.")) + protoconcepts))) + +(defn protoconcepts-order + "Returns for a given context ctx its protoconcepts with order." + [ctx] + (make-protoconcepts-nc (protoconcepts ctx) + (fn <= [[A B] [C D]] + (and (subset? A C) + (subset? D B))))) diff --git a/src/main/clojure/conexp/fca/random_contexts.clj b/src/main/clojure/conexp/fca/random_contexts.clj index 6802fa422..a4ef21a2e 100644 --- a/src/main/clojure/conexp/fca/random_contexts.clj +++ b/src/main/clojure/conexp/fca/random_contexts.clj @@ -3,8 +3,9 @@ (:require [conexp.fca.implications :refer [canonical-base]] [conexp.base :refer [set-of exists forall => defalias]] [conexp.fca.contexts :as contexts] - [clojure.set :refer [subset?]] - [clojure.math.numeric-tower :refer :all] + [clojure.set :refer [subset? difference union intersection select]] + [clojure.math.numeric-tower :refer :all] + [clojure.math.combinatorics :refer [cartesian-product]] ) (:import [org.apache.commons.math3.distribution GammaDistribution @@ -164,6 +165,42 @@ dispatch-random-dirichlet-context) +(defn- contains-maximal-contranominal-scale? + "Given a formal context K, computes whether K contains the largest contranominal scale, i.e., if there are objects such that each set of |M|-1 attributes is an intent." + [K] + (let [M (set (contexts/attributes K)) + G (set (contexts/objects K))] + (loop [g (first G) + restG (rest G) + S (set (for [m M] (difference M #{m})))] + (cond (empty? S) + true + (nil? g) + false + true + (recur (first restG) + (rest restG) + (difference S #{(set (contexts/object-derivation K #{g}))})))))) + + +(defn random-dirichlet-context-no-maximal-contranominal + "Try to generate a random dirichlet context that contains no maximal contranominal scale. + Try at most 10 times. + The parameters are the same as for random-dirichlet-context." + [& {:keys [attributes objects base-measure precision-parameter]}] + (loop [try 10 + generatedK (random-dirichlet-context :attributes attributes + :objects objects + :base-measure base-measure + :precision-parameter precision-parameter)] + (if (and (contains-maximal-contranominal-scale? generatedK) (< 0 try)) + (recur (dec try) + (random-dirichlet-context :attributes attributes + :objects objects + :base-measure base-measure + :precision-parameter precision-parameter)) + generatedK))) + (defn- init-base-measure "normalizes base measure" [base-measure num_attributes] @@ -270,5 +307,126 @@ (contexts/make-context (map inc (range num_objects)) attr new-incidence))) - +(defn- filter-full-and-empty-rows-and-columns-once + "remove all full columns and full rows and return the respective subcontext" + [K] + (let [G (contexts/objects K) + M (contexts/attributes K) + Gfiltered (select (fn [g] (let [gprime (contexts/object-derivation K [g])] + (and (not= M gprime) (not= #{} gprime)))) + G) + Mfiltered (select (fn [m] (let [mprime (contexts/attribute-derivation K [m])] + (and (not= G mprime) (not= #{} mprime)))) + M)] + (contexts/make-context Gfiltered Mfiltered (contexts/incidence K)))) + + +(defn- filter-full-and-empty-rows-and-columns + "remove all full columns/rows repeatedly from the context K until each remaining column/row has at least one 'x' and one '.'. + return the set of objects and attributes that remain" + [K] + (loop [G (contexts/objects K) + M (contexts/attributes K) + K K] + (let [Kfiltered (filter-full-and-empty-rows-and-columns-once K) + Gfiltered (contexts/objects Kfiltered) + Mfiltered (contexts/attributes Kfiltered)] + (if (and (= G Gfiltered) (= M Mfiltered)) + Kfiltered + (recur Gfiltered + Mfiltered + Kfiltered))))) + + +(defn randomize-context-by-edge-swapping + "Given K=(G,M,I) swap incidences repeatedly selecting two objects and two attributes and swapping their incidences: + |x|.| |.|x| + |.|x| -> |x|.| + i.e., selecting g,h in G and m,n in M such that (g,m),(h,n) in I, (g,n),(h,m) not in I, and then swapping the incidences. + " + ([K] + (randomize-context-by-edge-swapping K (* 10 (count (contexts/incidence-relation K))))) + ([K iterations] + (assert (>= iterations 0) + "need a positive number of iterations") + (assert (not= (count (contexts/incidence-relation K)) 0) + "context has empty incidence relation; no swaps possible") + (assert (not= (count (contexts/incidence-relation K)) (* (count (contexts/objects K)) + (count (contexts/attributes K)))) + "context has full incidence relation; no swaps possible") + (let [G (contexts/objects K) + M (contexts/attributes K) + Kswappable (filter-full-and-empty-rows-and-columns K) ; obtain the subcontext where swapping edges is possible (to reduce the occurrence of failed swap attempts) + Gfiltered (contexts/objects Kswappable) + Mfiltered (contexts/attributes Kswappable) + ] + (loop [i 0 ;iteration counter + j 0 ;failed iteration counter + Kswappable Kswappable + I (contexts/incidence-relation K)] + (if (>= j (+ 100 iterations)) + (do (println "Randomization stopped after " i + " successful swaps and " j " failed swap attempts") + (contexts/make-context G M I)) + (if (>= i iterations) + ;; (do (println "Randomization stopped after " i + ;; " successful swaps and " j " failed swap attempts") + ;; (contexts/make-context G M I)) + (contexts/make-context G M I) + (let [g (rand-nth (into [] Gfiltered)) + gprime (contexts/object-derivation Kswappable [g]) + m (rand-nth (into [] gprime)) + n (rand-nth (into [] (difference Mfiltered gprime))) + mprime (contexts/attribute-derivation Kswappable [m]) + nprime (contexts/attribute-derivation Kswappable [n]) + hpool (intersection (difference Gfiltered mprime) + nprime)] + (if (= #{} hpool) ; if there is no suitable object count the iteration as failed and repeat + (recur i + (inc j) + Kswappable + I) + (let [h (rand-nth (into [] hpool))] + ;; (println "remove " [g m] [h n]) + ;; (println "add " [g n] [h m]) + (let [Inew (union (difference I #{[g m] [h n]}) #{[g n] [h m]})] + (recur (inc i) + j + (contexts/make-context Gfiltered Mfiltered Inew) + Inew))))))))))) + +(defalias imitate-context-with-edge-swapping randomize-context-by-edge-swapping) + + +(defn randomize-context-by-edge-rewiring + "Given K=(G,M,I) randomize the incidence relation by repeatedly doing the following: select (g,m) in I, (h,n) not in I then add (h,n) to I and remove (g,m) from I" + ([K] + (randomize-context-by-edge-rewiring K (* 10 (count (contexts/incidence-relation K))))) + ([K iterations] + (assert (>= iterations 0) + "need a positive number of iterations") + (assert (not= (count (contexts/incidence-relation K)) 0) + "context has empty incidence relation; no rewiring possible") + (assert (not= (count (contexts/incidence-relation K)) (* (count (contexts/objects K)) + (count (contexts/attributes K)))) + "context has full incidence relation; no rewiring possible") + (let [G (contexts/objects K) + M (contexts/attributes K)] + (loop [i 0 + I (contexts/incidence-relation K) + IC (difference (into #{} (map vec (cartesian-product G M)))I)] + (if (>= i iterations) + (contexts/make-context G M I) + (let [[g m] (rand-nth (into [] I)) + [h n] (rand-nth (into [] IC))] + (recur (inc i) + (-> I + (disj [g m]) + (conj [h n])) + (-> IC + (disj [h n]) + (conj [g m])) + ))))))) + +(defalias imitate-context-with-edge-rewiring randomize-context-by-edge-rewiring) nil diff --git a/src/main/clojure/conexp/fca/smeasure.clj b/src/main/clojure/conexp/fca/smeasure.clj new file mode 100644 index 000000000..4c0b6033c --- /dev/null +++ b/src/main/clojure/conexp/fca/smeasure.clj @@ -0,0 +1,705 @@ +;; 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.smeasure + (:require [conexp.base :refer :all] + [conexp.fca.contexts :refer :all] + [conexp.fca.concept-transform :refer :all] + [conexp.fca.cover :refer [generate-concept-cover]] + [conexp.fca.lattices :refer [concept-lattice + lattice-inf-irreducibles]] + [clojure.math.combinatorics :as comb] + [loom.graph :as lg] [loom.alg :as la] + [clojure.core.reducers :as r] + [conexp.fca.implications :refer :all])) + +(defprotocol Smeasure + (context [sm] "Returns the original context that is measured.") + (scale [sm] "Returns the scale that measures the context.") + (measure [sm] "Returns the scale measure map that associates objects of context with objects of scale.")) + +(deftype ScaleMeasure [context scale measure] + Object + (equals [this other] + (and (= (class this) (class other)) + (= (.context this) (.context ^ScaleMeasure other)) + (every? #(= ((.measure this) %) + ((.measure ^ScaleMeasure other) %)) + (objects context)))) + (hashCode [this] + (hash-combine-hash ScaleMeasure context scale measure)) + ;; + Smeasure + (context [this] context) + (scale [this] scale) + (measure [this] measure)) + +;;; visualization terminal + +(defn ^String smeasure-to-string + "Prints smeasures in a human readable form." + [sm] + (let [context (context sm) + scale (scale sm) + mapping (measure sm) + groups (group-by #(mapping %) (objects context)) + ;; + ctx-incident? (incidence context) + sca-incident? (incidence scale) + ;; + max-att-ctx (reduce #(max %1 (count (str %2))) 0 (attributes context)) + max-obj-ctx (reduce #(max %1 (count (str %2))) 0 (objects context)) + max-att-sca (reduce #(max %1 (count (str %2))) 0 (attributes scale)) + max-obj-sca (reduce #(max %1 (count (str %2))) 0 (objects scale)) + ;; + seg-line [(ensure-length "" max-obj-ctx "-") "-+" + (for [att (attributes context)] + (ensure-length "" (inc (count (print-str att))) "-")) + " " + (ensure-length "" max-obj-sca "-") "-+" + (for [att (attributes scale)] + (ensure-length "" (inc (count (print-str att))) "-")) + "\n"]] + (with-str-out + ;; header + (ensure-length "" max-obj-ctx " ") " |" (for [att (attributes context)] + [(print-str att) " "]) + " " + (ensure-length "" max-obj-ctx " ") " |" (for [att (attributes scale)] + [(print-str att) " "]) "\n" + (for [[k group] groups] + ;; first line in group + [seg-line + (ensure-length (print-str (first group)) max-obj-ctx) + " |" + (for [att (attributes context)] + [(ensure-length (if (ctx-incident? [(first group) att]) "x" ".") + (count (print-str att))) + " "]) + " ⟶ " + (ensure-length (print-str (mapping (first group))) max-obj-sca) + " |" + (for [att (attributes scale)] + [(ensure-length (if (sca-incident? [(mapping(first group)) att]) + "x" ".") + (count (print-str att))) + " "]) + "\n" + ;; remaining lines + (for [obj (drop 1 group)] + [(ensure-length (print-str obj) max-obj-ctx) " |" + (for [att (attributes context)] + [(ensure-length (if (ctx-incident? [obj att]) "x" ".") + (count (print-str att))) + " "]) + " " + (ensure-length "" max-obj-ctx " ") " |" + (for [att (attributes scale)] + [(ensure-length "" (count (print-str att)) " ") + " "]) + "\n"])])))) + +(defn print-smeasure + "Prints the result of applying smeasure-to-string to the given + smeasure." + [sm] + (print (smeasure-to-string sm))) + +(defmethod print-method ScaleMeasure [sm out] + (.write ^java.io.Writer out + ^String (smeasure-to-string sm))) + +;;; +(defn- pre-image-measure + "Returns the pre-image map of a scale measures function sigma." + [sm] + (let [m (measure sm)] + (if (map? m) + (apply (partial merge-with into) {} + (for [[k v] m] {v #{k}})) + (let [mapified (into {} + (for [obj (objects (context sm))] + [obj ((measure sm) obj)]))] + (apply (partial merge-with into) {} + (for [[k v] mapified] {v #{k}})))))) + +(defn- lifted-pre-image-measure + "Returns the pre-image map as function on the power set of G of a + scale measures function sigma." + [sm] + (let [m (pre-image-measure sm)] + (fn [scale-objs] + (set (reduce into (map m scale-objs)))))) + +(defn original-extents + "Returns the pre-image of all extents whichs image is closed in the + scale." + [sm] + (let [scale-extents (extents (scale sm)) + pre-image (pre-image-measure sm)] + (map #(set (reduce into (map pre-image %))) + scale-extents))) + + + +(defn valid-scale-measure? + "Checks if the input is a valid scale measure. This check can be + performed in PTIME in the size of the context and scale by + considering attribute concepts only." + [sm] + (let [scon (scale sm) + lifted-pre-image (lift-map (pre-image-measure sm)) + pre-attribute-extents (->> scon attributes + (map #(attribute-derivation scon #{%})) + (map lifted-pre-image))] + (every? #(extent? (context sm) %) + pre-attribute-extents))) + +(defn smeasure? + "Checks if the input is a valid scale measure." + [sm] + (and (instance? ScaleMeasure sm) + (valid-scale-measure? sm))) + +(defn make-smeasure + "Returns a scale-measure object of the input is a valid scale measure." + [ctx scale m] + (let [sm (ScaleMeasure. ctx scale m)] + (assert (valid-scale-measure? sm) "The Input is no valid Scale Measure") + sm)) + +(defn make-smeasure-nc + "Generates a scale measure object without checking the validity." + [ctx scale m] + (ScaleMeasure. ctx scale m)) + +(defn make-id-smeasure + "Generates a scale-measure with the identity map and the context as scale." + [ctx] + (make-smeasure-nc ctx ctx identity)) + +(defn smeasure-by-exts + "Returns the canonical scale-measure which reflects exts." + [cxt exts] + (make-smeasure-nc cxt + (make-context (objects cxt) + exts + #(contains? %2 %1)) + identity)) + +(defn canonical-smeasure-representation + "Given a scale-measure computes its canonical representation." + [sm] + (let [scon (scale sm) + o (original-extents sm)] + (make-smeasure-nc (context sm) + (make-context (objects (context sm)) o #(contains? %2 %1)) + identity))) + +(defn scale-apposition + [sm1 sm2] + (assert (= (context sm1) (context sm2)) "Both scale-measure must be for the same context.") + (if (and + (= (objects (scale sm1)) (objects (scale sm2))) + (= (measure sm1) (measure sm2))) + (make-smeasure-nc (context sm1) + (context-apposition (scale sm1) (scale sm2)) + (measure sm1)) + (scale-apposition (canonical-smeasure-representation sm1) + (canonical-smeasure-representation sm2)))) + +(defalias join-smeasure scale-apposition) + +(defn meet-smeasure + "Returns the meet smeasure of sm1 and sm2." + [sm1 sm2] + (assert (= (context sm1) (context sm2)) "Both scale-measure must be for the same context.") + (smeasure-by-exts (context sm1) + (intersection (set (original-extents sm1)) + (set (original-extents sm2))))) + +(defn join-complement + "Returns the canonical representation of the join-complement of sm in the scale-hierarchy." + [sm] + (let [cxt (context sm) + s (scale sm) + m (measure sm) + join-complement-scale (make-context (objects cxt) + (difference (->> cxt concept-lattice + lattice-inf-irreducibles + (map first) + set) + (-> sm original-extents + set)) + #(contains? %2 %1))] + (canonical-smeasure-representation + (make-smeasure-nc cxt join-complement-scale identity)))) + +(defn error-in-smeasure + "Returns all false reflected extents." + ([sm] + (->> sm + original-extents + (filter #(not (extent? (context sm) %))))) + ([cxt s m] + (error-in-smeasure (make-smeasure-nc cxt s m)))) + +(defn valid-attributes + "Returns all attributes of the scale whichs derivation pre-image is an + extents of cxt." + ([sm] + (valid-attributes (context sm) + (scale sm) + (measure sm))) + ([cxt s m] + (let [pre-image (lifted-pre-image-measure (make-smeasure-nc cxt s m))] + (filter #(->> #{%} + (attribute-derivation s) + pre-image + (extent? cxt)) + (attributes s))))) + +(defn invalid-attributes + "Returns all attributes of the scale whichs derivation pre-image is + not an extents of cxt." + ([sm] + (invalid-attributes (context sm) + (scale sm) + (measure sm))) + ([cxt s m] + (let [v (valid-attributes cxt s m)] + (difference (attributes s) v)))) + +(defn conceptual-scaling-error + "Computes the conceptual scaling error, i.e., the number of falsely + reflected extents by the scaling sm. + DOI:https://doi.org/10.1007/978-3-030-86982-3_8" + ([sm & {:keys [relative] :or {relative false}}] + (let [o (original-extents sm) + error (filter #(not (extent? (context sm) %)) o)] + (if relative + (/ (count error) (count o)) + (count error))))) + +(defn attribute-scaling-error + "Computes the attribute scaling error, i.e., the number attributes that + induce an inconsistent data scaling. This score is an approximation of + the conceptual scaling error. + DOI:https://doi.org/10.1007/978-3-030-86982-3_8 " + ([sm & {:keys [relative] :or {relative false}}] + (let [a (-> sm scale attributes) + error (-> sm invalid-attributes)] + (if relative + (/ (count error) (count a)) + (count error))))) + +(defn remove-attributes-sm + "Removes 'attr attributes from the scale." + [sm attr] + (let [s (scale sm) + new-scale (make-context (objects s) + (difference (attributes s) attr) + (incidence-relation s))] + (make-smeasure-nc (context sm) new-scale (measure sm)))) + +(defn smeasure-valid-attr + "Filters the scale-measure scale attributes to the set of valid attributes." + [sm] + (let [inval-attr (difference (-> sm scale attributes) + (valid-attributes sm))] + (remove-attributes-sm sm inval-attr))) + +(defn smeasure-invalid-attr + "Filters the scale-measure scale attributes to the set of invalid attributes." + [sm] + (let [val-attr (valid-attributes sm)] + (remove-attributes-sm sm val-attr))) + + + +(defn smeasure-valid-exts + "Filters the scale-measure extents to the set of valid extents." + [sm] + (let [invalid-exts (error-in-smeasure sm) ] + (smeasure-by-exts (context sm) + (difference (-> sm context extents set) + (set invalid-exts))))) + +(defn smeasure-invalid-exts + "Filters the scale-measure extents to the set of invalid extents." + [sm] + (let [invalid-exts (error-in-smeasure sm) ] + (smeasure-by-exts (context sm) + (set invalid-exts)))) + +(defmulti rename-scale + "Renames objects or attributes in the scale. Input the renaming as function on the + set of objects or as key value pairs." + (fn [type & args] type)) +(alter-meta! #'rename-scale assoc :private true) + + +(defmethod rename-scale :objects + ([_ sm rename-fn] + (make-smeasure-nc (context sm) + (rename-objects (scale sm) rename-fn) + (comp rename-fn (measure sm)))) + ([_ sm key val & keyvals] + (let [rename-map (apply hash-map key val keyvals) + rename-fn (fn [o] (or (get rename-map o) o))] + (rename-scale :objects sm rename-fn)))) + +(defmethod rename-scale :attributes + ([_ sm rename-fn] + (make-smeasure-nc (context sm) + (rename-attributes (scale sm) rename-fn) + (measure sm))) + ([_ sm key val & keyvals] + (let [rename-map (apply hash-map key val keyvals) + rename-fn (fn [a] (or (get rename-map a) a))] + (rename-scale :attributes sm rename-fn)))) + +(defn meet-irreducibles-only-smeasure + "Removes all non meet-irreducible attributes from the scale-measure." + [sm] + (make-smeasure-nc (context sm) (-> sm scale reduce-attributes) (measure sm))) + +(defn conjunctive-normalform-smeasure-representation + "Given a scale-measure computes the equivalent scale-measure using + logical conjunctive formulas." + [sm] + (rename-scale :attributes + (canonical-smeasure-representation sm) + (fn [a] + (rest (reduce #(conj %1 :and %2) [] + (object-derivation (context sm) a)))))) + +(defn recommend-by-importance + "Recommends a scale-measure reflecting the 'n most important + concepts based on a concept importance measure. + Some importance measures are: + - stability: conexp.fca.metrics/concept-stability + - separation index: conexp.fca.metrics/concept-separation + - probability: conexp.fca.metrics/concept-probability + - robustness: (fn [context concept] + (conexp.fca.metrics/concept-robustness + concept (concepts context) your-alpha)) + - support: (fn [context concept] (count (first concept)))" + [context imp-fn n] + (let [dual (dual-context context) ;; measure importance for extents + imp-n-concepts (take-last n + (sort-by (partial imp-fn dual) + (rest (concepts dual)))) + exts (map last imp-n-concepts) + scale (make-context (objects context) exts + (fn [a b] (contains? b a)))] + (println "The " n " most important extents produced " + (-> scale extents count) " concepts.") + (make-smeasure-nc context scale identity))) + +(derive ::all ::quant) +(derive ::ex ::quant) + + +;;; Declare REPL commandso +(defmulti run-repl-command + "Runs a command for the counterexample REPL." + (fn [& args] (first args))) +(alter-meta! #'run-repl-command assoc :private true) + +(defmulti help-repl-command + "Returns the help string of the given command." + (fn [& args] (first args))) +(alter-meta! #'help-repl-command assoc :private true) + +(defn- suitable-repl-commands + "Returns all known repl commands for query, which can be a symbol or + a string." + [query] + (let [str-query (str query)] + (filter #(.startsWith (str %) str-query) + (remove #{:default} (keys (methods run-repl-command)))))) + +(def ^:private abortion-sentinal (Exception. "You should never see this")) + +(defn- eval-command + "Runs the given REPL command query with state, in the case the query uniquely + determines a command. If not, an error message is printed and state is + returned." + [query state] + (if (= query 'abort) + (throw abortion-sentinal) + (let [suitable-methods (suitable-repl-commands query)] + (cond + (second suitable-methods) + (do + (println "Ambigious command, suitable methods are") + (doseq [name suitable-methods] + (println " " name)) + state), + (empty? suitable-methods) + (do + (println "Unknown command") + state) + :else + (try + (run-repl-command (first suitable-methods) state) + (catch Throwable t + (print "Encountered Error: ") + (println t) + state)))))) + +(defmacro- define-repl-fn [name doc & body] + `(do + (defmethod run-repl-command '~name + ~'[_ state] + (let [~'smeasure (:smeasure ~'state) + + ~'scale (scale ~'smeasure)] + ~@body)) + (defmethod help-repl-command '~name + ~'[_] + ~doc))) + +(define-repl-fn done + "Ends the Scale Navigation." + (assoc state :done true)) + +(define-repl-fn clear + "Clears the current state and restarts from scratch." + (assoc state :smeasure (make-id-smeasure (context smeasure)))) + +(define-repl-fn truncate + "Enter an attribute that should be removed from the scale." + (assoc state :smeasure + (remove-attributes-sm smeasure + (ask (str "Please enter all to be removed attribute spereated by ';': \n" "Current Attributes: \n " (clojure.string/join " " (attributes scale)) "\n :") + #(map read-string (clojure.string/split (str (read-line)) #";")) + #(subset? % (attributes scale)) + "The attributes are not all present, please enter an existing attribute: \n")))) + +(define-repl-fn rename + "Renames objects or attributes in the scale." + (let [rename-kind (ask (str "Please enter if you want to rename attributes (:attributes) or objects (:objects): \n") + #(read-string (str (read-line))) + #(or (= :objects %) (= :attributes %)) + "The input must be :attributes or :objects: \n") + rename-method (partial rename-scale rename-kind) + rename-content (ask (str "Please enter all " (rename-kind {:attributes "attributes" :objects "objects"}) + " that should be renamed and their new name with ; seperator (name1;new-name1;name2;...): \n" + "Current " (rename-kind {:attributes "attributes" :objects "objects"})": \n "(clojure.string/join " " ((rename-kind {:attributes attributes :objects objects}) scale))"\n") + #(map read-string (clojure.string/split (str (read-line)) #";")) + (fn [input] (and (even? (count input)) + (every? + #(contains? ((rename-kind {:attributes attributes :objects objects}) scale) %) + (take-nth 2 input)))) + (str "Input must be ; seperated an contain only " (rename-kind {:attributes "attributes" :objects "objects"}) "of the scale and their new name:\n"))] + (if (empty? rename-content) state + (assoc state :smeasure (apply rename-method smeasure rename-content))))) + +(define-repl-fn logical-attr + "Generates a new attribute as logical formula of existing attributes." + (let [formula (ask (str "Current Attributes: \n " (clojure.string/join " " (attributes scale)) "\n Please enter a logical formula using logical formuals e.g. \"[\"C\" :or [1 :and [:not \"B\"]]]\": \n ") + #(read-string (str (read-line))) + #(formula-syntax-checker % scale attributes) + "Enter a formula in nested list format using only attributes of the scale: \n") + formula-derivation (logical-attribute-derivation scale formula) + formula-name (ask (str "Enter a name for your formula: \n ") + #(str (read-line)) + (constantly true) + "")] + (if (extent? (context (:smeasure state)) formula-derivation) + (assoc state :smeasure (make-smeasure-nc (-> state :smeasure context) + (make-context (objects scale) + (conj (attributes scale) formula-name) + (union (cross-product formula-derivation #{formula-name}) + (incidence-relation scale))) + (-> state :smeasure measure))) + state))) + +(define-repl-fn show + "Prints the current scale context, attributes or objects." + [state] + (let [toshow (ask (str "Please enter if you want display the scale (:scale) or original context (:context): \n") + #(read-string (str (read-line))) + #(or (= :context %) (= :scale %)) + "The input must be :context or :scale: \n") + option (ask (str "Please enter if you want display all (:all) the objects (:objects) or the attributes (:attributes): \n") + #(read-string (str (read-line))) + #(or (= :all %) (= :objects %) (= :attributes %)) + "The input must be :attributes or :objects: \n")] + (println "\n" ((option {:all identity + :attributes (comp (partial clojure.string/join "; ") attributes) + :objects (comp (partial clojure.string/join "; ") objects)}) + (toshow {:scale scale :context (context smeasure)}))) + state)) + +(define-repl-fn help + "Prints help." + (let [commands (suitable-repl-commands "")] + (println "Type «abort» to abort exploration.") + (println "Any other command can be abbreviated, as long as this is unambigious.") + (doseq [cmd commands] + (println (str " " cmd)) + (println (str " -> " (help-repl-command cmd)))) + state)) + +;;; Scale Exploration + +(defn conceptual-navigation + "Exploration for a scale context to measure a given context. + The exploration is done with online editing methods. + + - rename: Rename objects or attributes of the scale + - logical-attr: Clusters objects or attributes in the scale + The cluster incidence is set as either the common + or conjoined incidences of all entries + - truncate: Removes attributes form the scale + - clear: Restarts the exploration + + general functions for exploration interaction + - show: prints the current scale + - done: finishes exploration + - help: prints doc string" + [ctx] + (assert (context? ctx) "Input must be a Context.") + (println (:doc (meta #'conceptual-navigation)) "\n\n\n") + (println "Start scale exploration for:\n" ctx) + (loop [state {:smeasure (make-id-smeasure ctx)}] + (let [evaluated (eval-command (ask (str "Please enter an operation:\n") + #(read-string (str (read-line))) + (constantly true) + "Input must be a valid command: \n") state)] + (if (:done evaluated) + (:smeasure evaluated) + (recur evaluated))))) + + +(defn- provide-counter-example + "Queries for a counter example B and applies the closure operator of + the context of the explored scale-measure. This ensures that + reflected set of extents is a subset of the contexts extents." + [state cur cur-conclusion] + (let [premise-deri (object-derivation (:context state) cur) + concl-deri (object-derivation (:context state) cur-conclusion) + deri-margin (difference premise-deri concl-deri) + counter (ask (str "Have: " concl-deri "\n how much more of " deri-margin + " \n do you want? \n Enter a subset of " + deri-margin ":\n Note that the empty input will result in an infinite loop\n") + #(let [input (str (read-line))] + (if (= "" input) #{} + (set (map read-string + (clojure.string/split input #";"))))) + #(subset? % deri-margin) + (str "The input must be a subset of " deri-margin ":\n")) + counter-cl (context-attribute-closure (:context state) (union concl-deri counter))] + (let [answer (ask (str "You entered " counter " with closure " counter-cl "\n Please confirm with yes or no:") + #(str (read-line)) + #(or (= "yes" %) (= % "no")) + "\n Enter yes or no:")] + (println answer "\n") + (if (= "yes" answer) + (attribute-derivation (:context state) (union concl-deri counter)) + (provide-counter-example state cur cur-conclusion))))) + +(defn- coarsened-by-imp? + "Queries a boolean if an implication should used in the scale-measure + exploration." + [state cur cur-conclusion] + (let [premise-deri (object-derivation (:context state) cur) + concl-deri (object-derivation (:context state) cur-conclusion) + deri-margin (difference premise-deri concl-deri) + answer (ask (str "Have: " concl-deri + " want more of " deri-margin "?\n Enter yes, none or all:") + #(str (read-line)) + #(or (= "none" %) (= % "all") (= "yes" %) (= % "no")) + "\n Enter yes, all or none :")] + (println answer "\n") + answer)) + +(defn exploration-of-scales-iteration + "This is method performs each iteration of the scale-measure + exploration to determine an object set B to coarsen a context by the + object implication cur->B with B beeing a subset of + cur-conclusion. This is done by querying sets B until a suggested + implication holds and no B is provided. Then the next iteration can + be called with the next-closured set of cur." + [state] + (loop [cur-conclusion (context-object-closure (:scale state) (:cur state)) iter-state state] + ;(println (:cur iter-state) cur-conclusion) print object implication + (if (= cur-conclusion (:cur iter-state)) + (let [next-cur (next-closed-set (:object-order iter-state) + (clop-by-implications (:imps iter-state)) + (:cur iter-state))] + (assoc iter-state :cur next-cur)) + (let [coarse? (coarsened-by-imp? iter-state (:cur iter-state) cur-conclusion)] + (cond ; not wanting attributes means cur should be closed. Hence add all attributes in the question + ; all attributes are already closed. will could result in endless loop + (= coarse? "all") (let [closed-premise + (update iter-state :scale + #(make-context (objects %) + (conj (attributes %) + (:cur iter-state)) + (:inc state)))] + (recur (context-object-closure (:scale closed-premise) (:cur closed-premise)) + closed-premise)) + (= coarse? "yes") (let [counter (provide-counter-example iter-state (:cur iter-state) cur-conclusion) + state-with-counter + (update iter-state :scale + #(make-context (objects %) + (conj (attributes %) + counter) + (:inc state)))] + (recur (context-object-closure (:scale state-with-counter) (:cur state-with-counter)) + state-with-counter)) + (= coarse? "none") (let [new-imps (conj (:imps iter-state) (make-implication (:cur iter-state) cur-conclusion)) + next-cur (next-closed-set (:object-order iter-state) + (clop-by-implications new-imps) + (:cur iter-state))] + (-> iter-state + (assoc :imps new-imps) + (assoc :cur next-cur)))))))) + +(defn exploration-of-scales + "This algorithm performs an object-exploration with background + knowledge (context) to determine a scale-measure." + ([context] + (exploration-of-scales context (-> context objects vec))) + ([context object-order] + (let [incidence-fn (fn ([a b] (contains? b a)) + ([[a b]] (contains? b a))) + init-scale (make-context object-order #{} incidence-fn)] + (loop [state {:scale init-scale :cur #{} :object-order object-order + :imps (canonical-base (dual-context context)) + :inc incidence-fn :context context }] + (if (= (objects context) + (:cur state)) + (make-smeasure-nc context (:scale state) identity) + (recur (exploration-of-scales-iteration state))))))) + +;;;;;;;;;;;;;;; local scaling for a single object + +;; Computes all concepts containing an object g and the concepts in covering relation + +(defn concept-lattice-filter+covering-concepts + "Computes all concepts containing object g and their covering + concepts." + [ctx g] + (let [first-C [(attribute-derivation ctx #{}) + (context-attribute-closure ctx #{})]] + (loop [BV #{first-C} + queue #{first-C}] + (if (empty? queue) + BV + (let [C (first queue)] + (let [covering-C (direct-lower-concepts ctx C) + ;; those not containing g can be added since they are in cover with a concept containing c + new-C (difference covering-C BV) + ;; only continue with those that contain g to ensure selection criteria + for-queue (filter #(contains? (first %) g) new-C)] + (recur (into BV new-C) (into (disj queue C) for-queue)))))))) diff --git a/src/main/clojure/conexp/fca/triadic_exploration.clj b/src/main/clojure/conexp/fca/triadic_exploration.clj new file mode 100644 index 000000000..4ec9124ca --- /dev/null +++ b/src/main/clojure/conexp/fca/triadic_exploration.clj @@ -0,0 +1,380 @@ +(ns conexp.fca.triadic-exploration + (:require [clojure.algo.generic.functor :refer [fmap]] + [conexp.fca.contexts :as cxt] + [conexp.fca.implications :as impl] + [conexp.fca.exploration :as expl] + [conexp.base :as base] + )) + + + +(defn triadic-context->context-family + "Transform a triadic context into a family of contexts. A triadic context is a quadrupel (G,M,B,Y) of objects G, attributes M, conditions B, and an incidence relation Y\\subseteq G \times M \\times B where [g m b]\\in Y reads as object g has attribute m under condition b. The triadic context is sliced into one formal context per condition, i.e., contexts K^c (G,M,I^c), c\\in B where (g,m)\\in I^c \\Leftrightarrow (g,m,c)\\in Y. The function returns a map {:c K^c ...} mapping the conditions to their respective contexts. The triadic context must be represented as a map with keys :objects :attributes :conditions and :incidence, otherwise an error is thrown." + [triadic-context] + (if (and (contains? triadic-context :objects) + (contains? triadic-context :attributes) + (contains? triadic-context :conditions) + (contains? triadic-context :incidence)) + (let [objects (:objects triadic-context) + attributes (:attributes triadic-context) + incidences-by-condition (group-by #(nth % 2) (:incidence triadic-context))] + (into {} + (for [c (:conditions triadic-context)] + [c (cxt/make-context objects attributes (map drop-last (c incidences-by-condition)))]))) + (throw (Throwable. "triadic context is not a map with keys :objects :attributes :conditions and :incidence")) + )) + + +(defn is-context-family-triadic? + "Tests if the contexts in a context family {:cond1 cxt1 :cond2 cxt2 ...} form a triadic context, i.e., have the same sets of objects and attributes" + [context-family] + (let[[first-key & rest-keys :as conds] (keys context-family) + first-context (first-key context-family) + objs (cxt/objects first-context) + atts (cxt/attributes first-context)] + (->> rest-keys + (map #(% context-family)) + (map (fn [cxt] (and + (= (cxt/objects cxt) objs) + (= (cxt/attributes cxt) atts)))) + (reduce #(every? identity [%1 %2]))))) + + +(defn context-family->triadic-context + "Transforms a context family into a triadic context. The contexts in the family must have the same object and attribute sets otherwise an error is thrown. A context family is a map of conditions and their context on the same set of attributes, i.e., a map {:cond1 (G1,M,I1) :cond2 (G2,M,I2) ...}" + [cxt-family] + (if (is-context-family-triadic? cxt-family) + (let [conditions (keys cxt-family) + objects (cxt/objects ((first conditions) cxt-family)) + attributes (cxt/attributes ((first conditions) cxt-family)) + incidence (reduce into #{} (for [c conditions] + (map #(conj % c) (cxt/incidence-relation (c cxt-family)))))] + {:objects objects + :attributes attributes + :conditions conditions + :incidence incidence}) + (throw (Throwable. "context-family is not triadic")))) + + +(defn- context-union-nf + "compute (G_1 u G_2, M, I_1 u I_2) for contexts (G_1,M,I_1) and (G_2,M,I_2); In contrast to contexts/context-union this functions returns a context and *not* a function" + [cxt1 cxt2] + (let [objs (clojure.set/union (set (cxt/objects cxt1)) + (set (cxt/objects cxt2))) + atts (clojure.set/union (set (cxt/attributes cxt1)) + (set (cxt/attributes cxt2))) + incidence (clojure.set/union (set (cxt/incidence-relation cxt1)) + (set (cxt/incidence-relation cxt2)))] + (cxt/make-context objs atts incidence))) + + +(defn- contradicts? + "takes an object, an implication and a context, returns true if the object is a counterexample to the implication in the context" + [o impl cxt] + (if (impl/holds? impl (cxt/make-context #{o} + (cxt/attributes cxt) + (cxt/incidence-relation cxt))) + false + true)) + + +(defn- counterexample + "takes a formal context and an implication and + returns an counterexample with all its incidences if it exists or nil" + [context implication] + (let [objs (cxt/objects context) + prem (impl/premise implication) + concl (impl/conclusion implication)] + (loop [o (first objs) + other (rest objs)] + (if (contradicts? o implication context) + [o (into #{} (for [a (cxt/object-derivation context #{o})] + [o a]))] + (if (empty? other) + nil + (recur (first other) + (rest other))))))) + +(defn- triadic-object-incidences + "takes a triadic context and an object and returns all incidences of that object" + [triadic-context object] + (->> (:incidence triadic-context) + (filter (fn [i] (= object (first i)))) + (into #{}))) + + +(defn triadic-expert-from-triadic-context + "takes a triadic context and returns the corresponding triadic expert, i.e., + a function that takes a set of conditions D and an implication and returns + the conditions $C \\subseteq D$ for which the implications hold and if $C \\not = D$ + also a counterexample [g triadic-incidence-of-g] + + triadic-expert + input: conditions D and implication + output: + [D true] if the implication holds for all conditions in D + or + [subset-of-D-for-which-the-implication-holds counterexample] + where counterexample = [g triadic-incidence-of-g] + if the implication does not hold for all conditions + " + [tcxt] + (fn [D question] + (let [context-family (triadic-context->context-family tcxt) + ;; test if question holds in all contexts in D + ;; if not respond with a counterexample + counterex-conditions (->> D + (map #(hash-map % (% context-family)) ,,,) + (into {} ,,,) + (fmap #(impl/holds? question %) ,,,) + (filter (fn [[k v]] (false? v)) ,,,) + (into {} ,,,) + (keys))] + (if (some? counterex-conditions) + (let [counterex (->> (counterexample ((first counterex-conditions) context-family) question) + (first) + (triadic-object-incidences tcxt) + ((fn [coll] (vector (ffirst coll) coll))))] ;make the counterexample [g triadic-incidence] + [(clojure.set/difference (set D) counterex-conditions) counterex] + ) + [D true])))) + + +(defn- subposition-context-coll [cxts] + "For a collection of contexts on the same attributes returns the subposition context" + (reduce cxt/context-subposition cxts)) + + +(defn- triadic-context+conditions->subposition-context + "Given a triadic context and a set of conditions returns the subposition context consisting of the respective condition contexts" + [triadic-context D] + (subposition-context-coll (vals (select-keys (triadic-context->context-family triadic-context) D)))) + + +(defn- next-closure-by-implications + "Given a set of attributes A from the base set M and a set of implications L on M, returns the next closed set for A" + [A M L] + (base/next-closed-set + M + (impl/clop-by-implications L) + A)) + + +(defn- add-counterexample-to-triadic-context + "The counterexample [g incidence] consists of the object g and its incidence" + [triadic-context counterexample] + (-> triadic-context + (update-in [:objects] conj (first counterexample)) + (update-in [:incidence] into (second counterexample)))) + + +(defn- add-row-to-context + "Given a context and an [object incidence] pair adds the object to the context" + [context [object incidence]] + (cxt/make-context (conj (cxt/objects context) object) + (cxt/attributes context) + (into (cxt/incidence-relation context) incidence))) + + +(defn explore-conditions + " + required: + triadic-expert = the expert answering the questions + D = the set of conditions to explore + E = a triadic context of examples (possibly empty) + L0 = a set of background implications known to hold for all conditions in D + + optional: + collect-all-implications = true(default)/false to collect either all + implications that are asked about or only the ones that are valid for all conditions. + " + ([triadic-expert D E L0] + (explore-conditions triadic-expert D E L0 true)) + ([triadic-expert D E L0 collect-all-implications] + (let [M (:attributes E) + B (:conditions E) + C0 (cxt/make-context #{} + B + #{})] + (loop [A #{} + L L0 + E E + C C0] + (if (not A) + [L C E] + (let [S (triadic-context+conditions->subposition-context E D) + AJJ (cxt/context-attribute-closure S A) + question (impl/make-implication A AJJ)] + (if (= A AJJ) + (recur (next-closure-by-implications A M (conj L question)) + L + E + C) + (let [full-answer (triadic-expert D question) + conditions-for-which-question-holds (first full-answer) + answer (second full-answer) + Cnew (if (true? answer) + (add-row-to-context C [question (for [d D] [question d])]) + (if collect-all-implications + (add-row-to-context C [question (for [d conditions-for-which-question-holds] + [question d])]) + C) + )] + (if (true? answer) ;no counterexample + (recur (next-closure-by-implications A M (conj L question)) + (conj L question) + E + Cnew) + (recur A + L + (add-counterexample-to-triadic-context E answer) + Cnew)))))))))) + + +(defn explore-all-conditional-theories + "takes a triadic context of examples E + and a context of implications known to hold C + required: + triadic-expert = a triadic expert for the domain + E = context of examples (at least an empty context of attributes and conditions) + C = context of implications and the conditions for which they hold (at least an empty context of conditions) + optional: + collect-all-implications = either collect all implications that are asked about or just the ones that hold for all conditions + " + ([triadic-expert E C] + (explore-all-conditional-theories triadic-expert E C true)) + ([triadic-expert E C collect-all-implications] + (let [conditions (:conditions E) + condition-sets (->> (clojure.math.combinatorics/subsets conditions) + (map #(into #{} %)) + (filter (comp not empty?)) + (sort-by count) + (reverse))] + (loop [D (first condition-sets) + other (rest condition-sets) + E E + C C] + (if (nil? D) + [C E] + (let [L (cxt/attribute-derivation C D) + [LD CD ED] (explore-conditions triadic-expert D E L collect-all-implications)] + (recur (first other) + (rest other) + ED + (context-union-nf C CD)))))))) + + +(defn empty-triadic-context-from-triadic-context + "Given a triadic context [G M B Y] returns an empty triadic context [#{} M B #{}]" + [tcxt] {:objects #{} + :attributes (:attributes tcxt) + :conditions (:conditions tcxt) + :incidence #{}}) + + +(defn empty-implications-context-from-triadic-context + "Given a triadic context [G M B Y] returns an empty context [#{} B #{}] where the conditions are the attributes" + [tcxt] (cxt/make-context #{} (:conditions tcxt) #{})) + + +(defn compute-all-conditional-theories + "Compute all conditional theories of a triadic context. + Given a triadic context computes a context of implications and the conditions for which they hold. + The extents of the concepts of this context are generating sets for the implication theory of implications that hold for all conditions of the respective intents." + [triadic-context & {:keys [collect-all-implications] :or {collect-all-implications true}}] + (let [triadic-expert (triadic-expert-from-triadic-context triadic-context) + E (empty-triadic-context-from-triadic-context triadic-context) + C (empty-implications-context-from-triadic-context triadic-context)] + (first (explore-all-conditional-theories triadic-expert E C collect-all-implications)))) + + + +;;;;;; +;;; EXAMPLE +;;;;;; + +;;; The following shows the situation of public transport at the train station Bf. Wilhelmshöhe with direction to the city center in Kassel. +;;; This example is part of the paper \"Triadic Exploration and Exploration with Multiple Experts\", see arXiv:2102.02654. +;;; From Bf. Wilhelmshöhe you can travel by one of four bus lines (52, 55, 100 and 500), four tram lines (1, 3, 4 and 7), one night tram (N3) and one regional tram (RT5) to the city center. +;;; These are the objects of our context. +;;; The buses and trams leave the station at different times throughout each day of the week. +;;; The attributes of our context are the aggregated the leave-times, more specifically, we have split each day in five distinct time slots: early morning from 4:00 to 7:00, working hours from 7:00 to 19:00, evening from 19:00 to 21:00, late evening from 21:00 to 24:00 and night from 0:00 to 4:00. +;;; A bus or tram has a cross the formal context if there is at least one leave-time in the time slot. +;;; Furthermore, we have split the days of the week into Monday to Friday, Saturday and Sunday as conditions. + + +(def ^:private objs [:1 :3 :4 :7 :N3 :RT5 :52 :55 :100 :500]) +(def ^:private atts [:night :early-morning :working-hours :evening :late-evening ]) + +(def ^:private mo-fr (cxt/make-context-from-matrix objs atts [0 1 1 1 1 + 0 1 1 1 1 + 0 1 1 1 1 + 0 1 1 1 0 + 0 0 0 0 0 + 0 1 1 1 1 + 0 1 1 1 1 + 0 1 1 0 0 + 0 1 1 1 1 + 1 1 1 1 1])) + +(def ^:private sa (cxt/make-context-from-matrix objs atts [0 0 1 1 1 + 0 1 1 1 1 + 0 1 1 1 1 + 0 0 1 0 0 + 1 0 0 0 0 + 0 1 1 1 1 + 0 1 1 0 0 + 0 0 0 0 0 + 0 1 1 1 1 + 0 0 1 1 1])) + +(def ^:private so (cxt/make-context-from-matrix objs atts [0 0 1 1 1 + 0 1 1 1 1 + 0 1 1 1 1 + 0 0 0 0 0 + 1 0 0 0 0 + 0 0 1 1 1 + 0 0 0 0 0 + 0 0 0 0 0 + 0 1 1 1 1 + 0 0 1 1 1])) + +(def ^{:doc "This example is part of the paper \"Triadic Exploration and Exploration with Multiple Experts\", see arXiv:2102.02654"} + cxt-family-paper {:Mo-Fr mo-fr + :Sat sa + :Sun so}) + +;; (def ^{:doc "This example is part of the paper \"Triadic Exploration and Exploration with Multiple Experts\", see arXiv:2102.02654"} +;; tcxt-paper (context-family->triadic-context cxt-family-paper)) + +(def cxt-family-conceptual-exploration-book {:mo-fr (cxt/make-context-from-matrix [1 6 7 8] + [:very-early + :working-hours + :evening + :late-night] + [0 1 1 1 + 1 1 0 0 + 1 1 1 1 + 1 0 1 1]) + :sat (cxt/make-context-from-matrix [1 6 7 8] + [:very-early + :working-hours + :evening + :late-night] + [1 0 0 1 + 0 0 0 0 + 1 1 0 1 + 1 1 1 1]) + :sun (cxt/make-context-from-matrix [1 6 7 8] + [:very-early + :working-hours + :evening + :late-night] + [0 0 1 0 + 0 0 0 0 + 1 1 0 1 + 0 1 1 0])}) + + +(def cxt-family-paper2 {:A (cxt/make-context [1] [:a :b ] [[1 :a]]) + :B (cxt/make-context [1] [:a :b ] [])}) diff --git a/src/main/clojure/conexp/gui/draw.clj b/src/main/clojure/conexp/gui/draw.clj index 5ac661df4..1ef806755 100644 --- a/src/main/clojure/conexp/gui/draw.clj +++ b/src/main/clojure/conexp/gui/draw.clj @@ -21,10 +21,15 @@ [conexp.gui.draw.scenes :refer :all] [conexp.io.layouts :refer :all] [conexp.layouts :refer :all] + [conexp.layouts.base :refer :all] + [conexp.layouts.common :refer :all] [conexp.layouts.util :refer :all] [seesaw.core :refer [listen]]) (:import [java.awt BorderLayout Dimension] - [javax.swing BoxLayout JFrame JPanel JScrollBar JScrollPane])) + [javax.swing BoxLayout JFrame JPanel JScrollBar JScrollPane] + [conexp.fca.posets Poset] + [conexp.fca.lattices Lattice] + [conexp.fca.protoconcepts Protoconcepts])) ;;; Lattice Editor @@ -61,7 +66,8 @@ "Freese" freese, "Force" improve-layout-by-force), snapshot-saver, - export-as-file) + export-as-file + import-from-file) ;; drawing area (doto canvas-panel @@ -103,9 +109,23 @@ (when-let [scn (get-scene-from-panel panel)] (get-layout-from-scene scn))) +(defmulti make-frame + "Returns a frame with correct title" + (fn [layout] (type (poset layout)))) -;;; Drawing Routine for the REPL +(defmethod make-frame Lattice + [layout] + (JFrame. "conexp-clj Lattice")) + +(defmethod make-frame Poset + [layout] + (JFrame. "conexp-clj Ordered Set")) + +(defmethod make-frame Protoconcepts + [layout] + (JFrame. "conexp-clj Protoconcepts")) +;;; Drawing Routine for the REPL (defn draw-layout "Draws given layout on a canvas. Returns the frame and the scene (as map). The following options are allowed, their default values are @@ -115,10 +135,10 @@ - dimension [600 600] " [layout - & {:keys [visible dimension] + & {:keys [visible dimension title] :or {visible true, dimension [600 600]}}] - (let [frame (JFrame. "conexp-clj Lattice"), + (let [frame (make-frame layout), lattice-editor (make-lattice-editor frame layout)] (doto frame (.add lattice-editor) @@ -127,13 +147,30 @@ {:frame frame, :scene (get-scene-from-panel lattice-editor)})) +(defn draw-poset + "Draws poset with given layout. Passes all other parameters to draw-layout." + [poset & args] + (let [args (flatten args) + map (apply hash-map args), + layout-fn (get map :layout-fn standard-layout), + value-fn (get map :value-fn (constantly nil))] + (apply draw-layout (-> poset layout-fn (to-valued-layout value-fn)) args))) + +(defn draw-protoconcepts + "Draws protoconcepts with given layout." + [protoconcepts & args] + (if (nil? args) + (draw-poset protoconcepts) + (draw-poset protoconcepts args))) + (defn draw-lattice "Draws lattice with given layout. Passes all other parameters to draw-layout." [lattice & args] - (let [map (apply hash-map args), - layout-fn (get map :layout-fn standard-layout)] - (apply draw-layout (layout-fn lattice) args))) + (if (nil? args) + (draw-poset lattice) + (draw-poset lattice args))) + (defn draw-concept-lattice "Draws the concept lattice of a given context, passing all remaining @@ -141,6 +178,15 @@ [ctx & args] (apply draw-lattice (concept-lattice ctx) args)) +(defn draw-protoconcepts + "Draws protoconcepts (ordered set) with given layout. + Passes all other parameters to draw-layout." + [protoconcepts & args] + (let [map (apply hash-map args), + layout-fn (get map :layout-fn standard-layout) + value-fn (get map :value-fn (constantly nil))] + (apply draw-layout (-> protoconcepts layout-fn (to-valued-layout value-fn)) :title "conexp-clj Protoconcepts" args))) + ;;; (defn draw-lattice-to-file diff --git a/src/main/clojure/conexp/gui/draw/control/dim_draw.clj b/src/main/clojure/conexp/gui/draw/control/dim_draw.clj index dadadecac..53a874693 100644 --- a/src/main/clojure/conexp/gui/draw/control/dim_draw.clj +++ b/src/main/clojure/conexp/gui/draw/control/dim_draw.clj @@ -22,20 +22,20 @@ ^JButton greedy (make-button buttons "Greedy")] (listen exact :action (fn [_] - (update-layout-of-scene scn (dim-draw-layout (lattice (get-layout-from-scene scn)))) + (update-layout-of-scene scn (dim-draw-layout (poset (get-layout-from-scene scn)))) (call-scene-hook scn :update-grid) (fit-scene-to-layout scn))) (listen genetic :action (fn [_] (update-layout-of-scene scn (dim-draw-layout - (lattice (get-layout-from-scene scn)) + (poset (get-layout-from-scene scn)) "genetic" (int (.getValue ind)) (int (.getValue gen)))) (fit-scene-to-layout scn))) (listen greedy :action (fn [_] - (update-layout-of-scene scn (dim-draw-layout (lattice (get-layout-from-scene scn)) + (update-layout-of-scene scn (dim-draw-layout (poset (get-layout-from-scene scn)) "greedy")) (fit-scene-to-layout scn))))) diff --git a/src/main/clojure/conexp/gui/draw/control/file_exporter.clj b/src/main/clojure/conexp/gui/draw/control/file_exporter.clj index 6e3a91cde..0e8ca2c0e 100644 --- a/src/main/clojure/conexp/gui/draw/control/file_exporter.clj +++ b/src/main/clojure/conexp/gui/draw/control/file_exporter.clj @@ -29,11 +29,13 @@ jpg-filter (FileNameExtensionFilter. "JPEG Files" (into-array ["jpg" "jpeg"])), gif-filter (FileNameExtensionFilter. "GIF Files" (into-array ["gif"])), tikz-filter (FileNameExtensionFilter. "TIKZ Files" (into-array ["tikz"])) + layout-filter (FileNameExtensionFilter. "Layout Files" (into-array ["layout"])) png-filter (FileNameExtensionFilter. "PNG Files" (into-array ["png"]))] (doto fc (.addChoosableFileFilter jpg-filter) (.addChoosableFileFilter gif-filter) (.addChoosableFileFilter tikz-filter) + (.addChoosableFileFilter layout-filter) (.addChoosableFileFilter png-filter)) (listen save-button :action (fn [_] @@ -41,9 +43,29 @@ (when (= retVal JFileChooser/APPROVE_OPTION) (let [^File file (.getSelectedFile fc)] (with-swing-error-msg frame "Error while saving" - (if (= "tikz" (get-file-extension file)) - (write-layout :tikz (get-layout-from-scene scn) (.getAbsolutePath file)) - (save-image scn file (get-file-extension file)))))))))) + (case (get-file-extension file) + "tikz" (write-layout :tikz (get-layout-from-scene scn) (.getAbsolutePath file)) + "layout" (write-layout :simple (get-layout-from-scene scn) (.getAbsolutePath file)) + (save-image scn file (get-file-extension file)))))))))) + nil) + +(defn import-from-file + "Installs a file exporter." + [frame scn buttons] + (let [^JButton load-button (make-button buttons "Import from File"), + ^JFileChooser fc (JFileChooser.), + layout-filter (FileNameExtensionFilter. "Layout Files" (into-array ["layout"]))] + (doto fc + (.addChoosableFileFilter layout-filter)) + (listen load-button :action + (fn [_] + (let [retVal (.showSaveDialog fc frame)] + (when (= retVal JFileChooser/APPROVE_OPTION) + (let [^File file (.getSelectedFile fc)] + (with-swing-error-msg frame "Error while saving" + (let [layout (read-layout (.getAbsolutePath file))] + (update-layout-of-scene scn layout) + (fit-scene-to-layout scn layout))))))))) nil) ;;; diff --git a/src/main/clojure/conexp/gui/draw/control/freese.clj b/src/main/clojure/conexp/gui/draw/control/freese.clj index 59a239fcf..38097c23c 100644 --- a/src/main/clojure/conexp/gui/draw/control/freese.clj +++ b/src/main/clojure/conexp/gui/draw/control/freese.clj @@ -15,7 +15,7 @@ [frame scn buttons] (let [^JButton btn (make-button buttons "Freese"), ^JSpinner spn (make-spinner buttons 0 (* 2 Math/PI) 0 0.01), - layout (interactive-freese-layout (lattice (get-layout-from-scene scn))), + layout (interactive-freese-layout (poset (get-layout-from-scene scn))), get-value #(.getValue spn), ^JButton rotate (make-button buttons "Rotate"), rotate-thread (atom nil), diff --git a/src/main/clojure/conexp/gui/draw/control/parameters.clj b/src/main/clojure/conexp/gui/draw/control/parameters.clj index 2ab4ec291..366c696df 100644 --- a/src/main/clojure/conexp/gui/draw/control/parameters.clj +++ b/src/main/clojure/conexp/gui/draw/control/parameters.clj @@ -1,5 +1,6 @@ (ns conexp.gui.draw.control.parameters - (:require [conexp.fca.metrics :refer [elements-distributivity elements-modularity]] + (:require [conexp.fca.metrics :refer :all] + [conexp.fca.lattices :refer [extract-context-from-bv]] [conexp.gui.draw.control.util :refer :all] [conexp.gui.draw.nodes-and-connections :refer :all] [conexp.gui.draw.scene-layouts :refer :all] @@ -14,10 +15,13 @@ ;;; -(declare single-move-mode, ideal-move-mode, filter-move-mode, chain-move-mode, - infimum-additive-move-mode, supremum-additive-move-mode, - no-valuation-mode, count-int-valuation-mode, count-ext-valuation-mode, - modularity-valuation-mode, distributivity-valuation-mode) +(declare single-move-mode, ideal-move-mode, filter-move-mode, + chain-move-mode, infimum-additive-move-mode, + supremum-additive-move-mode, no-valuation-mode, + count-int-valuation-mode, count-ext-valuation-mode, + modularity-valuation-mode, distributivity-valuation-mode, + separation-index-mode, support-valuation-mode, + stability-valuation-mode, probability-valuation-mode) ;;; @@ -70,7 +74,11 @@ "count-ints" 'count-int-valuation-mode, "count-exts" 'count-ext-valuation-mode, "distributivity" 'distributivity-valuation-mode, - "modularity" 'modularity-valuation-mode} + "modularity" 'modularity-valuation-mode, + "separation-index" 'separation-index-mode + "support" 'support-valuation-mode, + "stability" 'stability-valuation-mode, + "probability" 'probability-valuation-mode} ^JComboBox combo-box (make-combo-box buttons (keys valuation-modes)), current-valuation-mode (atom (valuation-modes "none"))] (listen combo-box :action @@ -81,10 +89,12 @@ (reset! current-valuation-mode valuation-mode)) (if (not (nil? @current-valuation-mode)) (let [layout (get-layout-from-scene scn) - thelattice (lattice layout) + thelattice (poset layout) thens (find-ns 'conexp.gui.draw.control.parameters) val-fn (ns-resolve thens @current-valuation-mode) - newlayout (update-valuations layout (partial val-fn thelattice))] + newlayout (try (update-valuations layout (partial val-fn thelattice)) + (catch Exception e + (update-valuations-error layout)))] (update-valuations-of-scene scn newlayout) (fit-scene-to-layout scn newlayout)))))) @@ -127,7 +137,7 @@ layout-fn (get layouts selected), layout (scale-layout [0.0 0.0] [100.0 100.0] - (layout-fn (lattice (get-layout-from-scene scn))))] + (layout-fn (poset (get-layout-from-scene scn))))] (update-layout-of-scene scn layout) (fit-scene-to-layout scn layout))))) @@ -303,6 +313,28 @@ [lat c] (float (elements-distributivity lat c))) +(defn- separation-index-mode + [lat c] + (let [ctx (extract-context-from-bv lat)] + (float (separation-index ctx c)))) + +(defn- support-valuation-mode + [lat c] + (float (/ (count (first c)) + (apply max (map (comp count first) (.base-set lat)))))) + +(defn- stability-valuation-mode + [lat c] + (let [ctx (extract-context-from-bv lat)] + (float (concept-stability ctx c)))) + +(defn- probability-valuation-mode + [lat c] + (let [ctx (extract-context-from-bv lat)] + (binding [*fast-computation* true] + (if (empty? (second c)) 1 + (float (concept-probability ctx c)))))) + (defn- no-valuation-mode [_ concept] "") diff --git a/src/main/clojure/conexp/gui/draw/scene_layouts.clj b/src/main/clojure/conexp/gui/draw/scene_layouts.clj index 8e2b42212..b679c45b3 100644 --- a/src/main/clojure/conexp/gui/draw/scene_layouts.clj +++ b/src/main/clojure/conexp/gui/draw/scene_layouts.clj @@ -54,38 +54,53 @@ ([^GScene scene] (fit-scene-to-layout scene (get-layout-from-scene scene))) ([^GScene scene, layout] - (let [[x_min y_min x_max y_max] (enclosing-rectangle (vals (positions layout))), - width (- x_max x_min), - height (- y_max y_min), - width (if (zero? width) - 1 - width), - height (if (zero? height) - 1 - height)] - (.setWorldExtent scene - (double (- x_min (* 0.05 width))) - (double (- y_min (* 0.05 height))) - (double (* 1.10 width)) - (double (* 1.10 height))) - (.unzoom scene) - (call-scene-hook scene :image-changed)))) + (do (add-data-to-scene scene :layout layout) + (let [[x_min y_min x_max y_max] (enclosing-rectangle (vals (positions layout))), + width (- x_max x_min), + height (- y_max y_min), + width (if (zero? width) + 1 + width), + height (if (zero? height) + 1 + height)] + (.setWorldExtent scene + (double (- x_min (* 0.05 width))) + (double (- y_min (* 0.05 height))) + (double (* 1.10 width)) + (double (* 1.10 height))) + (.unzoom scene) + (call-scene-hook scene :image-changed))))) (defn update-layout-of-scene "Updates layout according to new layout. The underlying lattice must not be changed." [^GScene scene, layout] - (let [pos (positions layout)] - (do-nodes [node scene] - (let [[x y] (pos (get-name node))] - (move-node-unchecked-to node x y))))) + + (do + (let [old-layout (-> scene get-layout-from-scene) + get-value-fn (fn [L] + (if (or (instance? java.util.concurrent.Future (.valuations L)) + (instance? clojure.lang.Ref (.valuations L))) + (deref (.valuations L)) + (.valuations L))) + old-value-fn (get-value-fn old-layout)] + (if (-> layout get-value-fn empty?) + (add-data-to-scene scene :layout + (update-valuations layout old-value-fn)) + (add-data-to-scene scene :layout layout))) + (let [pos (positions layout)] + (do-nodes [node scene] + (let [[x y] (pos (get-name node))] + (move-node-unchecked-to node x y)))))) (defn update-valuations-of-scene "Updates valutions according to new valuation function. The underlying lattice must not be changed." [^GScene scene, layout] - (do-nodes [node scene] - (revaluate-node-unchecked node (valuations layout)))) + (do (add-data-to-scene scene :layout layout) + (do-nodes [node scene] + (revaluate-node-unchecked node (valuations layout))))) (defn set-layout-of-scene "Sets given layout as current layout of scene." diff --git a/src/main/clojure/conexp/gui/editors/contexts.clj b/src/main/clojure/conexp/gui/editors/contexts.clj index 04d9e31b3..c88d2e16a 100644 --- a/src/main/clojure/conexp/gui/editors/contexts.clj +++ b/src/main/clojure/conexp/gui/editors/contexts.clj @@ -3,6 +3,7 @@ (:require [conexp.base :refer :all] [conexp.fca.contexts :refer :all] [conexp.fca.lattices :refer :all] + [conexp.fca.protoconcepts :refer :all] [conexp.gui.draw :refer :all] [conexp.gui.editors.context-editor.context-editor :refer :all] [conexp.gui.plugins.base :refer :all] @@ -78,6 +79,16 @@ (standard-layout (concept-lattice thing))) "Concept-Lattice")))) +(defn- show-protoconcepts-and-go + "Shows protoconcepts of current tab." + [frame] + (with-swing-error-msg frame "Error" + (let [thing (get-context-from-panel (current-tab frame))] + (add-tab frame + (make-lattice-editor frame + (standard-layout (protoconcepts-order thing))) + "Protoconcepts")))) + ;;; The Hooks (defn- context-menu @@ -116,7 +127,10 @@ :separator (menu-item :text "Show Concept Lattice", :listen [:action (fn [_] - (show-lattice-and-go frame))])])) + (show-lattice-and-go frame))]) + (menu-item :text "Show Protoconcepts", + :listen [:action (fn [_] + (show-protoconcepts-and-go frame))])])) (let [menu-hash (atom {})] diff --git a/src/main/clojure/conexp/gui/editors/lattices.clj b/src/main/clojure/conexp/gui/editors/lattices.clj index 88848e1be..4c97b5bbf 100644 --- a/src/main/clojure/conexp/gui/editors/lattices.clj +++ b/src/main/clojure/conexp/gui/editors/lattices.clj @@ -66,7 +66,7 @@ (let [layout (get-layout-from-panel (current-tab frame))] (if (nil? layout) (illegal-argument "Current tab does not contain a lattice editor.") - (add-tab frame (make-context-editor (standard-context (lattice layout))) + (add-tab frame (make-context-editor (standard-context (poset layout))) "Standard-Context"))))) @@ -97,7 +97,7 @@ (menu-item :text (str "Format " (name format)), :listen [:action (fn [_] (save-layout frame - lattice + poset write-lattice format))])) (list-lattice-output-formats))), diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 6cbdd2dfc..6b237e070 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -12,8 +12,13 @@ [conexp.io.util :refer :all] [conexp.fca.many-valued-contexts :refer :all] [conexp.io.latex :refer :all] + [conexp.io.json :refer :all] [clojure.string :refer (split)] - [clojure.data.xml :as xml]) + [clojure.data.xml :as xml] + [clojure.data.json :as json] + [json-schema.core :as json-schema] + [clojure.data.csv :as csv] + [clojure.java.io :as io]) (:import [java.io PushbackReader])) @@ -109,8 +114,8 @@ [ctx file] (with-out-writer file (println \A) - (println (count objects)) - (println (count attributes)) + (println (count (objects ctx))) + (println (count (attributes ctx))) (let [inz (incidence ctx)] (doseq [g (objects ctx)] (doseq [m (attributes ctx)] @@ -326,20 +331,21 @@ (add-context-input-format :csv (fn [rdr] (try - (re-matches #"^[^,]+,[^,]+$" (read-line)) + (let [first-line (read-line)] + (and (re-matches #"^[^,]+,[^,]+$" first-line) + ;; do not read empty json context as csv + (not= first-line "{\"objectsn:[],\"attributes\":[],\"incidence\":[]}"))) (catch Exception _)))) (define-context-input-format :csv [file] - (with-in-reader file - (loop [inz #{}] - (let [line (read-line)] - (if (not line) - (make-context-nc (set-of g [[g m] inz]) - (set-of m [[g m] inz]) - inz) - (let [[r g m] (re-matches #"^((?:[^,]+|\".*\")),((?:[^,]+|\".*\"))$" line)] - (recur (conj inz [g m])))))))) + (with-open [reader (io/reader file)] + (let [csv-list (doall + (csv/read-csv reader)) + obj (set (map first csv-list)) + attr (set (map second csv-list)) + incidence (set csv-list)] + (make-context-nc obj attr incidence)))) (define-context-output-format :csv [ctx file] @@ -347,9 +353,9 @@ (and (string? x) (some #(= \, %) x))) (concat (objects ctx) (attributes ctx))) (unsupported-operation "Cannot export to :csv format, object or attribute names contain \",\".")) - (with-out-writer file - (doseq [[g m] (incidence-relation ctx)] - (println (str g "," m))))) + (with-open [writer (io/writer file)] + (csv/write-csv writer + (incidence-relation ctx)))) ;; Binary CSV (:binary-csv) @@ -360,64 +366,81 @@ (define-context-input-format :binary-csv [file] - (with-in-reader file - (read-line) - (let [first-line (split (read-line) #",") - atts (range (count first-line))] - (loop [objs #{0}, - incidence (set-of [0 n] | n atts, :when (= (nth first-line n) "1"))] - (if-let [line (read-line)] - (let [line (split line #","), - i (count objs)] - (recur (conj objs i) - (into incidence - (for [n atts :when (= (nth line n) "1")] - [i n])))) - (make-context objs atts incidence)))))) + (with-open [reader (io/reader file)] + (let [csv-list (rest ; remove the first element [binary CSV] + (doall + (csv/read-csv reader))) + num-objects (count csv-list) + num-attributes (count (first csv-list)) + incidences (map #(Integer/parseInt %) + (into [] (apply concat csv-list)))] + (make-context-from-matrix num-objects num-attributes incidences)))) (define-context-output-format :binary-csv [ctx file] (when (or (empty? (objects ctx)) (empty? (attributes ctx))) (unsupported-operation "Cannot export empty context in binary-csv format")) + (when (some (fn [x] + (and (string? x) (some #(= \, %) x))) + (concat (objects ctx) (attributes ctx))) + (unsupported-operation "Cannot export to :binary-csv format, object or attribute names contain \",\".")) (let [objs (sort (objects ctx)), - atts (sort (attributes ctx))] - (with-out-writer file - (println "binary CSV") - (doseq [g objs] - (loop [atts atts] - (when-let [m (first atts)] - (print (if (incident? ctx g m) - "1" - "0")) - (when (next atts) - (print ",")) - (recur (rest atts)))) - (println))))) + atts (sort (attributes ctx)) + output-matrix (for [obj objs] + (for [att atts] + (if (incident? ctx obj att) 1 0)))] + (with-open [writer (io/writer file)] + (csv/write-csv writer + (conj output-matrix ["binary CSV"]) ; add "binary CSV as first line of output + )))) + +(add-context-input-format :named-binary-csv + (fn [rdr] + (try (= "NB" (subs (read-line) 0 2)) + ;; if file is empty, read-line returns nil + (catch NullPointerException _) + ;; first line of file can contain less than 2 characters + (catch StringIndexOutOfBoundsException _)))) + +(define-context-input-format :named-binary-csv + [file] + (with-open [reader (io/reader file)] + (let [csv-list (doall + (csv/read-csv reader))] + (let [M ((comp rest first) csv-list) rows (rest csv-list) + G (reduce (fn [s row] + (conj s (first row))) + [] + rows) + I (reduce (fn [s row] + (into s + (map #(Integer/parseInt %) + (rest row)))) + [] + rows)] + (make-context-from-matrix G M I))))) (define-context-output-format :named-binary-csv [ctx file] (when (or (empty? (objects ctx)) (empty? (attributes ctx))) - (unsupported-operation "Cannot export empty context in binary-csv format")) + (unsupported-operation "Cannot export empty context in named-binary-csv format")) + (when (some (fn [x] + (and (string? x) (some #(= \, %) x))) + (concat (objects ctx) (attributes ctx))) + (unsupported-operation "Cannot export to :named-binary-csv format, object or attribute names contain \",\".")) (let [objs (sort (objects ctx)), - atts (sort (attributes ctx))] - (with-out-writer file - (print "objects") - (doseq [m atts] - (print ", " m)) - (println) - (doseq [g objs] - (print g ",") - (loop [atts atts] - (when-let [m (first atts)] - (print (if (incident? ctx g m) - "1" - "0")) - (when (next atts) - (print ",")) - (recur (rest atts)))) - (println))))) + atts (sort (attributes ctx)) + output-matrix (conj (for [obj objs] + (into [obj] (for [att atts] + (if (incident? ctx obj att) + 1 + 0)))) + (into ["NB"] atts))] + (with-open [writer (io/writer file)] + (csv/write-csv writer + output-matrix)))) ;; output as tex array @@ -562,6 +585,93 @@ (catch javax.xml.stream.XMLStreamException _ (illegal-argument "Specified file does not contain valid XML.")))) +(define-context-output-format :tex + [ctx file & options] + (let [{:keys [objorder attrorder] + :or {objorder (constantly true), + attrorder (constantly true)}} options] + (with-out-writer file + (println "\\begin{cxt}") + (println "\\cxtName{}") + (let [attr (sort-by attrorder (attributes ctx)) + obj (sort-by objorder (objects ctx))] + (doseq [a attr] + (println (str "\\att{" a "}"))) + (doseq [o obj] + (println (str "\\obj{" + (clojure.string/join "" + (for [a attr] + (if ((incidence ctx) [o a]) "x" "."))) + "}{" o "}"))) + (println "\\end{cxt}"))))) + +;; Json helpers + +(defn- object->json + "Returns an objects with its attributes as a map that can easily be converted into json format. + + Example output: + {object: \"b\", + attributes: [\"1\", \"2\"]}" + [ctx object] + {:object object + :attributes (filter #(incident? ctx object %) (attributes ctx))}) + +(defn ctx->json + "Returns a formal context as a map that can easily be converted into json format. + + Example: + {:objects #{\"b\"} + :attributes #{\"1\" \"2\"} + :incidence #{[\"b\" \"1\"] [\"b\" \"2\"]}" + [ctx] + {:objects (objects ctx) + :attributes (attributes ctx) + :incidence (if (= (type ctx) clojure.lang.PersistentHashSet) + (incidence ctx) + (into #{} (for [o (objects ctx) + a (attributes ctx) + :when (incident? ctx o a)] + [o a])))} + ;; {:attributes (into () (attributes ctx)) + ;; :adjacency-list + ;; (mapv (partial object->json ctx) (objects ctx))} + ) + +(defn- json-ctx->incidence + "Returns the incidence of a json context as set of tuples." + [json-ctx] + (set-of [o a] [o [(:object json-ctx)], a (:attributes json-ctx)])) + +(defn json->ctx + "Returns a Context object for the given json context." + [json-ctx] + (let [attributes (:attributes json-ctx) + objects (:objects json-ctx) + incidence (:incidence json-ctx)] + (make-context objects attributes incidence))) + +;; Json Format (src/main/resources/schemas/context_schema_v1.1.json) + +(add-context-input-format :json + (fn [rdr] + (try (json-object? rdr) + (catch Exception _)))) + +(define-context-output-format :json + [ctx file] + (with-out-writer file + (print (json/write-str (ctx->json ctx))))) + +(define-context-input-format :json + [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"] + (assert (matches-schema? file-content schema-file) + (str "The input file does not match the schema given at " schema-file ".")) + (json->ctx file-content)))) + ;;; TODO ;; slf diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj new file mode 100644 index 000000000..024748c73 --- /dev/null +++ b/src/main/clojure/conexp/io/fcas.clj @@ -0,0 +1,71 @@ +;; 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.io.fcas + (:require [conexp.io.contexts :refer :all] + [conexp.io.implications :refer :all] + [conexp.io.json :refer :all] + [conexp.io.lattices :refer :all] + [conexp.io.util :refer :all] + [clojure.data.json :as json] + [json-schema.core :as json-schema])) + +;;; Input format dispatch + +(define-format-dispatch "fca") +(set-default-fca-format! :json) + +;;; Formats + +;; Json Format (src/main/resources/schemas/fca_schema_v1.0.json) + +(defn- create-fca-output-map + "Returns a map containing the elements of the fca in json format each. + + The map contains :lattice and :implication-sets only if they are included in the fca." + [fca] + (let [ctx (:context fca) + lattice (:lattice fca) + implication-sets (:implication-sets fca)] + (cond-> {:context (ctx->json ctx)} + (some? lattice) (assoc :lattice (lattice->json lattice)) + (some? implication-sets) (assoc :implication_sets (mapv implications->json implication-sets))))) + +(defn- create-fca-input-map + "Takes a map {:context context, :lattice lattice, :implication_sets [implication-sets]} as input in which the context, lattice and implication-sets are in json format. Returns a map containing the elements of the fca from the json-fca. + + :lattice and :implication-sets are optional. The output map contains them only if they are included in the json-fca." + [json-fca] + (let [json-ctx (:context json-fca) + json-lattice (:lattice json-fca) + json-implication-sets (:implication_sets json-fca)] + (cond-> {:context (json->ctx json-ctx)} + (some? json-lattice) (assoc :lattice (json->lattice json-lattice)) + (some? json-implication-sets) (assoc :implication-sets (map json->implications json-implication-sets))))) + + +(add-fca-input-format :json + (fn [rdr] + (try (json-object? rdr) + (catch Exception _)))) + +(define-fca-output-format :json + [fca file] + (let [fca-map (create-fca-output-map fca)] + (with-out-writer file + (print + (json/write-str fca-map))))) + +(define-fca-input-format :json + [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"] + (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 new file mode 100644 index 000000000..3531ce854 --- /dev/null +++ b/src/main/clojure/conexp/io/implications.clj @@ -0,0 +1,68 @@ +;; 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.io.implications + (:require [conexp.fca.implications :refer :all] + [conexp.io.util :refer :all] + [conexp.io.json :refer :all] + [clojure.data.json :as json] + [json-schema.core :as json-schema])) + +;;; Input format dispatch + +(define-format-dispatch "implication") +(set-default-implication-format! :json) + +;;; Formats + +;; Json helpers + +(defn implication->json + "Returns one implication in json format." + [implication] + {:premise (premise implication) + :conclusion (conclusion implication)}) + +(defn implications->json + "Returns a vector of implications in json format." + [implication-list] + {:implications + (mapv implication->json implication-list)}) + +(defn json->implication + "Returns an Implication object for the given json implication." + [json-impl] + (make-implication (into #{} (:premise json-impl)) + (into #{} (:conclusion json-impl)))) + +(defn json->implications + "Returns a sequence of Implication objects for the given json implications." + [json] + (let [impl (:implications json)] + (map json->implication impl))) + +;; Json Format (src/main/resources/schemas/implications_schema_v1.0.json) + +(add-implication-input-format :json + (fn [rdr] + (try (json-object? rdr) + (catch Exception _)))) + +(define-implication-output-format :json + [impl file] + (with-out-writer file + (print (json/write-str (implications->json impl))))) + +(define-implication-input-format :json + [file] + (with-in-reader file + (let [impl (json/read *in* :key-fn keyword) + schema-file "src/main/resources/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/incomplete_contexts.clj b/src/main/clojure/conexp/io/incomplete_contexts.clj new file mode 100644 index 000000000..66958f07b --- /dev/null +++ b/src/main/clojure/conexp/io/incomplete_contexts.clj @@ -0,0 +1,203 @@ +;; 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.io.incomplete-contexts + "Implements IO for Incomplete Contexts." + (:require + [conexp.base :refer [defalias illegal-argument unsupported-operation set-of]] + [conexp.fca.incomplete-contexts.incomplete-contexts :refer :all] + [conexp.fca.incomplete-contexts.conexp-interop :refer :all] + [conexp.io.util :refer [define-format-dispatch with-out-writer with-in-reader get-line get-lines]]) + (:import [java.io PushbackReader])) + +;;; Input format dispatch + +(define-format-dispatch "incomplete-context") +(set-default-incomplete-context-format! :json) + +(defalias read-?-context read-incomplete-context) +(defalias write-?-context write-incomplete-context) + + +;; Data Table Format + +;; Note the following restrictions: +;; - we need at least one object and at least two attributes (to +;; reliably determine the file type) +;; - the first line must contain of the attributes in the correct order +;; - if the subsequent lines have the same number of entries as the +;; first, the resulting mv-context will have the line number as +;; objects, +;; - if the subsequent lines have one more element as the first, the first +;; entry will be the object for that line + +(add-incomplete-context-input-format :data-table + (fn [rdr] + (try + (re-matches #"^[^,]+,[^,]+.*$" (read-line)) + (catch Exception _)))) + +(define-incomplete-context-output-format :data-table + [mv-context file] + (with-out-writer file + (when (> 2 (count (attributes mv-context))) + (unsupported-operation + "Cannot store many-valued contexts with less then 2 attributes in format :data-table.")) + (when (= 0 (count (objects mv-context))) + (unsupported-operation + "Cannot store many-valued context without objects in format :data-table.")) + (let [write-comma-line (fn [things] + (cond + (empty? things) nil, + (= 1 (count things)) (prn (first things)), + :else (do (pr (first things)) + (print ",") + (recur (rest things)))))] + (write-comma-line (attributes mv-context)) + (doseq [g (objects mv-context)] + (write-comma-line (cons g (map #((incidence mv-context) [g %]) + (attributes mv-context)))))))) + +(define-incomplete-context-input-format :data-table + [file] + (with-in-reader file + (let [read-comma-line (fn [] + (try + (let [line (get-line)] + (read-string (str "(" line ")"))) + (catch java.io.EOFException _ + nil))), + attributes (read-comma-line), + lines (doall + (take-while #(not (nil? %)) + (repeatedly read-comma-line))), + line-lengths (set-of (count line) [line lines])] + (when (< 1 (count line-lengths)) + (illegal-argument "Many-Valued Context in file " file " has lines of different length.")) + (when (and (not= (count attributes) (first line-lengths)) + (not= (inc (count attributes)) (first line-lengths))) + (illegal-argument + "Number of values in lines in file " file " does not match given attributes.\n" + "Number of values given should be equal or once more to the number of attributes.")) + (let [lines (if (not= (first line-lengths) (count attributes)) + lines + (map #(cons %1 %2) (iterate inc 0) lines)), + objects (map first lines), + object-set (set objects)] + (when (not= (count objects) (count object-set)) + (illegal-argument "Given file " file " contains double entries for objects.")) + (let [interpretation (into {} + (for [line lines + :let [g (first line), + values (rest line), + mapped_values (map map-true-false-unknown-to-x-o-? values)], + [m w] (map vector attributes mapped_values)] + [[g m] w]))] + (make-incomplete-context object-set attributes interpretation)))))) + + + +;; Burmeister Format + +(add-incomplete-context-input-format :burmeister + (fn [rdr] + (= "B" (read-line)))) + + +(defn convert-incomplete-context-incidences-to-burmeister-output + "converts x to X; o to . and ? to ? for burmeister output" + [in] + (if (= in known-true) "X" (if (= in known-false) "." (if (= in unknown) "?" (throw "input not x,o,?"))))) + + +(define-incomplete-context-output-format :burmeister + [ctx file] + (with-out-writer file + (println \B) + (println) + (println (count (objects ctx))) + (println (count (attributes ctx))) + (println) + (doseq [g (objects ctx)] (println g)) + (doseq [m (attributes ctx)] (println m)) + (let [inz (incidence ctx)] + (doseq [g (objects ctx)] + (doseq [m (attributes ctx)] + (print (convert-incomplete-context-incidences-to-burmeister-output (inz [g m])))) + (println))))) + + +(define-incomplete-context-input-format :burmeister + [file] + (with-in-reader file + (let [_ (get-lines 2) ; "B\n\n", we don't support names + + number-of-objects (Integer/parseInt (.trim (get-line))) + number-of-attributes (Integer/parseInt (.trim (get-line))) + + _ (get-line) ; "\n" + + seq-of-objects (get-lines number-of-objects) + seq-of-attributes (get-lines number-of-attributes)] + (loop [objs seq-of-objects + incidence {}] + (if (empty? objs) + (make-incomplete-context (set seq-of-objects) + (set seq-of-attributes) + incidence) + (let [line (get-line)] + (recur (rest objs) + (into incidence + (for [idx-m (range number-of-attributes)] + [[(first objs) (nth seq-of-attributes idx-m)] (map-true-false-unknown-to-x-o-? (nth line idx-m))]))))))))) + + + + +;;; Json +(defn icxt->json + "Returns a formal context as a map that can easily be converted into json format." + [cxt] + (let [icxt (to-incomplete-context cxt)] + {:attributes (attributes icxt) + :objects (objects icxt) + :certain-incidences (mapv first (true-incidence icxt)) + :possible-incidences (mapv first (true-or-unknown-incidences icxt))} + )) + + + +(defn json->icxt + "Returns a Context object for the given json context." + [json-icxt] + (let [attributes (:attributes json-icxt) + objects (:objects json-icxt) + certain-incidences (into {} (map #(vector % known-true) (:certain-incidences json-icxt))) + possible-incidences (into {} (map #(vector % unknown) (:possible-incidences json-icxt))) + hm (into {} (map #(vector % known-false) (clojure.math.combinatorics/cartesian-product objects attributes))) + incidence (reduce into [hm possible-incidences certain-incidences]) + ] + (make-incomplete-context objects attributes incidence))) + + +(add-incomplete-context-input-format :json (fn [rdr] + (try (conexp.io.json/json-object? rdr) + (catch Exception _)))) + +(define-incomplete-context-output-format :json + [cxt file] + (with-out-writer file + (print (clojure.data.json/write-str (icxt->json cxt))))) + +(define-incomplete-context-input-format :json + [file] + (with-in-reader file + (let [file-content (clojure.data.json/read *in* :key-fn keyword) + json-cxt file-content] + (json->icxt json-cxt)))) +nil diff --git a/src/main/clojure/conexp/io/json.clj b/src/main/clojure/conexp/io/json.clj new file mode 100644 index 000000000..fae1f57a3 --- /dev/null +++ b/src/main/clojure/conexp/io/json.clj @@ -0,0 +1,42 @@ +;; 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.io.json + "Provides functionality to read and process json files" + (:require [json-schema.core :as json-schema] + [clojure.data.json :as json])) + +(defn- read-schema + "Returns the file content as Json object." + [file] + (json-schema/prepare-schema + (-> file slurp + (cheshire.core/parse-string true)) + ;; referencing inside of schemas with relative references + {:classpath-aware? true + :default-resolution-scope "classpath://schemas/"})) + +(defn- json-format? + "Validate if string is in json format" + [string] + (try (json/read-str string) true + (catch Exception _ false))) + +(defn json-object? + "Validate if file content is a json object (json format beginning with {)" + [rdr] + (let [content (slurp rdr)] + (and (json-format? content) (= \{ (first content))))) + +(defn matches-schema? + "Tests whether the Json object matches the given schema." + [json schema-file] + (let [schema (read-schema schema-file)] + (try (json-schema/validate schema json) + true + (catch Exception _ false)))) diff --git a/src/main/clojure/conexp/io/latex.clj b/src/main/clojure/conexp/io/latex.clj index f94b20450..ba0a215e8 100644 --- a/src/main/clojure/conexp/io/latex.clj +++ b/src/main/clojure/conexp/io/latex.clj @@ -12,7 +12,7 @@ [conexp.fca.contexts :only (objects attributes incidence)] conexp.fca.lattices conexp.fca.many-valued-contexts - [conexp.layouts.base :only (positions connections nodes inf-irreducibles sup-irreducibles annotation)])) + [conexp.layouts.base :only (positions connections nodes inf-irreducibles sup-irreducibles annotation valuations)])) ;;; @@ -114,24 +114,27 @@ (nodes layout)), vertex-idx (into {} (map-indexed (fn [i v] [v i]) - sorted-vertices))] + sorted-vertices)), + value-fn #(if (nil? ((valuations layout) %)) + "" ((valuations layout) %))] (with-out-str (println "\\colorlet{mivertexcolor}{blue}") (println "\\colorlet{jivertexcolor}{red}") (println "\\colorlet{vertexcolor}{mivertexcolor!50}") (println "\\colorlet{bordercolor}{black!80}") (println "\\colorlet{linecolor}{gray}") - (println "\\tikzset{vertexbase/.style={semithick, shape=circle, inner sep=2pt, outer sep=0pt, draw=bordercolor},%") - (println " vertex/.style={vertexbase, fill=vertexcolor!45},%") - (println " mivertex/.style={vertexbase, fill=mivertexcolor!45},%") - (println " jivertex/.style={vertexbase, fill=jivertexcolor!45},%") - (println " divertex/.style={vertexbase, top color=mivertexcolor!45, bottom color=jivertexcolor!45},%") + (println "% parameter corresponds to the used valuation function and can be addressed by #1") + (println "\\tikzset{vertexbase/.style 2 args={semithick, shape=circle, inner sep=2pt, outer sep=0pt, draw=bordercolor},%") + (println " vertex/.style 2 args={vertexbase={#1}{}, fill=vertexcolor!45},%") + (println " mivertex/.style 2 args={vertexbase={#1}{}, fill=mivertexcolor!45},%") + (println " jivertex/.style 2 args={vertexbase={#1}{}, fill=jivertexcolor!45},%") + (println " divertex/.style 2 args={vertexbase={#1}{}, top color=mivertexcolor!45, bottom color=jivertexcolor!45},%") (println " conn/.style={-, thick, color=linecolor}%") (println "}") (println "\\begin{tikzpicture}") (println " \\begin{scope} %for scaling and the like") (println " \\begin{scope} %draw vertices") - (println " \\foreach \\nodename/\\nodetype/\\xpos/\\ypos in {%") + (println " \\foreach \\nodename/\\nodetype/\\param/\\xpos/\\ypos in {%") (let [infs (set (inf-irreducibles layout)), sups (set (sup-irreducibles layout)), insu (intersection infs sups), @@ -144,12 +147,13 @@ (contains? sups v) "jivertex" (contains? infs v) "mivertex" :else "vertex") + "/" (value-fn v) "/" x "/" y))) sorted-vertices)] (doseq [x (interpose ",\n" vertex-lines)] (print x)) (println)) - (println " } \\node[\\nodetype] (\\nodename) at (\\xpos, \\ypos) {};") + (println " } \\node[\\nodetype={\\param}{}] (\\nodename) at (\\xpos, \\ypos) {};") (println " \\end{scope}") (println " \\begin{scope} %draw connections") (doseq [[v w] (connections layout)] @@ -159,16 +163,29 @@ (println " \\foreach \\nodename/\\labelpos/\\labelopts/\\labelcontent in {%") (let [ann (annotation layout), ann-lines (mapcat (fn [v] - (let [[u l] (map tex-escape (ann v)), + (let [[u _] (map tex-escape (ann v)), lines (if-not (= "" u) (list (str " " (vertex-idx v) "/above//{" u "}")) - ()), - lines (if-not (= "" l) - (conj lines - (str " " (vertex-idx v) "/below//{" l "}")) - lines)] + ())] lines)) - sorted-vertices)] + sorted-vertices) + ann-lines (concat ann-lines + (mapcat (fn [v] + (let [[_ l] (map tex-escape (ann v)), + val (value-fn v), + lines (if-not (= "" l) + (list (str " " (vertex-idx v) "/below//{" l "}")) + ())] + lines)) + sorted-vertices)) + ann-lines (concat ann-lines + (mapcat (fn [v] + (let [val (value-fn v), + lines (if-not (= "" val) + (list (str " " (vertex-idx v) "/right//{" val "}")) + ())] + lines)) + sorted-vertices))] (doseq [x (interpose ",\n" ann-lines)] (print x)) (println)) diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 06979452c..5a83501f0 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -7,10 +7,13 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.io.lattices + (:require [clojure.data.json :as json] + [json-schema.core :as json-schema]) (:use conexp.base conexp.math.algebra conexp.fca.lattices - conexp.io.util) + conexp.io.util + conexp.io.json) (:import [java.io PushbackReader])) ;;; Input format dispatch @@ -48,6 +51,62 @@ (illegal-argument "File " file " does not contain a lattice.")) (apply make-lattice lattice)))) +;; Json helpers +(defn lattice->json + "Returns a concept lattice, consisting of the base set and order, in json format." + [lat] + {:nodes (base-set lat) + :edges (set-of [x y] + [x (base-set lat) + y (base-set lat) + :when ((order lat) [x y])])}) + +(defn- json->concept + "Returns a vector containing both extent and intent." + [json-concept] + [(set (first json-concept)) + (set (second json-concept))]) + +(defn- json->concept-pair + "Returns a vector containing the concept pair." + [json-concept-pair] + [(json->concept (first json-concept-pair)) + (json->concept (second json-concept-pair))]) + +(defn json->lattice + "Returns a Lattice object for the given json lattice." + [json-lattice] + (let [json-base-set (:nodes json-lattice) + json-order (:edges json-lattice) + lattice-base-set (if (coll? (first json-base-set)) ;; if it is a concept-lattice + (map json->concept json-base-set) + (set json-base-set)) + lattice-order (if (coll? (first (first json-order))) ;; if it is a concept-lattice + (map json->concept-pair json-order) + (set json-order))] + (make-lattice lattice-base-set lattice-order))) + +;; Json Format (src/main/resources/schemas/lattice_schema_v1.1.json) + +(add-lattice-input-format :json + (fn [rdr] + (try (json-object? rdr) + (catch Exception _)))) + +(define-lattice-output-format :json + [lattice file] + (with-out-writer file + (print (json/write-str (lattice->json lattice))))) + +(define-lattice-input-format :json + [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"] + (assert (matches-schema? json-lattice schema-file) + (str "The input file does not match the schema given at " schema-file ".")) + (json->lattice json-lattice)))) + ;;; ConExp lattice format 'TODO diff --git a/src/main/clojure/conexp/io/layouts.clj b/src/main/clojure/conexp/io/layouts.clj index ab6aaf607..87f30778e 100644 --- a/src/main/clojure/conexp/io/layouts.clj +++ b/src/main/clojure/conexp/io/layouts.clj @@ -11,9 +11,11 @@ (:use conexp.base conexp.io.util conexp.io.latex + conexp.io.json conexp.layouts.util conexp.layouts.base) - (:require clojure.string) + (:require clojure.string + [clojure.data.json :as json]) (:import [java.io PushbackReader])) ;;; Input format dispatch @@ -164,6 +166,120 @@ [layout file] (unsupported-operation "Output in :fca-style is not yet supported.")) +;; Json helpers + +(defn json->nodes + [json-layout] + (try + (reduce + (fn [ncoll [k v]] + (assoc ncoll k + (mapv set v))) + {} + (apply conj (:nodes json-layout))) + (catch java.lang.IllegalArgumentException _ + (apply conj (:nodes json-layout))))) + +(defn json->positions + "Transforms the positions from json format to a map, using the id of the nodes." + [json-positions nodes] + (into {} + (map #(vector (get nodes (key %)) + (val %)) + json-positions))) + +(defn json->connections + "Transforms the connections from json format to a map, using the id of the nodes." + [json-connections nodes] + (map #(vector + (get nodes (first %)) + (get nodes (keyword (second %)))) + (for [A (keys json-connections) + B (get json-connections A)] + [A B]))) + +(defn- valuation-function + "Maps the json-valuations to the correct node." + [json-valuations nodes] + (fn [concept] + (get (into {} + (map #(vector (get nodes (key %)) + (val %)) + json-valuations)) + concept))) + +(defn json->layout + "Returns a Layout object for the given json layout." + [json-layout] + (let [nodes (json->nodes json-layout) + positions (apply conj (:positions json-layout)) + edges (apply conj (:edges json-layout)) + valuations (apply conj (:valuations json-layout)) + positions (json->positions positions nodes) + connections (json->connections edges nodes) + layout (make-layout positions connections)] + (if (every? #(nil? (val %)) valuations) + layout + (update-valuations layout (valuation-function valuations nodes))))) + +(defn layout->json + "" + [layout] + (let [vertex-pos (positions layout) + sorted-vertices (sort #(let [[x_1 y_1] (vertex-pos %1), + [x_2 y_2] (vertex-pos %2)] + (or (< y_1 y_2) + (and (= y_1 y_2) + (< x_1 x_2)))) + (nodes layout)), + vertex-idx (into {} + (map-indexed (fn [i v] [v i]) + sorted-vertices))] + (let [nodes (map #(hash-map (key %) (val %)) (map-invert vertex-idx)) + pos (into [] + (for [n sorted-vertices] + {(vertex-idx n), (vertex-pos n)})) + edges (map #(hash-map (key %) (val %)) + (reduce + (fn [ncoll [k v]] + (assoc ncoll k (conj (get ncoll k) v))) + {} + (for [[A B] (connections layout)] + [(vertex-idx A),(str(vertex-idx B))]))) + v (into [] + (for [n sorted-vertices] + {(vertex-idx n), ((valuations layout) n)})) + + ann (into [] + (for [n sorted-vertices] + {(vertex-idx n), ((annotation layout) n)}))] + (hash-map :nodes nodes + :positions pos + :edges edges + :valuations v + :shorthand-annotation ann)))) + +;;; Json Format (src/main/resources/schemas/layout_schema_v1.0.json) + +(add-layout-input-format :json + (fn [rdr] + (try (json-object? rdr) + (catch Exception _)))) + +(define-layout-output-format :json + [layout file] + (with-out-writer file + (print (json/write-str (layout->json layout))))) + +(define-layout-input-format :json + [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"] + (assert (matches-schema? json-layout schema-file) + (str "The input file does not match the schema given at " schema-file ".")) + (json->layout json-layout)))) + ;;; nil diff --git a/src/main/clojure/conexp/io/many_valued_contexts.clj b/src/main/clojure/conexp/io/many_valued_contexts.clj index 3fd709d0b..9a6d782c4 100644 --- a/src/main/clojure/conexp/io/many_valued_contexts.clj +++ b/src/main/clojure/conexp/io/many_valued_contexts.clj @@ -8,10 +8,13 @@ (ns conexp.io.many-valued-contexts "Implements IO for Many-Valued Contexts." + (:require [clojure.data.json :as json] + [clojure.edn :as edn]) (:use conexp.base conexp.fca.contexts conexp.fca.many-valued-contexts - conexp.io.util) + conexp.io.util + conexp.io.json) (:import [java.io PushbackReader])) ;;; Input format dispatch @@ -66,7 +69,10 @@ (add-mv-context-input-format :data-table (fn [rdr] (try - (re-matches #"^[^,]+,[^,]+.*$" (read-line)) + (let [first-line (read-line)] + (and (re-matches #"^[^,]+,[^,]+.*$" (read-line)) + ;; do not read empty json mv-context as data-table + (not= first-line "{\"objectsn:[],\"attributes\":[],\"incidence\":[]}"))) (catch Exception _)))) (define-mv-context-output-format :data-table @@ -126,6 +132,44 @@ [[g m] w]))] (make-mv-context object-set attributes interpretation)))))) +;;; Json format +(defn mv-context->json + "Returns a mv-context in json format" + [mv-context] + {:objects (objects mv-context) + :attributes (attributes mv-context) + :incidence (incidence mv-context)}) + +(defn json->mv-context + "Returns a mv-context object for the given json mv-context." + [json-mv-context] + (let [incidence (set (for [[k v] (:incidence json-mv-context)] + (map #(if (symbol? %) (str %) %) + (conj (edn/read-string (name k)) v))))] + (make-mv-context + (:objects json-mv-context) + (:attributes json-mv-context) + incidence))) + +(add-mv-context-input-format :json + (fn [rdr] + (try (json-object? rdr) + (catch Exception _)))) + +(define-mv-context-output-format :json + [mv-context file] + (with-out-writer file + (print (json/write-str (mv-context->json mv-context))))) + +(define-mv-context-input-format :json + [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"] + (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)))) + ;;; nil diff --git a/src/main/clojure/conexp/io/smeasure.clj b/src/main/clojure/conexp/io/smeasure.clj new file mode 100644 index 000000000..f6f213fc5 --- /dev/null +++ b/src/main/clojure/conexp/io/smeasure.clj @@ -0,0 +1,323 @@ +;; 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.io.smeasure + "Provides functionality to represent conexp-clj smeasures as latex code." + (:use conexp.base + conexp.fca.contexts + conexp.fca.lattices + conexp.fca.smeasure + [conexp.layouts.base :refer [positions nodes inf-irreducibles + sup-irreducibles connections annotation]] + conexp.layouts.dim-draw + conexp.io.latex)) + +;;; Smeasure + +(declare smeasure->tikz) +(declare smeasure->lattice) + +(extend-type conexp.fca.smeasure.Smeasure + LaTeX + (latex + ([this] + (latex this :tikz)) + ([this choice] + (case choice + :tikz (smeasure->tikz this) + :lattice (smeasure->lattice this) + true (illegal-argument + "Unsupported latex format " choice " for contexts."))))) + +(defn- smeasure->tikz + [smeasure] + (let [ctx (context smeasure) + scale (scale smeasure) + mapping (measure smeasure) + ctx-obj (group-by #(mapping %) (objects ctx)) + sca-obj (loop [order (vec (difference (objects scale) + (keys ctx-obj))) + next (keys ctx-obj) + place (if (> (count (objects scale)) + (count (objects ctx))) + (int (/ (- (count (objects scale)) + (count (objects ctx))) + 2)) + 0)] + (if (empty? next) + order + (let [element (first next) + pos (+ place + (int + (/ (- (count (get ctx-obj element)) + 1) + 2))) + new-order (vec (concat (take pos order) + (list element) + (drop pos order))) + new-place (+ place (count (get ctx-obj element)))] + (recur new-order (drop 1 next) new-place))))] + (with-out-str + (println "%necessary tikz libraries") + (println "%\\usetikzlibrary{tikzmark,arrows,positioning}") + (println "\\begin{figure}") + (println " \\centering") + ;; + ;; original context + (println " \\begin{minipage}[c]{.5\\textwidth}") + (println " \\centering") + (println " \\begin{cxt}%") + (println " \\cxtName{}%") + (doseq [m (attributes ctx)] + (if (>= 2 (count (str m))) + (println (str " \\att{\\tikzmarknode{ca" (tex-escape m) + "}{" (tex-escape m) "}}%")) + (println (str " \\atr{\\tikzmarknode{ca" (tex-escape m) + "}{" (tex-escape m) "}}%")))) + (let [inz (incidence ctx)] + (doall + (for [[k objs] ctx-obj] + (doseq [g objs] + (print " \\obj{") + (doseq [m (attributes ctx)] + (print (if (inz [g m]) "x" "."))) + (println (str "}{" (tex-escape g) "}")))))) + (println " \\end{cxt}") + (println " \\end{minipage}%") + ;; + ;; scale context + (println " \\begin{minipage}[c]{.5\\textwidth}") + (println " \\centering") + (println " \\begin{cxt}%") + (println " \\cxtName{}%") + (doseq [m (attributes scale)] + (if (>= 2 (count (str m))) + (println (str " \\att{" (tex-escape m) "}%")) + (println (str " \\atr{" (tex-escape m) "}%")))) + (doall (let [inz (incidence scale)] + (doseq [g sca-obj] + (print " \\obj{") + (doseq [m (attributes scale)] + (print (if (inz [g m]) "x" "."))) + (println (str "}{\\tikzmarknode{so" (tex-escape g) + "}{" (tex-escape g) "}}"))))) + (println " \\end{cxt}") + (println " \\end{minipage}%") + (println "\\end{figure}") + ;; + ;; tikz overlay + (println "\\begin{tikzpicture}[overlay,remember picture]") + (let [ctx-obj-list (flatten (map second (vec ctx-obj))) + anchor (last (butlast (attributes ctx)))] + (doall (if (>= 2 (count (str anchor))) + (do + (println (str " \\node[right = 0.45cm of ca" + (tex-escape anchor) + "] (za) {};")) + (println (str " \\node[below = 0.25cm of za] (co" + (first ctx-obj-list) ") {};"))) + (println (str " \\node[below = 0.25cm of ca" + (tex-escape anchor) + "] (co" (first ctx-obj-list) ") {};")))) + (doall + (map + (fn[a b] + (println (str " \\node[below = 0.16cm of co" + (tex-escape a) + "] (co" (tex-escape b) ") {};"))) + ctx-obj-list + (drop 1 ctx-obj-list))) + (doall + (map + #(println (str " \\node[left = 0cm of so" % "](lso" % "){};")) + (objects scale))) + (doall + (for [obj ctx-obj-list] + (println (str " \\draw[->, >=stealth] (co" + (tex-escape obj) ") -- (lso" + (tex-escape (mapping obj)) ");"))))) + (println "\\end{tikzpicture}")))) + +;;; Smeasure + Lattice + +(defn- smeasure->lattice + [smeasure] + (let [ctx (context smeasure) + scale (scale smeasure) + mapping (measure smeasure) + ctx-obj (group-by #(mapping %) (objects ctx))] + (with-out-str + (println "%necessary tikz libraries") + (println "%\\usetikzlibrary{tikzmark,arrows,positioning}") + (println "\\begin{figure}") + (println " \\centering") + ;; + ;; original context + (println " \\begin{minipage}[c]{.5\\textwidth}") + (println " \\centering") + (println " \\begin{cxt}%") + (println " \\cxtName{}%") + (doseq [m (attributes ctx)] + (if (>= 2 (count (str m))) + (println (str " \\att{\\tikzmarknode{ca" (tex-escape m) + "}{" (tex-escape m) "}}%")) + (println (str " \\atr{\\tikzmarknode{ca" (tex-escape m) + "}{" (tex-escape m) "}}%")))) + (let [inz (incidence ctx)] + (doall + (for [[k objs] ctx-obj] + (doseq [g objs] + (print " \\obj{") + (doseq [m (attributes ctx)] + (print (if (inz [g m]) "x" "."))) + (println (str "}{" (tex-escape g) "}")))))) + (println " \\end{cxt}") + (println " \\end{minipage}%") + ;; + ;; scale context + (println " \\begin{minipage}[c]{.5\\textwidth}") + (println " \\centering") + (let [layout (dim-draw-layout (concept-lattice scale)), + vertex-pos (positions layout), + sorted-vertices (sort #(let [[x_1 y_1] (vertex-pos %1), + [x_2 y_2] (vertex-pos %2)] + (or (< y_1 y_2) + (and (= y_1 y_2) + (< x_1 x_2)))) + (nodes layout)), + vertex-idx (into {} + (map-indexed (fn [i v] [v i]) + sorted-vertices))] + (println " \\colorlet{mivertexcolor}{blue}") + (println " \\colorlet{jivertexcolor}{red}") + (println " \\colorlet{vertexcolor}{mivertexcolor!50}") + (println " \\colorlet{bordercolor}{black!80}") + (println " \\colorlet{linecolor}{gray}") + (println (str " \\tikzset{vertexbase/.style={semithick, " + "shape=circle, inner sep=2pt, outer sep=0pt, " + "draw=bordercolor},%")) + (println " vertex/.style={vertexbase, fill=vertexcolor!45},%") + (println " mivertex/.style={vertexbase, fill=mivertexcolor!45},%") + (println " jivertex/.style={vertexbase, fill=jivertexcolor!45},%") + (println (str " divertex/.style={vertexbase, " + "top color=mivertexcolor!45, " + "bottom color=jivertexcolor!45},%")) + (println " conn/.style={-, thick, color=linecolor}%") + (println " }") + (println " \\begin{tikzpicture}") + (println " \\begin{scope} %for scaling and the like") + (println " \\begin{scope} %draw vertices") + (println (str " \\foreach \\nodename/\\nodetype/\\xpos/\\ypos" + " in {%")) + (let [infs (set (inf-irreducibles layout)), + sups (set (sup-irreducibles layout)), + insu (intersection infs sups), + vertex-lines (map (fn [v] + (let [i (vertex-idx v), + [x y] (vertex-pos v)] + (str " " i "/" + (cond + (contains? insu v) "divertex" + (contains? sups v) "jivertex" + (contains? infs v) "mivertex" + :else "vertex") + "/" x "/" y))) + sorted-vertices)] + (doseq [x (interpose ",\n" vertex-lines)] + (print x)) + (println)) + (println (str " } \\node[\\nodetype] (\\nodename) at " + "(\\xpos, \\ypos) {};")) + (println " \\end{scope}") + (println " \\begin{scope} %draw connections") + (doseq [[v w] (connections layout)] + (println (str " \\path (" (vertex-idx v) ") edge[conn] (" + (vertex-idx w) ");"))) + (println " \\end{scope}") + (println " \\begin{scope} %add labels") + (println (str " \\foreach " + "\\nodename/\\labelpos/\\labelopts/\\labelcontent " + "in {%")) + (let [ann (annotation layout), + ann-lines (mapcat (fn [v] + (let [[u l] (map tex-escape (ann v)), + lines (if-not (= "" u) + (list (str " " + (vertex-idx v) + "/above//{" u "}")) + ()), + lines (if-not (= "" l) + (conj + lines + (str " " + (vertex-idx v) + "/below//{" + (apply str + (map + #(str "\\tikzmarknode{n" % + "}{}") + (clojure.string/split + l + #", "))) + l "}")) + lines)] + lines)) + sorted-vertices)] + (doseq [x (interpose ",\n" ann-lines)] + (print x)) + (println)) + (println (str " } \\coordinate[label={[\\labelopts]\\labelpos" + ":{\\labelcontent}}](c) at (\\nodename);")) + (println " \\end{scope}") + (println " \\end{scope}") + (println " \\end{tikzpicture}")) + (println " \\end{minipage}%") + (println "\\end{figure}") + ;; + ;; tikz overlay + (println "\\begin{tikzpicture}[overlay,remember picture]") + (let [ctx-obj-list (flatten (map second (vec ctx-obj))) + anchor (last (butlast (attributes ctx)))] + (doall (if (>= 2 (count (str anchor))) + (do + (println (str " \\node[right = 0.45cm of ca" + (tex-escape anchor) + "] (za) {};")) + (println (str " \\node[below = 0.25cm of za] (co" + (first ctx-obj-list) ") {};"))) + (println (str " \\node[below = 0.25cm of ca" + (tex-escape anchor) + "] (co" (first ctx-obj-list) ") {};")))) + (doall + (map + (fn[a b] + (println (str " \\node[below = 0.16cm of co" + (tex-escape a) + "] (co" (tex-escape b) ") {};"))) + ctx-obj-list + (drop 1 ctx-obj-list))) + (doall + (map + #(println (str " \\node[above = 0.2cm of n" (tex-escape %) + "] (an" % ") {};")) + (objects scale))) + (doall + (for [obj ctx-obj-list] + (println (str " \\draw[->, >=stealth] (co" + (tex-escape obj) ") -- (an" + (tex-escape (mapping obj)) ");"))))) + (println "\\end{tikzpicture}")))) + +(defn write-smeasure + "This method dumps the tikz export for smeasures in a file." + [file smeasure choice] + (spit file (latex smeasure choice))) + +;;; + +nil diff --git a/src/main/clojure/conexp/io/util.clj b/src/main/clojure/conexp/io/util.clj index 1c75a2c55..4d5035c77 100644 --- a/src/main/clojure/conexp/io/util.clj +++ b/src/main/clojure/conexp/io/util.clj @@ -111,7 +111,8 @@ (defmulti ~write ~(str "Writes " name " to file using format.") - {:arglists (list [(symbol "format") (symbol ~name) (symbol "file")] + {:arglists (list [(symbol "format") (symbol ~name) (symbol "file") (symbol "& options")] + [(symbol "format") (symbol ~name) (symbol "file")] [(symbol ~name) (symbol "file")])} (fn [& args#] (cond @@ -161,9 +162,9 @@ (defmacro ~(symbol (str "define-" name "-output-format")) ~(str "Defines output format for " name "s.") - [~'input-format [~'thing ~'file] & ~'body] + [~'input-format [~'thing ~'file & ~'options] & ~'body] `(defmethod ~'~write ~~'input-format - [~'~'_ ~~'thing ~~'file] + [~'~'_ ~~'thing ~~'file ~@~'options] ~@~'body)) nil))) diff --git a/src/main/clojure/conexp/layouts/base.clj b/src/main/clojure/conexp/layouts/base.clj index 91d673874..3756149c9 100644 --- a/src/main/clojure/conexp/layouts/base.clj +++ b/src/main/clojure/conexp/layouts/base.clj @@ -11,22 +11,23 @@ (:use conexp.base conexp.math.algebra conexp.fca.lattices + conexp.fca.posets clojure.pprint)) ;;; -(deftype Layout [lattice ;the underlying lattice +(deftype Layout [poset ;the underlying ordered set positions ;map mapping nodes to $\RR^2$ connections ;connections as set of pairs upper-labels ;map mapping nodes to vectors of labels and coordinates/nil lower-labels ;same - valuations ;valuations of the lattice elements + valuations ;valuations of the poset elements information] ;ref for technicals Object (equals [this other] - (generic-equals [this other] Layout [lattice positions connections upper-labels lower-labels])) + (generic-equals [this other] Layout [poset positions connections upper-labels lower-labels])) (hashCode [this] - (hash-combine-hash Layout lattice positions connections upper-labels lower-labels))) + (hash-combine-hash Layout poset positions connections upper-labels lower-labels))) (defn layout? "Returns true iff thing is a layout." @@ -35,8 +36,8 @@ ;;; helper functions -(defn- lattice-from-layout-data - "Returns lattice represented by layout." +(defn- poset-from-layout-data + "Returns poset or lattice represented by layout." [positions connections] ;; error checking (when-not (map? positions) @@ -74,18 +75,23 @@ (when (exists [x (keys positions)] (cycles? x)) (illegal-argument "Given set of edges is cyclic.")) ;; actual construction - (make-lattice-nc (set (keys positions)) - (memo-fn order [x y] - (or (= x y) - (exists [z (uppers x)] - (order z y))))))) + (let [poset-base-set (set (keys positions)), + poset-order (memo-fn order [x y] + (or (= x y) + (exists [z (uppers x)] + (order z y))))] + (try (make-lattice poset-base-set + poset-order) + (catch IllegalArgumentException _ ;; no lattice order -> create a poset instead of a lattice + (make-poset-nc poset-base-set + poset-order)))))) ;;; plain construction (defn make-layout-nc "Creates layout datatype from given information. The arguments thereby have the following meaning: - - lattice is the underlying lattice of the to be constructed layout + - poset is the underlying poset of the to be constructed layout - positions is a hash-map, mapping node names to coordinate pairs, - connections is a set of pairs of node names denoting edges in the layout, - upper-labels is a map mapping nodes to pairs of upper labels and coordinates or nil, @@ -93,19 +99,19 @@ - valuations is a map mapping nodes to their value for some valuation This functions does only a limited amount of error checking." - ([lattice positions connections upper-labels lower-labels valuations] - (Layout. lattice positions connections upper-labels lower-labels valuations (ref {}))) - ([lattice positions connections upper-labels lower-labels] - (Layout. lattice positions connections upper-labels lower-labels (ref {}) (ref {}))) - ([lattice positions connections] - (make-layout-nc lattice positions connections nil nil)) + ([poset positions connections upper-labels lower-labels valuations] + (Layout. poset positions connections upper-labels lower-labels valuations (ref {}))) + ([poset positions connections upper-labels lower-labels] + (Layout. poset positions connections upper-labels lower-labels (ref {}) (ref {}))) + ([poset positions connections] + (make-layout-nc poset positions connections nil nil)) ([positions connections upper-label lower-label] - (make-layout-nc (lattice-from-layout-data positions connections) + (make-layout-nc (poset-from-layout-data positions connections) positions connections upper-label lower-label)) ([positions connections] - (make-layout-nc (lattice-from-layout-data positions connections) + (make-layout-nc (poset-from-layout-data positions connections) positions connections))) @@ -116,7 +122,7 @@ (assert (= (set (keys new-positions)) (set (keys (.positions layout)))) "Nodes must stay the same when updating positions of an already existing layout.") - (Layout. (.lattice layout) + (Layout. (.poset layout) new-positions (.connections layout) (.upper-labels layout) @@ -127,9 +133,9 @@ (defn update-valuations "Updates valuation map in layout." [^Layout layout, val-fn] - (let [thelattice (.lattice layout) - elements (lattice-base-set thelattice)] - (Layout. (.lattice layout) + (let [theposet (.poset layout) + elements (base-set theposet)] + (Layout. (.poset layout) (.positions layout) (.connections layout) (.upper-labels layout) @@ -137,18 +143,24 @@ (reduce (fn [e x] (assoc e x (val-fn x))) {} elements) (.information layout)))) +(defn update-valuations-error + "Write \"err\" to each valuation in layout." + [^Layout layout] + (let [error-fn (fn [_] "err")] + (update-valuations layout error-fn))) + ;;; argument verification -(defn- verify-lattice-positions-connections - [lattice positions connections] - (when-not (= (base-set lattice) +(defn- verify-poset-positions-connections + [poset positions connections] + (when-not (= (base-set poset) (set (keys positions))) - (illegal-argument "Positioned points must be the elements of the given lattice.")) - (when-not (forall [x (base-set lattice), - y (base-set lattice)] + (illegal-argument "Positioned points must be the elements of the given poset.")) + (when-not (forall [x (base-set poset), + y (base-set poset)] (<=> (contains? connections [x y]) - (directly-neighboured? lattice x y))) - (illegal-argument "The given connections must represent the edges of the given lattice."))) + (directly-neighboured? poset x y))) + (illegal-argument "The given connections must represent the edges of the given poset."))) (defn- check-labels [positions labels direction] @@ -184,52 +196,52 @@ "Creates layout datatype from given positions hash-map, mapping node names to coordinate pairs, and connections, a set of pairs of node names denoting edges in the layout." - ([lattice positions connections upper-label lower-label valuations] + ([poset positions connections upper-label lower-label valuations] (let [connections (set connections), upper-label (if (fn? upper-label) - (map-by-fn upper-label (base-set lattice)) + (map-by-fn upper-label (base-set poset)) upper-label), lower-label (if (fn? lower-label) - (map-by-fn lower-label (base-set lattice)) + (map-by-fn lower-label (base-set poset)) lower-label) valuations (if (fn? valuations) - (map-by-fn valuations (base-set lattice)) + (map-by-fn valuations (base-set poset)) valuations)] - (verify-lattice-positions-connections lattice positions connections) + (verify-poset-positions-connections poset positions connections) (verify-labels positions upper-label lower-label) - (make-layout-nc lattice positions connections upper-label lower-label valuations))) - ([lattice positions connections upper-label lower-label] + (make-layout-nc poset positions connections upper-label lower-label valuations))) + ([poset positions connections upper-label lower-label] (let [connections (set connections), upper-label (if (fn? upper-label) - (map-by-fn upper-label (base-set lattice)) + (map-by-fn upper-label (base-set poset)) upper-label), lower-label (if (fn? lower-label) - (map-by-fn lower-label (base-set lattice)) + (map-by-fn lower-label (base-set poset)) lower-label)] - (verify-lattice-positions-connections lattice positions connections) + (verify-poset-positions-connections poset positions connections) (verify-labels positions upper-label lower-label) - (make-layout-nc lattice positions connections upper-label lower-label))) + (make-layout-nc poset positions connections upper-label lower-label))) ([positions connections upper-label lower-label] - (make-layout (lattice-from-layout-data positions connections) + (make-layout (poset-from-layout-data positions connections) positions connections upper-label lower-label)) - ([lattice positions connections] + ([poset positions connections] (let [connections (set connections)] - (verify-lattice-positions-connections lattice positions connections) - (make-layout-nc lattice positions connections))) + (verify-poset-positions-connections poset positions connections) + (make-layout-nc poset positions connections))) ([positions connections] - (make-layout (lattice-from-layout-data positions connections) + (make-layout (poset-from-layout-data positions connections) positions connections))) ;;; basic functions -(defn lattice - "Returns the lattice underlying the given layout." +(defn poset + "Returns the poset underlying the given layout." [^Layout layout] - (.lattice layout)) + (.poset layout)) (defn positions "Return positions map of layout." @@ -354,6 +366,8 @@ "Returns hash-map mapping the infimum irreducible elements to their upper neighbours." [layout] + (assert (has-lattice-order? (poset layout)) + "The given layout does not contain a lattice.") (loop [inf-uppers (transient {}), all-uppers (seq (upper-neighbours layout))] (if (empty? all-uppers) @@ -367,12 +381,16 @@ (def-layout-fn inf-irreducibles "Returns the set of infimum irreducible elements of layout." [layout] + (assert (has-lattice-order? (poset layout)) + "The given layout does not contain a lattice.") (set-of v [[v uppers] (upper-neighbours layout), :when (singleton? uppers)])) (def-layout-fn sup-irreducibles "Returns the set of supremum irreducible elements of layout." [layout] + (assert (has-lattice-order? (poset layout)) + "The given layout does not contain a lattice.") (set-of v [[v lowers] (lower-neighbours layout), :when (singleton? lowers)])) @@ -385,7 +403,7 @@ (def-layout-fn context "Returns a context whose lattice is represented by this layout." [layout] - (standard-context (lattice layout))) + (poset-context (poset layout))) (def-layout-fn concept-lattice-layout? "Tests whether layout comes from a concept lattice. @@ -394,8 +412,8 @@ the layout repects the subset relation in the first component and the superset relation in the second component of every node." [layout] - (let [lattice (lattice layout)] - (and (forall [x (base-set lattice)] + (let [poset (poset layout)] + (and (forall [x (base-set poset)] (and (vector? x) (= 2 (count x)) (set? (first x)) @@ -434,15 +452,6 @@ (first ((lower-labels layout) x))]) (nodes layout)), ;; - (and (upper-labels layout) - (lower-labels layout) - (valuations layout)) - (map-by-fn (fn [x] - [(first ((upper-labels layout) x)), - (first ((lower-labels layout) x)), - (valuations ((valuations layout) x))]) - (nodes layout)), - ;; (concept-lattice-layout? layout) (let [ann (concept-lattice-annotation layout)] (map-by-fn (fn [node] diff --git a/src/main/clojure/conexp/layouts/common.clj b/src/main/clojure/conexp/layouts/common.clj index 311c42359..b7d330322 100644 --- a/src/main/clojure/conexp/layouts/common.clj +++ b/src/main/clojure/conexp/layouts/common.clj @@ -11,17 +11,20 @@ (:use conexp.base conexp.math.algebra conexp.fca.lattices + conexp.fca.posets conexp.layouts.util conexp.layouts.layered - conexp.layouts.base)) + conexp.layouts.base) + (:import [conexp.fca.posets Poset] + [conexp.fca.lattices Lattice])) ;;; inf-irreducible additive layout (defn placement-by-initials "Computes placement for all elements by positions of some initial nodes. Top element will be at top." - [lattice top placement] - (let [ord (order lattice), + [poset top placement] + (let [ord (order poset), pos (fn pos [v] (get placement v (reduce (fn [p w] @@ -32,18 +35,22 @@ p)) top (keys placement))))] - (map-by-fn pos (base-set lattice)))) + (map-by-fn pos (base-set poset)))) -(defn to-inf-additive-layout - "Returns an infimum additive layout from given layout, taking the - positions of the infimum irreducible elements as initial positions for - the resulting additive layout." +(defmulti to-inf-additive-layout + "Returns an infimum additive layout from given layout." + (fn [layout] (type (poset layout)))) + +(defmethod to-inf-additive-layout Lattice + ;; Returns an infimum additive layout from given layout, taking the + ;; positions of the infimum irreducible elements as initial positions for + ;; the resulting additive layout. ;; this is stupid, do it better! [layout] - (let [lattice (lattice layout), + (let [lattice (poset layout), old-positions (positions layout), top-pos (old-positions (lattice-one lattice)), - inf-irr (set (inf-irreducibles layout)), + inf-irr (set (inf-irreducibles layout)), elements (filter inf-irr (top-down-elements-in-layout layout))] (loop [positions (select-keys old-positions inf-irr), nodes elements] @@ -64,23 +71,28 @@ [x-old (min y-old y-new)]) (rest nodes))))))) +(defmethod to-inf-additive-layout Poset + ;; Returns an infimum additive layout from given poset layout. + [layout] + (layout-fn-on-poset to-inf-additive-layout layout)) + ;;; (defn layout-by-placement - "Computes additive layout of lattice by given positions of the keys + "Computes additive layout of ordered set by given positions of the keys of placement. The values of placement should be the positions of the corresponding keys. Top element will be at top." - [lattice top placement] - (make-layout-nc lattice - (placement-by-initials lattice top placement) - (edges lattice))) + [poset top placement] + (make-layout-nc poset + (placement-by-initials poset top placement) + (edges poset))) ;;; Valued layout stuff (defn to-valued-layout [layout val-fn] - (let [lattice (lattice layout) - elements (lattice-base-set lattice)] + (let [poset (poset layout) + elements (base-set poset)] (update-valuations layout val-fn))) ;;; diff --git a/src/main/clojure/conexp/layouts/dim_draw.clj b/src/main/clojure/conexp/layouts/dim_draw.clj index a7e64aa70..cc83bd360 100644 --- a/src/main/clojure/conexp/layouts/dim_draw.clj +++ b/src/main/clojure/conexp/layouts/dim_draw.clj @@ -1,8 +1,8 @@ (ns conexp.layouts.dim-draw (:require [loom.graph :as lg] [conexp.math.algebra :as alg] - [conexp.fca.lattices :as lat] [conexp.fca.graph :refer :all] + [conexp.fca.posets :refer :all] [conexp.util.graph :refer :all] [conexp.layouts.base :as lay] [conexp.base :exclude [transitive-closure] :refer :all] @@ -247,8 +247,8 @@ (map reverse (cond (= (first args) "greedy") - (difference (set (lg/nodes graph) - (fill-graph graph #{}))) + (difference (set (lg/nodes graph)) + (fill-graph graph #{})) (= (first args) "genetic") (genetic-bipartite-subgraph graph (nth args 1) @@ -284,12 +284,12 @@ coords))) (defn dim-draw-layout - "Returns a layout for a given lattice. + "Returns a layout for a given ordered set. The positions in the layout are computed using DimDraw, see Dürrschnabel, Hanika, Stumme (2019) https://arxiv.org/abs/1903.00686" - [lattice & args] - (let [g (lattice->graph lattice) + [poset & args] + (let [g (poset->graph poset) coordinates (map #(vector (first %) (let [x1x2 (second %) x (- (first x1x2) (second x1x2)) @@ -297,11 +297,11 @@ [x y])) (compute-coordinates g args)) positions (reduce conj {} coordinates)] - (lay/make-layout-nc lattice - positions - (mapcat (fn [n] (map #(vector n %) - (lat/lattice-upper-neighbours lattice n))) - (alg/base-set lattice))))) + (lay/make-layout-nc poset + positions + (mapcat (fn [n] (map #(vector n %) + (poset-upper-neighbours poset n))) + (alg/base-set poset))))) (defn- replicate-str [s i] diff --git a/src/main/clojure/conexp/layouts/force.clj b/src/main/clojure/conexp/layouts/force.clj index c254f1dd5..cd1059bf2 100644 --- a/src/main/clojure/conexp/layouts/force.clj +++ b/src/main/clojure/conexp/layouts/force.clj @@ -13,8 +13,11 @@ conexp.fca.lattices conexp.layouts.base conexp.layouts.common + conexp.layouts.util conexp.math.util - conexp.math.optimize)) + conexp.math.optimize) + (:import [conexp.fca.posets Poset] + [conexp.fca.lattices Lattice])) ;; Helpers @@ -132,6 +135,8 @@ (defn layout-energy "Returns the overall energy of the given layout." [layout] + (assert (has-lattice-order? (poset layout)) + "The given layout does not contain a lattice.") (+ (* *repulsive-amount* (repulsive-energy layout)) (* *attractive-amount* (attractive-energy layout)) (* *gravitative-amount* (gravitative-energy layout)))) @@ -142,7 +147,7 @@ "Returns pair of energy function and function returning the n-th partial derivative when given index n." [layout seq-of-inf-irrs] - (let [lattice (lattice layout), + (let [lattice (poset layout), top-pos ((positions layout) (lattice-one lattice)), energy (fn [point-coordinates] (let [points (partition 2 point-coordinates), @@ -157,17 +162,21 @@ ;;; Force Layout -(defn force-layout +(defmulti force-layout "Improves given layout with force layout." + (fn [layout & iterations] (type (poset layout)))) + +(defmethod force-layout Lattice + ;; Improves given lattice layout with force layout. ([layout] (force-layout layout nil)) ([layout iterations] - (let [;; compute lattice from layout and ensure proper starting layout - lattice (lattice layout), + (let [;; compute lattice from layout and ensure proper starting layout + lattice (poset layout), layout (to-inf-additive-layout layout), ;; get positions of inf-irreducibles from layout as starting point - inf-irrs (inf-irreducibles layout), + inf-irrs (inf-irreducibles layout), node-positions (positions layout), inf-irr-points (map node-positions inf-irrs), top-pos (node-positions (lattice-one lattice)), @@ -180,11 +189,19 @@ ;; make hash point-hash (apply hash-map (interleave inf-irrs - (partition 2 new-points)))] + (map #(into []%) + (partition 2 new-points))))] ;; final layout (layout-by-placement lattice top-pos point-hash)))) +(defmethod force-layout Poset + ;; Improves given poset layout with force layout. + ([layout] + (force-layout layout nil)) + ([layout iterations] + (layout-fn-on-poset force-layout layout iterations))) + ;;; nil diff --git a/src/main/clojure/conexp/layouts/freese.clj b/src/main/clojure/conexp/layouts/freese.clj index f64f4a4c2..4f6ef5775 100644 --- a/src/main/clojure/conexp/layouts/freese.clj +++ b/src/main/clojure/conexp/layouts/freese.clj @@ -9,7 +9,7 @@ (ns conexp.layouts.freese (:use conexp.base [conexp.math.algebra :only (base-set)] - [conexp.fca.lattices :only (lattice-upper-neighbours)] + [conexp.fca.posets :only (poset-upper-neighbours)] [conexp.layouts.util :only (edges)] [conexp.layouts.base :only (make-layout-nc)]) (:import [org.latdraw.diagram Diagram Vertex])) @@ -17,20 +17,20 @@ ;;; (defn interactive-freese-layout - "Returns the interactive Freese Layout of the given lattice. This is + "Returns the interactive Freese Layout of the given ordered set. This is a function of one argument (the projection angle) returning the corresponding layout." - [lattice] - (let [nodes (seq (base-set lattice)), - ucs (map #(or (seq (lattice-upper-neighbours lattice %)) []) + [poset] + (let [nodes (seq (base-set poset)), + ucs (map #(or (seq (poset-upper-neighbours poset %)) []) nodes), ^Diagram diag (Diagram. "" nodes ucs), - edges (edges lattice)] + edges (edges poset)] (.improve diag) (fn [angle] (.project2d diag ^double angle) - (make-layout-nc lattice + (make-layout-nc poset (reduce! (fn [map, ^Vertex vertex] (assoc! map (.. vertex getUnderlyingElem getUnderlyingObject) @@ -40,9 +40,9 @@ edges)))) (defn freese-layout - "Returns the Freese Layout of the given lattice." - [lattice] - ((interactive-freese-layout lattice) 0.0)) + "Returns the Freese Layout of the given ordered set." + [poset] + ((interactive-freese-layout poset) 0.0)) ;;; diff --git a/src/main/clojure/conexp/layouts/layered.clj b/src/main/clojure/conexp/layouts/layered.clj index eff2c123c..b25383767 100644 --- a/src/main/clojure/conexp/layouts/layered.clj +++ b/src/main/clojure/conexp/layouts/layered.clj @@ -7,11 +7,14 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.layouts.layered - "Layered lattice layouts." + "Layered poset layouts." (:use conexp.base + conexp.math.algebra conexp.layouts.util conexp.layouts.base - conexp.fca.lattices)) + conexp.math.algebra + conexp.fca.lattices + conexp.fca.protoconcepts)) ;;; Simple Layered Layout @@ -26,26 +29,26 @@ (iterate inc start))))) (defn simple-layered-layout - "Simple layered layout for lattice visualization." - [lattice] - (make-layout-nc lattice + "Simple layered layout for poset visualization." + [poset] + (make-layout-nc poset (apply hash-map (mapcat layer-coordinates (iterate inc 0) - (layers lattice))) - (edges lattice))) + (layers poset))) + (edges poset))) (defn as-chain - "Returns the layout of lattice as a simple chain." - [lattice] - (make-layout-nc lattice + "Returns the layout of poset as a simple chain." + [poset] + (make-layout-nc poset (into {} (mapcat (fn [i layer] (map (fn [x] [x [0, i]]) layer)) (iterate inc 0) - (layers lattice))) - (edges lattice))) + (layers poset))) + (edges poset))) ;;; diff --git a/src/main/clojure/conexp/layouts/util.clj b/src/main/clojure/conexp/layouts/util.clj index 607ae1624..9e042138e 100644 --- a/src/main/clojure/conexp/layouts/util.clj +++ b/src/main/clojure/conexp/layouts/util.clj @@ -11,8 +11,13 @@ (:require [conexp.base :refer :all] [conexp.math.algebra :refer :all] [conexp.fca.lattices :refer :all] + [conexp.fca.protoconcepts :refer :all] + [conexp.fca.posets :refer :all] [conexp.layouts.base :refer :all] - [conexp.util.graph :as graph])) + [conexp.util.graph :as graph]) + (:import [conexp.fca.posets Poset] + [conexp.fca.lattices Lattice] + [conexp.fca.protoconcepts Protoconcepts])) ;;; @@ -59,7 +64,7 @@ "Scales given layout to rectangle [x1 y1], [x2 y2]." [[x1 y1] [x2 y2] layout] (let [points (seq (positions layout))] - (make-layout-nc (lattice layout) + (make-layout-nc (poset layout) (zipmap (map first points) (scale-points-to-rectangle [x1 y1] [x2 y2] (map second points))) @@ -112,12 +117,32 @@ cover))) H)))) -(defn edges - "Returns a sequence of pairs of vertices of lattice which are - directly neighbored in lattice." +(defmulti edges + "Returns a sequence of pairs of vertices of poset (or lattice) which + are directly neighboured in poset." + (fn [poset] (type poset))) + +(defn- poset-edges + [poset] + (into #{} + (filter #(directly-neighboured? poset (first %) (second %)) + (for [x (base-set poset) + y (base-set poset) + :when ((order poset) x y)] + [x y])))) + +(defmethod edges Poset + [poset] + (poset-edges poset)) + +(defmethod edges Lattice [lattice] (edges-by-border lattice)) +(defmethod edges Protoconcepts + [protoconcepts] + (poset-edges protoconcepts)) + (defn top-down-elements-in-layout "Returns the elements in layout ordered top down." [layout] @@ -229,6 +254,60 @@ (into (below b) (below a)))] (recur above below (rest edges)))))) +;; Layouts for Posets, using Dedekind MacNeille completion + +(defn- order-embedding + "Return an order embedding using the Dedekind MacNeille completion." + [poset] + (into {} + (map #(vector [(order-ideal poset #{%}) + (order-filter poset #{%})] + %) + (base-set poset)))) + +(defn- poset-layout->lattice-layout + "The poset of the given layout is transformed into a lattice, using the Dedekind MacNeille completion." + [layout] + (let [poset (poset layout)] + (if (= Lattice (type poset)) + layout + (let [lattice (concept-lattice (poset-context poset)) ;; Dedekind MacNeille completion + embedding (order-embedding poset) + new-base-set (map #(get embedding % %) (base-set lattice)) + max-y-position (apply max (map second (vals (positions layout))))] + (make-layout (merge + (into {} (map #(vector % [0 (inc max-y-position)]) + new-base-set)) + (positions layout)) + (map #(vector + (get embedding (first %) (first %)) + (get embedding (second %) (second %))) + (edges lattice))))))) + +(defn- lattice-layout->poset-layout + "The lattice of the given layout is transformed into a poset. Only the given nodes remain in the layout." + [layout nodes] + (let [new-positions (into {} (filter #(contains? nodes (key %)) + (positions layout))) + new-connections (into [] (filter #(and (contains? nodes (first %)) + (contains? nodes (second %))) + (connections layout)))] + (make-layout new-positions new-connections))) + +(defn layout-fn-on-poset + "The poset of the given layout is transformed into a lattice, using + the Dedekind MacNeille completion. After computing the new layout + with the given layout-fn, the layout is re-transformed to the previous + layout by deleting all nodes added in the Dedekind MacNeille completion." + ([layout-fn layout] + (let [lattice-layout (poset-layout->lattice-layout layout), + new-layout (layout-fn lattice-layout)] + (lattice-layout->poset-layout new-layout (nodes layout)))) + ([layout-fn layout args] + (let [lattice-layout (poset-layout->lattice-layout layout), + new-layout (layout-fn lattice-layout args)] + (lattice-layout->poset-layout new-layout (nodes layout))))) + ;;; nil diff --git a/src/main/clojure/conexp/main.clj b/src/main/clojure/conexp/main.clj index 4783bbca3..f5ac3be2e 100644 --- a/src/main/clojure/conexp/main.clj +++ b/src/main/clojure/conexp/main.clj @@ -25,11 +25,14 @@ conexp.fca.dependencies conexp.fca.lattices conexp.fca.more + conexp.fca.posets conexp.io.latex conexp.io.contexts + conexp.io.implications conexp.io.lattices conexp.io.layouts conexp.io.many-valued-contexts + conexp.io.fcas conexp.layouts]) (apply use conexp-clj-namespaces) diff --git a/src/main/clojure/conexp/math/algebra.clj b/src/main/clojure/conexp/math/algebra.clj index 71744bf17..202a298ff 100644 --- a/src/main/clojure/conexp/math/algebra.clj +++ b/src/main/clojure/conexp/math/algebra.clj @@ -6,7 +6,8 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.math.algebra - (:use conexp.base)) + (:use conexp.base) + (:require [conexp.fca.contexts :refer :all])) ;;; Datastructure @@ -16,83 +17,6 @@ order relation. If called with one argument it is assumed that this argument is a pair of elements.")) -(deftype Poset [base-set order-function] - Object - (equals [this other] - (and (= (class this) (class other)) - (= (.base-set this) (.base-set ^Poset other)) - (let [order-this (order this), - order-other (order other)] - (or (= order-this order-other) - (forall [x (.base-set this) - y (.base-set this)] - (<=> (order-this x y) - (order-other x y))))))) - (hashCode [this] - (hash-combine-hash Poset base-set)) - ;;; - Order - (base-set [this] base-set) - (order [this] - (fn order-fn - ([pair] (order-function (first pair) (second pair))) - ([x y] (order-function x y))))) - -(defmethod print-method Poset [^Poset poset, ^java.io.Writer out] - (.write out - ^String (str "Poset on " (count (base-set poset)) " elements."))) - -;;; Constructors - -(defn make-poset-nc - "" - [base-set order-relation] - (Poset. (to-set base-set) order-relation)) - -(defn has-partial-order? - "Given a poset checks if its order is indeed a partial order." - [poset] - (let [<= (order poset)] - (and (forall [x (base-set poset)] - (<= x x)) - (forall [x (base-set poset), - y (base-set poset)] - (=> (and (<= x y) - (<= y x)) - (= x y))) - (forall [x (base-set poset), - y (base-set poset), - z (base-set poset)] - (=> (and (<= x y) - (<= y z)) - (<= x z)))))) - -(defn make-poset - "Standard constructor for making a poset from a base set and an order - relation. - Note: This function will test the resulting poset for being one, - which may take some time. If you don't want this, use - make-poset-nc." - [base-set order-relation] - (let [poset (make-poset-nc base-set order-relation)] - (when-not (has-partial-order? poset) - (illegal-argument "Given arguments do not describe a poset.")) - poset)) - -;; Converter - -(defn poset-to-matrix - "Returns the relational matrix of the base set as one continous vector. - The function either takes only a poset and or the poset and a presorted - base set." - ([poset] - (poset-to-matrix poset (vec (base-set poset)))) - ([poset base] - (let [ord (order poset)] - (vec - (for [x base y base] - (if (ord x y) 1 0)))))) - ;;; (defn closure-induced-preorder diff --git a/src/main/java/org/latdraw/orderedset/ChainDecomposition.java b/src/main/java/org/latdraw/orderedset/ChainDecomposition.java index b30f913e9..dac3633f4 100644 --- a/src/main/java/org/latdraw/orderedset/ChainDecomposition.java +++ b/src/main/java/org/latdraw/orderedset/ChainDecomposition.java @@ -18,7 +18,7 @@ public class ChainDecomposition { */ public ChainDecomposition(OrderedSet poset) { - Integer h = new Integer(0); + Integer h = Integer.valueOf(0); HashMap visited = new HashMap(poset.card()); HashMap chain_ht = new HashMap(poset.card()); List elems = poset.univ(); @@ -39,7 +39,7 @@ public ChainDecomposition(OrderedSet poset) { x = y; } } - h = new Integer(1 + h.intValue()); + h = Integer.valueOf(1 + h.intValue()); } } numChains = h.intValue(); diff --git a/src/main/java/org/latdraw/orderedset/OrderedSet.java b/src/main/java/org/latdraw/orderedset/OrderedSet.java index a9ed02b07..ebcd9050f 100644 --- a/src/main/java/org/latdraw/orderedset/OrderedSet.java +++ b/src/main/java/org/latdraw/orderedset/OrderedSet.java @@ -318,7 +318,7 @@ void setElemOrder() { for (Iterator it = elems.iterator(); it.hasNext(); ) { // was label() elemOrder.put(((POElem)it.next()).getUnderlyingObject(), - new Integer(k++)); + Integer.valueOf(k++)); } } diff --git a/src/main/java/org/latdraw/partition/BasicPartition.java b/src/main/java/org/latdraw/partition/BasicPartition.java index 977393211..65f4615dd 100644 --- a/src/main/java/org/latdraw/partition/BasicPartition.java +++ b/src/main/java/org/latdraw/partition/BasicPartition.java @@ -134,7 +134,7 @@ private static BasicPartition kernel( int[] part ) { Integer v; int j; for( i=0; i readPartitions(File file, final int delta) for (int j = 0; j < block.length; j++) { int u = Integer.parseInt(block[j]); max = Math.max(max, u); - blk.add(new Integer(u)); + blk.add(Integer.valueOf(u)); } part.add(blk); } diff --git a/src/main/java/org/latdraw/util/SimpleList.java b/src/main/java/org/latdraw/util/SimpleList.java index f21fc4107..5072d70c4 100644 --- a/src/main/java/org/latdraw/util/SimpleList.java +++ b/src/main/java/org/latdraw/util/SimpleList.java @@ -531,10 +531,10 @@ public static void main(String[] args) { SimpleList foo = EMPTY_LIST; SimpleList bar = EMPTY_LIST; for(int i = 0; i < n; i++) { - bar = bar.cons(new Integer(i)); + bar = bar.cons(Integer.valueOf(i)); foo = foo.cons(bar); - //foo = new SimpleList(new Integer(i), foo); - //foo = foo.cons(new Integer(i)); + //foo = new SimpleList(Integer.valueOf(i), foo); + //foo = foo.cons(Integer.valueOf(i)); } System.out.println("before: equals? " @@ -551,7 +551,7 @@ public static void main(String[] args) { SimpleList goo = EMPTY_LIST; SimpleList tails = EMPTY_LIST; for(int i = 0; i < n; i++) { - goo = goo.cons(new Integer(i)); + goo = goo.cons(Integer.valueOf(i)); tails = tails.cons(goo); } diff --git a/src/main/resources/schemas/context_schema_v1.0.json b/src/main/resources/schemas/context_schema_v1.0.json new file mode 100644 index 000000000..6c3348e2e --- /dev/null +++ b/src/main/resources/schemas/context_schema_v1.0.json @@ -0,0 +1,54 @@ +{ + "$id": "context_schema_v1.0", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "JSON schema for a formal context", + "type": "object", + "description": "JSON Schema for the formal context. A formal context contains a set of objects, each with corresponding attributes. The minimum number of attributes assigned to an object is 0.", + "$defs": { + "context_item": { + "type": "object", + "required": [ + "object", + "attributes" + ], + "properties": { + "object": { + "type": "object", + "$ref": "fca_schema_v1.0.json#/$defs/object" + }, + "attributes": { + "type": "array", + "description": "List of attributes", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/attribute" + } + } + }, + "additionalProperties": false + } + }, + "required": [ + "attributes", + "adjacency-list" + ], + "properties": { + "attributes": { + "type": "array", + "description": "List of all attributes", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/attribute" + } + }, + "adjacency-list": { + "type": "array", + "items": { + "$ref": "#/$defs/context_item" + } + }, + "additional_information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/src/main/resources/schemas/context_schema_v1.1.json b/src/main/resources/schemas/context_schema_v1.1.json new file mode 100644 index 000000000..8894662fa --- /dev/null +++ b/src/main/resources/schemas/context_schema_v1.1.json @@ -0,0 +1,48 @@ +{ + "$id": "context_schema_v1.1", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "JSON schema for a formal context", + "type": "object", + "description": "JSON Schema for the formal context. A formal context contains a list of objects, a list of attributes and the incidence between them.", + "$defs": { + "incidence_item": { + "type": "array", + "prefixItems": [ + {"type": "fca_schema_v1.0.json#/$defs/object"}, + {"type": "fca_schema_v1.0.json#/$defs/attribute"} + ] + } + }, + "required": [ + "objects", + "attributes", + "incidence" + ], + "properties": { + "objects": { + "type": "array", + "description": "List of all objects", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/object" + } + }, + "attributes": { + "type": "array", + "description": "List of all attributes", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/attribute" + } + }, + "incidence": { + "type": "array", + "items": { + "$ref": "#/$defs/incidence_item" + } + }, + "additional_information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/src/main/resources/schemas/fca_schema_v1.0.json b/src/main/resources/schemas/fca_schema_v1.0.json new file mode 100644 index 000000000..d4c3c85b5 --- /dev/null +++ b/src/main/resources/schemas/fca_schema_v1.0.json @@ -0,0 +1,65 @@ +{ + "$id": "fca_schema_v1.0", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "JSON schema for an FCA process", + "type": "object", + "description": "JSON schema for FCA (Formal Concept Analysis). The schema contains context, concepts and sets of implications.\n\nIn Python, an instance can be validated with the function 'validate', provided by the 'jsonschema' package.", + "$defs": { + "object": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "description": "JSON schema for a single object." + }, + "attribute": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "description": "JSON schema for a single attribute." + } + }, + "properties": { + "source": { + "type": "string", + "description": "In 'source', the source of the context / data can be added." + }, + "context": { + "$ref": "context_schema_v1.1.json" + }, + "lattice": { + "$ref": "lattice_schema_v1.1.json" + }, + "implication_sets": { + "type": "array", + "description": "Several sets of implications can be added.", + "items": { + "$ref": "implications_schema_v1.0.json" + } + }, + "layouts":{ + "type": "array", + "description": "Several layouts can be added.", + "items": { + "$ref": "layout_schema_v1.0.json" + } + }, + "additional_information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "context" + ] +} diff --git a/src/main/resources/schemas/implications_schema_v1.0.json b/src/main/resources/schemas/implications_schema_v1.0.json new file mode 100644 index 000000000..4e99db2f4 --- /dev/null +++ b/src/main/resources/schemas/implications_schema_v1.0.json @@ -0,0 +1,105 @@ +{ + "$id": "implications_schema_v1.0.json", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "JSON schema for a set of implications", + "type": "object", + "description": "JSON schema for a set of implications in FCA (Formal Concept Analysis). The schema contains information about the set of implications (whether it is a base, the type of the base, additional information).", + "$defs": { + "implication": { + "type": "object", + "description": "JSON schema for a single implication. Premise and conclusion are required, other properties (id, support, confidence) are optional.", + "required": [ + "premise", + "conclusion" + ], + "properties": { + "id": { + "description": "Identifier of the implication. The id can be of any type, e.g. integer or string.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "premise": { + "description": "The premise of the implication", + "type": "array", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/attribute" + } + }, + "conclusion": { + "description": "The conclusion of the implication", + "type": "array", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/attribute" + } + }, + "support": { + "$comment": "If the support is given, the corresponding context set should be referenced.", + "description": "The support of the implication in a specific data set", + "type": "number" + }, + "confidence": { + "$comment": "If the confidence is given, the corresponding context should be referenced.", + "description": "The confidence of the implication in a specific data set", + "type": "number" + } + }, + "additionalProperties": false + } + }, + "required": [ + "implications" + ], + "properties": { + "is_base": { + "description": "Indicates, whether the set of implications is a base or not.", + "type": "boolean" + }, + "base_type": { + "description": "Specifies the type of the base, if 'is_base' is true. If the set of implications is not a base, the base_type has to be empty.", + "$comment": "Further base_type enums can be added.", + "type": "string", + "enum": [ + "canonical", + "ganter" + ] + }, + "implications": { + "type": "array", + "items": { + "$ref": "#/$defs/implication" + } + }, + "additional information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false, + "if": { + "$comment": "If 'is_base' is false, then a property named 'base_type' may not exist.", + "properties": { + "is_base": { + "const": false + } + } + }, + "then": { + "properties": { + "base_type": { + "not": {} + } + } + }, + "dependentRequired": { + "$comment": "If 'base_type' exists, then 'is_base' has to exist as well.", + "base_type": [ + "is_base" + ] + } +} diff --git a/src/main/resources/schemas/lattice_schema_v1.0.json b/src/main/resources/schemas/lattice_schema_v1.0.json new file mode 100644 index 000000000..2dd986a2f --- /dev/null +++ b/src/main/resources/schemas/lattice_schema_v1.0.json @@ -0,0 +1,71 @@ +{ + "$id": "lattice_schema_v1.0", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "JSON schema for a concept lattice", + "type": "object", + "description": "JSON schema for a concept lattice. Each concept consists of the extent (an array of objects) and the intent (an array of attributes). The lattice structure can be saved as well.", + "$defs": { + "formal_concept": { + "type": "object", + "required": [ + "extent", + "intent" + ], + "properties": { + "extent": { + "type": "array", + "description": "Array of objects", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/object" + } + }, + "intent": { + "type": "array", + "description": "Array of attributes", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/attribute" + } + }, + "valuation": { + "type": "number" + } + } + }, + "link": { + "type": "object", + "required": [ + "start_concept", + "end_concept" + ], + "properties": { + "start_concept": { + "$ref": "#/$defs/formal_concept" + }, + "end_concept": { + "$ref": "#/$defs/formal_concept" + } + } + } + }, + "required": [ + "formal_concepts" + ], + "properties": { + "formal_concepts": { + "type": "array", + "items": { + "$ref": "#/$defs/formal_concept" + } + }, + "lattice_structure": { + "type": "array", + "items": { + "$ref": "#/$defs/link" + } + }, + "additional_information": { + "type": "string" + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/src/main/resources/schemas/lattice_schema_v1.1.json b/src/main/resources/schemas/lattice_schema_v1.1.json new file mode 100644 index 000000000..add362247 --- /dev/null +++ b/src/main/resources/schemas/lattice_schema_v1.1.json @@ -0,0 +1,71 @@ +{ + "$id": "lattice_schema_v1.1", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "JSON schema for a concept lattice", + "type": "object", + "description": "JSON schema for a concept lattice. Each concept (node) consists of the extent (an array of objects) and the intent (an array of attributes). The lattice structure can be saved as well (edges).", + "$defs": { + "extent": { + "type": "array", + "description": "Array of objects", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/object" + } + }, + "intent": { + "type": "array", + "description": "Array of attributes", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/attribute" + } + }, + "node": { + "anyOf": [ + { + "type": ["integer","string"] + }, + { + "type": "array", + "prefixItems": [ + {"type": "#/$defs/extent"}, + {"type": "#/$defs/intent"} + ] + } + ], + "items": { + "valuation": { + "type": "number" + } + } + }, + "edge": { + "type": "array", + "prefixItems": [ + {"type": "#/$defs/node"}, + {"type": "#/$defs/node"} + ] + } + }, + "required": [ + "nodes", + "edges" + ], + "properties": { + "nodes": { + "type": "array", + "items": { + "$ref": "#/$defs/node" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/$defs/edge" + } + }, + "additional_information": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/src/main/resources/schemas/layout_schema_v1.0.json b/src/main/resources/schemas/layout_schema_v1.0.json new file mode 100644 index 000000000..fec066a0a --- /dev/null +++ b/src/main/resources/schemas/layout_schema_v1.0.json @@ -0,0 +1,74 @@ +{ + "$id": "layout_schema_v1.0", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "JSON schema for the layout of an ordered set", + "type": "object", + "description": "JSON Schema for a layout. A layout contains information about the nodes of a line diagram, their positions, connections, valuations and annotations.", + "$defs": { + "id": {}, + "node": { + "type": "object" + }, + "position": { + "type": "object", + "patternProperties": { + "": { + "type": "array" + } + } + }, + "edge": { + "type": "object", + "patternProperties": { + "": { + "type": "array" + } + } + } + }, + "required": [ + "nodes", + "positions", + "edges" + ], + "properties": { + "nodes": { + "type": "array", + "description": "Array of nodes (node-id and node description)", + "items": { + "$ref": "#/$defs/node" + } + }, + "positions": { + "type": "array", + "description": "Array of positions", + "items": { + "$ref": "#/$defs/position" + } + }, + "edges": { + "type": "array", + "description": "Array of edges", + "items": { + "$ref": "#/$defs/edge" + } + }, + "valuations": { + "type": "array", + "items": { + "type": "object" + } + }, + "shorthand-annotation": { + "type": "array", + "items": { + "type": "object" + } + }, + "additional_information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/src/main/resources/schemas/mv-context_schema_v1.0.json b/src/main/resources/schemas/mv-context_schema_v1.0.json new file mode 100644 index 000000000..fc9705b75 --- /dev/null +++ b/src/main/resources/schemas/mv-context_schema_v1.0.json @@ -0,0 +1,45 @@ +{ + "$id": "mv-context_schema_v1.0", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "JSON schema for a many valued context", + "type": "object", + "description": "JSON schema for a many valued context.", + "required": [ + "objects", + "attributes", + "incidence" + ], + "properties": { + "objects": { + "type": "array", + "description": "List of all objects", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/object" + } + }, + "attributes": { + "type": "array", + "description": "List of all attributes", + "items": { + "$ref": "fca_schema_v1.0.json#/$defs/attribute" + } + }, + "incidence": { + "type": "object", + "$comment": "The property names are strings that each contain an object-attribute tuple.", + "propertyNames": { + "pattern": "^\\[([^ ])+ ([^ ])+\\]$" + }, + "patternProperties": { + "^\\[([^ ])+ ([^ ])+\\]$": { + "type": ["string", "number"] + } + } + }, + "additional_information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/src/test/clojure/conexp/api/handler_test.clj b/src/test/clojure/conexp/api/handler_test.clj index be14e74b8..16824571b 100644 --- a/src/test/clojure/conexp/api/handler_test.clj +++ b/src/test/clojure/conexp/api/handler_test.clj @@ -255,7 +255,7 @@ :conclusion {:type "list" :data ["2" "1"]}}) impl (:result (:function result))] - (is (= (make-implication (first impl)(second impl)) + (is (= (make-implication (:premise impl)(:conclusion impl)) (make-implication ["a" "b"]["2" "1"]))) (is (= (:type (:function result)) "implication")))) @@ -264,7 +264,7 @@ :name "premise" :args ["impl1"]} :impl1 {:type "implication" - :data [["a"] ["b"]]}}) + :data {:premise ["a"] :conclusion ["b"]}}}) premise (:result (:function result))] (is (= premise ["a"])) @@ -279,7 +279,7 @@ :ctx1 {:type "context" :data (write-data context)}}) impls (:result (:function result))] - (is (= (map #(make-implication (first %) (last %)) impls) + (is (= (map #(make-implication (:premise %) (:conclusion %)) impls) (canonical-base context))) (is (= (:type (:function result)) "implication_set")))) @@ -305,24 +305,45 @@ edge #{[1 2][1 3][2 4][3 4]} result (mock-request {:function {:type "function" :name "make-layout" - :args ["lattice" "positions" "edges"]} - :lattice {:type "lattice" + :args ["poset" "positions" "edges"]} + :poset {:type "lattice" :data (write-data lat)} :positions {:type "map" :data pos} :edges {:type "list" :data edge}}) layout (:result (:function result))] - (is (= (make-layout-nc - ;; Lattice object - (make-lattice-nc (:nodes (:lattice layout)) - (:edges (:lattice layout))) - ;; remove colons from keys - (read-data {:type "map" :data (:positions layout)}) - ;; cast vector to set, as JSON only supports lists - (into #{} (:connections layout))) - (make-layout lat pos edge))) - (is (= (:type (:function result)) "layout")))) + (is (= (:type (:function result)) "layout")) + (let [nodes (json->nodes layout) + positions (json->positions (apply conj (:positions layout)) nodes) + connections (json->connections (apply conj (:edges layout)) nodes)] + (is (= positions pos)) + (is (= (set connections) edge))))) + +(deftest test-layout-valuations-write + (let [lat (make-lattice #{1 2} + #{[1 2] [1 1] [2 2]}) + pos (hash-map 1 [0 0] 2 [0 1]) + connections #{[1 2]} + result (mock-request {:layout {:type "function" + :name "make-layout" + :args ["lattice" "positions" "connections"]} + :lattice {:type "lattice" + :data (write-data lat)} + :positions {:type "map" + :data pos} + :connections {:type "list" + :data connections} + :update-fn {:type "method" + :data "identity"} + :function {:type "function" + :name "update-valuations" + :args ["layout" "update-fn"]}}) + layout (:result (:function result))] + (is (= (:type (:function result)) "layout")) + (is (= (:valuations layout) + ;; node 1 gets key :0 and node 2 gets key :1 + [{:0 1} {:1 2}])))) (deftest test-layout-read (let [lat (make-lattice #{1 2 3 4} @@ -339,46 +360,67 @@ :new-pos {:type "map" :data new-pos}}) layout (:result (:function result))] - (is (= (make-layout-nc - ;; Lattice object - (make-lattice-nc (:nodes (:lattice layout)) - (:edges (:lattice layout))) - ;; remove colons from keys - (read-data {:type "map" :data (:positions layout)}) - ;; cast vector to set, as JSON only supports lists - (into #{} (:connections layout))) - (make-layout lat new-pos edge))) - (is (= (:type (:function result)) "layout")))) - -(deftest test-layout-read-write-label - (let [lat (make-lattice #{1 2 3 4} - #{[1 2][1 3][2 4][3 4][1 4][1 1][2 2][3 3][4 4]}) - pos (hash-map 1 [0 0] 2 [-1 1] 3 [1 1] 4 [0 2]) - new-pos (hash-map 1 [0 0] 2 [-2 1] 3 [1 1] 4 [0 2]) - edge #{[1 2][1 3][2 4][3 4]} - up (hash-map 1 ["a" nil] 2 ["b" nil] 3 ["c" nil] 4 ["d" nil]) - lo (hash-map 1 ["e" nil] 2 ["f" nil] 3 ["g" nil] 4 ["h" nil]) + (is (= (:type (:function result)) "layout")) + (let [nodes (json->nodes layout) + positions (json->positions (apply conj (:positions layout)) nodes) + connections (json->connections (apply conj (:edges layout)) nodes)] + (is (= positions new-pos)) + (is (= (set connections) edge))))) + +;; FCA +(deftest test-fca-write + (let [file "testing-data/digits-fca-2.json" result (mock-request {:function {:type "function" - :name "update-positions" - :args ["lay" "new-pos"]} - :lay {:type "layout" - :data (write-data - (make-layout lat pos edge up lo))} - :new-pos {:type "map" - :data new-pos}}) - layout (:result (:function result))] - (is (= (make-layout-nc - ;; Lattice object - (make-lattice-nc (:nodes (:lattice layout)) - (:edges (:lattice layout))) - ;; remove colons from keys - (read-data {:type "map" :data (:positions layout)}) - ;; cast vector to set, as JSON only supports lists - (into #{} (:connections layout)) - (read-data {:type "map" :data (:upper-labels layout)}) - (read-data {:type "map" :data (:lower-labels layout)})) - (make-layout lat new-pos edge up lo))) - (is (= (:type (:function result)) "layout")))) + :name "read-fca" + :args ["file"]} + :file {:type "string" + :data file}}) + fca (:result (:function result))] + (is (= (:type (:function result)) "map")) + (let [ctx (json->ctx (:context fca)) + lat (json->lattice (:lattice fca)) + impl (map json->implications (:implication_sets fca)) + file-fca (read-fca file)] + (= (:context file-fca) ctx) + (= (:lattice file-fca) lat) + (= (:implication_sets file-fca) impl)))) + +(deftest test-fca-read + (let [ctx (make-context-from-matrix ["a" "b"] ["x" "y"] [1 0 0 1]) + lattice (concept-lattice ctx) + result-ctx-fca (mock-request {:function {:type "function" + :name "map-invert" + :args ["fca"]} + :fca {:type "map" + :data {":context" {:type "context" + :data (write-data ctx)}}}}) + result-ctx-lat-fca (mock-request {:function {:type "function" + :name "map-invert" + :args ["fca"]} + :fca {:type "map" + :data {":context" {:type "context" + :data (write-data ctx)} + ":lattice" {:type "lattice" + :data (write-data lattice)}}}}) + result-ctx-lat-impl-fca (mock-request {:function {:type "function" + :name "map-invert" + :args ["fca"]} + :fca {:type "map" + :data {":context" {:type "context" + :data (write-data ctx)} + ":lattice" {:type "lattice" + :data (write-data lattice)} + ":implication_sets" {:type "list" + :data #{(write-data (canonical-base ctx))}}}}})] + (is (= (:status (:function result-ctx-fca)) 200)) + (is (= (:type (:function result-ctx-fca)) "map")) + (is (= (vals (:result (:function result-ctx-fca))) '("context"))) + (is (= (:status (:function result-ctx-lat-fca)) 200)) + (is (= (:type (:function result-ctx-fca)) "map")) + (is (= (vals (:result (:function result-ctx-lat-fca))) '("context" "lattice"))) + (is (= (:status (:function result-ctx-lat-impl-fca)) 200)) + (is (= (:type (:function result-ctx-lat-impl-fca)) "map")) + (is (= (vals (:result (:function result-ctx-lat-impl-fca))) '("context" "lattice" "implication_sets"))))) ;;; diff --git a/src/test/clojure/conexp/fca/concept_transform_test.clj b/src/test/clojure/conexp/fca/concept_transform_test.clj new file mode 100644 index 000000000..4effab466 --- /dev/null +++ b/src/test/clojure/conexp/fca/concept_transform_test.clj @@ -0,0 +1,25 @@ +;; 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.concept-transform-test + (:use conexp.fca.concept-transform conexp.fca.cover) + (:use conexp.fca.contexts) + (:use clojure.test)) + +(deftest test-transform-bv + (let [ctx1 (make-context (range 5) (range 5) <=) + ctx2 (make-context (range 3 7) (range 3 7) <=) + ctx3 (make-context (range 3) (range 5) <=) + ctx4 (make-context (range 7) (range 7) <=) + bv1 (generate-concept-cover (concepts ctx1)) + bv2 (generate-concept-cover (concepts ctx2)) + bv3 (generate-concept-cover (concepts ctx3)) + bv4 (generate-concept-cover (concepts ctx4))] + (is (= (transform-bv-cover ctx1 ctx2 bv1) bv2)) + (is (= (transform-bv-cover ctx1 ctx3 bv1) bv3)) + (is (= (transform-bv-cover ctx1 ctx4 bv1) bv4)))) diff --git a/src/test/clojure/conexp/fca/contexts_test.clj b/src/test/clojure/conexp/fca/contexts_test.clj index 055324e5d..70b3d2d5e 100644 --- a/src/test/clojure/conexp/fca/contexts_test.clj +++ b/src/test/clojure/conexp/fca/contexts_test.clj @@ -647,5 +647,5 @@ (some #(= some-context %) (compatible-subcontexts test-ctx-08))))) ;;; - +;(deftest logical-derivation) true diff --git a/src/test/clojure/conexp/fca/cover_test.clj b/src/test/clojure/conexp/fca/cover_test.clj index 75e48bddb..2d624c07c 100644 --- a/src/test/clojure/conexp/fca/cover_test.clj +++ b/src/test/clojure/conexp/fca/cover_test.clj @@ -7,9 +7,11 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.fca.cover-test - (:use conexp.fca.pqcores conexp.fca.cover - conexp.fca.contexts) - (:use conexp.fca.contexts) + (:use conexp.fca.pqcores + conexp.fca.cover + conexp.fca.contexts + conexp.fca.lattices + conexp.math.algebra) (:use clojure.test clojure.set)) (deftest test-generate-cover @@ -90,3 +92,4 @@ new-cover (generate-cover (map last (concepts new-ctx)))] (is (= new-cover (cover-reducer cover ctx new-ctx 4))))) + diff --git a/src/test/clojure/conexp/fca/lattices_test.clj b/src/test/clojure/conexp/fca/lattices_test.clj index 0821d81c6..a317d2370 100644 --- a/src/test/clojure/conexp/fca/lattices_test.clj +++ b/src/test/clojure/conexp/fca/lattices_test.clj @@ -10,6 +10,7 @@ (:use conexp.base conexp.fca.contexts conexp.fca.implications + conexp.fca.posets conexp.math.algebra conexp.fca.lattices) (:use clojure.test)) @@ -140,18 +141,6 @@ (forall [x (base-set lattice)] ((order lattice) zero x)))))) -(deftest test-directly-neighboured? - (with-testing-data [lattice testing-data] - (forall [x (base-set lattice), - y (base-set lattice)] - (<=> (directly-neighboured? lattice x y) - (and (not= x y) - ((order lattice) x y) - (forall [z (base-set lattice)] - (=> (and ((order lattice) x z) - ((order lattice) z y)) - (or (= x z) (= y z))))))))) - (deftest test-lattice-upper-neighbours (with-testing-data [lat testing-data] (forall [x (base-set lat)] diff --git a/src/test/clojure/conexp/fca/posets_test.clj b/src/test/clojure/conexp/fca/posets_test.clj new file mode 100644 index 000000000..c45c1d511 --- /dev/null +++ b/src/test/clojure/conexp/fca/posets_test.clj @@ -0,0 +1,142 @@ +;; 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.posets-test + (:use clojure.test) + (:require [conexp.base :refer :all] + [conexp.fca.contexts :refer [make-context-from-matrix]] + [conexp.fca.posets :refer :all] + [conexp.math.algebra :refer [base-set order]])) + +(def adj0 #{}) + +(def adj1 #{['a 'a]['b 'b]['c 'c]['d 'd] + ['a 'c]['a 'd]['b 'd]}) + +(def adj2 #{['a 'c]['a 'd]['b 'd]}) + +(def adj3 #{[1 2][2 3][3 4][4 1] + [1 1][2 2][3 3][4 4]}) + +(def adj4 #{[1 2][2 3][3 4] + [1 'a][2 'b][3 'c][4 'd] + [1 1][2 2][3 3][4 4] + [1 3][1 4][2 4] + [1 'b][1 'c][1 'd] + [2 'c][2 'd][3 'd] + ['a 'a]['b 'b]['c 'c]['d 'd]}) + +(def fast-poset (fn [a] (make-poset (set (apply concat a)) + (fn [x y] (some #{[x y]} a))))) + +(def testing-posets + (map fast-poset [adj0 adj1 adj4])) + +(deftest test-make-poset + (is (fast-poset adj0)) + (is (fast-poset adj1)) + (is (thrown? IllegalArgumentException + (fast-poset adj2))) + (is (thrown? IllegalArgumentException + (fast-poset adj3))) + (is (fast-poset adj4))) + +(deftest test-poset-to-matrix + (is (= [] + (poset-to-matrix (fast-poset adj0)))) + (is (= [1 0 1 1 ;a + 0 1 0 1 ;b + 0 0 1 0 ;c + 0 0 0 1] ;d + (poset-to-matrix (fast-poset adj1) + ['a 'b 'c 'd]))) + (is (= [1 1 1 1 1 1 1 1 ;1 + 0 1 1 1 0 1 1 1 ;2 + 0 0 1 1 0 0 1 1 ;3 + 0 0 0 1 0 0 0 1 ;4 + 0 0 0 0 1 0 0 0 ;a + 0 0 0 0 0 1 0 0 ;b + 0 0 0 0 0 0 1 0 ;c + 0 0 0 0 0 0 0 1] ;d + (poset-to-matrix (fast-poset adj4) + [1 2 3 4 'a 'b 'c 'd])))) + +(deftest test-poset-context + (are [poset context] (= (poset-context poset) context) + (fast-poset adj0) (make-context-from-matrix [] [] #{}) + (fast-poset adj1) (make-context-from-matrix ['a 'b 'c 'd] ['a 'b 'c 'd] + [1 0 1 1 + 0 1 0 1 + 0 0 1 0 + 0 0 0 1]) + (fast-poset adj4) (make-context-from-matrix [1 2 3 4 'a 'b 'c 'd] + [1 2 3 4 'a 'b 'c 'd] + [1 1 1 1 1 1 1 1 + 0 1 1 1 0 1 1 1 + 0 0 1 1 0 0 1 1 + 0 0 0 1 0 0 0 1 + 0 0 0 0 1 0 0 0 + 0 0 0 0 0 1 0 0 + 0 0 0 0 0 0 1 0 + 0 0 0 0 0 0 0 1]))) + +(deftest test-directly-neighboured? + (with-testing-data [poset testing-posets] + (forall [x (base-set poset), + y (base-set poset)] + (<=> (directly-neighboured? poset x y) + (and (not= x y) + ((order poset) x y) + (forall [z (base-set poset)] + (=> (and ((order poset) x z) + ((order poset) z y)) + (or (= x z) (= y z))))))))) + +(deftest test-poset-upper-neighbours + (with-testing-data [poset testing-posets] + (forall [x (base-set poset)] + (let [xs (set (poset-upper-neighbours poset x))] + (forall [y (base-set poset)] + (<=> (directly-neighboured? poset x y) + (contains? xs y))))))) + +(deftest test-poset-lower-neighbours + (with-testing-data [poset testing-posets] + (forall [x (base-set poset)] + (let [xs (set (poset-lower-neighbours poset x))] + (forall [y (base-set poset)] + (<=> (directly-neighboured? poset y x) + (contains? xs y))))))) + +(deftest test-order-ideal + (is (thrown-with-msg? AssertionError + #"Assert failed: x must be a subset of the ordered set." + (order-ideal (fast-poset adj1) #{1}))) + (are [poset x ideal] (= (order-ideal poset x) + ideal) + (fast-poset adj0) #{} #{} + (fast-poset adj1) #{} #{} + (fast-poset adj1) #{'d} #{'a 'b 'd} + (fast-poset adj1) #{'c} #{'a 'c} + (fast-poset adj1) #{'c 'd} #{'a 'b 'c 'd} + (fast-poset adj4) #{'a 3} #{1 2 3 'a} + (fast-poset adj4) #{1} #{1})) + +(deftest test-order-filter + (is (thrown-with-msg? AssertionError + #"Assert failed: x must be a subset of the ordered set." + (order-filter (fast-poset adj1) #{1}))) + (are [poset x o-filter] (= (order-filter poset x) + o-filter) + (fast-poset adj0) #{} #{} + (fast-poset adj1) #{} #{} + (fast-poset adj1) #{'d} #{'d} + (fast-poset adj1) #{'a} #{'a 'c 'd} + (fast-poset adj1) #{'b} #{'b 'd} + (fast-poset adj4) #{'a 3} #{3 4 'a 'c 'd} + (fast-poset adj4) #{1} #{1 2 3 4 'a 'b 'c 'd})) diff --git a/src/test/clojure/conexp/fca/pqcores_test.clj b/src/test/clojure/conexp/fca/pqcores_test.clj index d266ba496..33ed99906 100644 --- a/src/test/clojure/conexp/fca/pqcores_test.clj +++ b/src/test/clojure/conexp/fca/pqcores_test.clj @@ -73,16 +73,3 @@ (count (concepts (compute-core ctx (dec p) 1))))) (is (< 3 (count (concepts (compute-core ctx 1 (dec q)))))))) - -(deftest test-transform-bv - (let [ctx1 (make-context (range 5) (range 5) <=) -; ctx2 (make-context (range 3 7) (range 3 7) <=) -; ctx3 (make-context (range 3) (range 3) <=) - ctx4 (make-context (range 7) (range 7) <=) - bv1 (generate-concept-cover (concepts ctx1)) -; bv2 (generate-concept-cover (concepts ctx2)) -; bv3 (generate-concept-cover (concepts ctx3)) - bv4 (generate-concept-cover (concepts ctx4))] -; (is (= (transform-bv ctx1 ctx2 bv1) bv2)) -; (is (= (transform-bv ctx1 ctx3 bv1) bv3)) - (is (= (transform-bv ctx1 ctx4 bv1) bv4)))) diff --git a/src/test/clojure/conexp/fca/protoconcepts_test.clj b/src/test/clojure/conexp/fca/protoconcepts_test.clj new file mode 100644 index 000000000..4b5c172ef --- /dev/null +++ b/src/test/clojure/conexp/fca/protoconcepts_test.clj @@ -0,0 +1,158 @@ +;; 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.protoconcepts-test + (:use conexp.base + conexp.fca.contexts + conexp.fca.protoconcepts) + (:use clojure.test)) + +(def test-context-01 (make-context #{"a" "b" "c"} + #{0 1 2 3} + #{["a" 2] ["a" 3] + ["b" 1] ["b" 3] + ["c" 0] ["c" 1]})) + +(def test-context-02 (make-context #{1 2 3} + #{'a 'b 'c 'e} + #{[1 'a] [1 'c] + [2 'b] [2 'e] + [3 'b] [3 'c] [3 'e]})) + +(deftest test-protoconcepts-equals + (is (= (make-protoconcepts-nc #{1 2 3} <=) (make-protoconcepts-nc #{1 2 3} <=))) + (is (not= (make-protoconcepts-nc #{1 2 3} <=) (make-protoconcepts-nc #{1 2 3} >=)))) + +(deftest test-protoconcept? + ;; test all protoconcepts of test-context-01 + (are [object-set attribute-set] (protoconcept? test-context-01 [object-set attribute-set]) + #{} #{0 2} + #{} #{0 3} + #{} #{1 2} + #{} #{0 1 2} + #{} #{0 1 3} + #{} #{0 2 3} + #{} #{1 2 3} + #{} #{0 1 2 3} + #{"a"} #{2} + #{"a"} #{2 3} + #{"b"} #{1 3} + #{"c"} #{0} + #{"c"} #{0 1} + #{"a" "b"} #{3} + #{"a" "c"} #{} + #{"b" "c"} #{1} + #{"a" "b" "c"} #{}) + ;; test some non-protoconcepts of test-context-01 + (are [object-set attribute-set] (not (protoconcept? test-context-01 [object-set attribute-set])) + #{"a"} #{0} + #{"a"} #{1} + #{"a"} #{0 1} + #{"a"} #{0 2} + #{"a"} #{0 3} + #{"b"} #{0 3} + #{"b"} #{1 2} + #{"b"} #{2 3} + #{"b"} #{1 2 3} + #{"b"} #{0 2 3} + #{"b"} #{0 1 2 3} + #{"c"} #{2} + #{"c"} #{3} + #{"c"} #{1 2} + #{"c"} #{1 3} + #{"c"} #{0 1 2 3} + #{} #{} + #{} #{0} + #{} #{1} + #{} #{2} + #{} #{3} + #{} #{0 1} + #{} #{1 3} + #{} #{2 3} + #{"a"} #{} + #{"a"} #{3} + #{"b"} #{} + #{"b"} #{1} + #{"b"} #{3} + #{"c"} #{} + #{"c"} #{1} + #{"a" "b"} #{} + #{"b" "c"} #{})) + +(deftest test-protoconcepts? + ;; + (is (protoconcepts? test-context-01 + #{[#{} #{0 2}] + [#{} #{0 3}] + [#{} #{1 2}] + [#{} #{0 1 2}] + [#{} #{0 1 3}] + [#{} #{0 2 3}] + [#{} #{1 2 3}] + [#{} #{0 1 2 3}] + [#{"a"} #{2}] + [#{"a"} #{2 3}] + [#{"b"} #{1 3}] + [#{"c"} #{0}] + [#{"c"} #{0 1}] + [#{"a" "b"} #{3}] + [#{"a" "c"} #{}] + [#{"b" "c"} #{1}] + [#{"a" "b" "c"} #{}]})) + (is (not (protoconcepts? test-context-01 + #{[#{} #{0 2}] + [#{"a"} #{0 3}] + [#{"a" "b" "c"} #{}]})))) + +(deftest test-protoconcepts + ;; test protoconcept generation + (let [all-protoconcepts (protoconcepts test-context-02) + all-combinations + (for [obj-subset (subsets (objects test-context-02)) + attr-subset (subsets (attributes test-context-02))] + [obj-subset attr-subset])] + (is (= (map #(protoconcept? test-context-02 %) all-combinations) + (map #(contains? all-protoconcepts %) all-combinations))))) + +(def test-context-03 + (make-context-from-matrix #{1 2} + #{'a 'b} + [1 0 1 1])) + +(deftest test-protoconcepts-order + (is (= (protoconcepts-order test-context-03) + (make-protoconcepts-nc #{[#{} #{'b}] [#{} #{'a 'b}] [#{2} #{'b}] [#{2} #{'a 'b}] + [#{1} #{}] [#{1} #{'a}] [#{1 2} #{}] [#{1 2} #{'a}]} + (fn [[A B] [C D]] + (and (subset? A C) + (subset? D B))))))) + +(deftest test-make-protoconcepts + (is (make-protoconcepts #{1 2 3} <=)) + (is (thrown? IllegalArgumentException (make-protoconcepts #{1 2 3} <))) + (is (make-protoconcepts-nc #{1 2 3} <)) + (is (make-protoconcepts #{[#{} #{0 2}] + [#{} #{0 3}] + [#{} #{1 2}] + [#{} #{0 1 2}] + [#{} #{0 1 3}] + [#{} #{0 2 3}] + [#{} #{1 2 3}] + [#{} #{0 1 2 3}] + [#{"a"} #{2}] + [#{"a"} #{2 3}] + [#{"b"} #{1 3}] + [#{"c"} #{0}] + [#{"c"} #{0 1}] + [#{"a" "b"} #{3}] + [#{"a" "c"} #{}] + [#{"b" "c"} #{1}] + [#{"a" "b" "c"} #{}]} + (fn [[A B] [C D]] + (and (subset? A C) (subset? D B))) + test-context-01))) diff --git a/src/test/clojure/conexp/fca/random_contexts_test.clj b/src/test/clojure/conexp/fca/random_contexts_test.clj index 84cd3c03d..7fa6da28b 100644 --- a/src/test/clojure/conexp/fca/random_contexts_test.clj +++ b/src/test/clojure/conexp/fca/random_contexts_test.clj @@ -43,3 +43,29 @@ (is (satisfies? Context (random-dirichlet-context :attributes 7 :objects 15 :precision-parameter (* 0.2 8))))) + +(def K (make-context #{0 7 1 4 6 3 2 9 5 8} + #{"att_0" "att_5" "att_6" "att_4" "att_1" "att_7" "att_2" "att_3"} + #{[2 "att_5"] [8 "att_6"] [9 "att_4"] [5 "att_3"] [0 "att_7"] + [7 "att_0"] [3 "att_1"] [4 "att_3"] [9 "att_5"] [3 "att_5"] + [0 "att_3"] [4 "att_2"] [1 "att_7"] [5 "att_4"] [4 "att_6"] + [5 "att_1"] [7 "att_7"] [8 "att_0"] [9 "att_1"] [8 "att_2"] + [0 "att_6"] [0 "att_5"] [6 "att_3"] [5 "att_7"] [1 "att_5"] + [3 "att_0"] [6 "att_4"] [2 "att_4"] [4 "att_5"] [6 "att_2"] + [9 "att_7"] [1 "att_4"] [2 "att_1"] [4 "att_7"] [0 "att_1"] + [1 "att_2"] [2 "att_7"] [7 "att_1"] [2 "att_0"] [9 "att_6"] + [6 "att_7"] [7 "att_6"] [8 "att_7"] [1 "att_3"] [8 "att_1"] + [7 "att_5"] [6 "att_1"] [5 "att_6"] [3 "att_6"] [3 "att_7"]})) + +(deftest test-context-edge-swapping + (is (= (objects K) (objects (randomize-context-by-edge-swapping K)))) + (is (= (attributes K) (attributes (randomize-context-by-edge-swapping K)))) + (doseq [r (range 10)] + (is (= (count (incidence-relation K)) (count (incidence-relation (randomize-context-by-edge-swapping K))))))) + + +(deftest test-context-edge-rewiring + (is (= (objects K) (objects (randomize-context-by-edge-rewiring K)))) + (is (= (attributes K) (attributes (randomize-context-by-edge-rewiring K)))) + (doseq [r (range 10)] + (is (= (count (incidence-relation K)) (count (incidence-relation (randomize-context-by-edge-rewiring K))))))) diff --git a/src/test/clojure/conexp/fca/smeasure_test.clj b/src/test/clojure/conexp/fca/smeasure_test.clj new file mode 100644 index 000000000..c68fe508a --- /dev/null +++ b/src/test/clojure/conexp/fca/smeasure_test.clj @@ -0,0 +1,516 @@ +;; 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.smeasure-test + (:use conexp.fca.contexts + conexp.fca.implications + conexp.fca.smeasure + conexp.base) + (:use clojure.test)) + +(def- ctx1 + (make-context-nc #{1 2 3 4} #{1 2 3 4 5} + #{[1 1][2 2][1 3][2 4][3 5][4 1][4 3]})) + +(def- ctx2 + (make-context-nc #{1 2 3} #{1 2 3 4 5} + #{[1 1][2 2][1 3][2 4][3 5]})) + +(def- ctx3 + (make-context-nc #{1 2 3 4} #{1 2 5} + #{[1 1][2 2][3 5][4 1]})) + +(def- ctx4 + (make-context-nc #{1 2 3 4} #{3 5} + #{[1 3][3 5][4 3]})) + +(def- ctx-equality + (make-context #{1 2 3} #{1 2 3} =)) + +(def- ctx-inequality + (make-context #{1 2 3} #{1 2 3} not=)) + +(def- ctx5 + (make-context-nc #{1 2 3} #{4 5 6} + #{[1 4] [1 6] [2 5] [3 6]})) + +(def- ctx6 + (make-context-nc #{1 2} #{4 5 6} + #{[1 4] [1 6] [2 5]})) + +(def- sm1 + (make-id-smeasure ctx1)) + +(def- sm1-str + (str " |1 4 3 2 5 |1 4 3 2 5 \n" + "--+---------- --+----------\n" + "1 |x . x . . ⟶ 1 |x . x . . \n" + "--+---------- --+----------\n" + "4 |x . x . . ⟶ 4 |x . x . . \n" + "--+---------- --+----------\n" + "3 |. . . . x ⟶ 3 |. . . . x \n" + "--+---------- --+----------\n" + "2 |. x . x . ⟶ 2 |. x . x . \n")) + +(def- sm2 + (make-smeasure ctx1 ctx2 #(case % 1 1 4 1 2 2 3 3))) + +(def- sm2-str + (str " |1 4 3 2 5 |1 4 3 2 5 \n" + "--+---------- --+----------\n" + "1 |x . x . . ⟶ 1 |x . x . . \n" + "4 |x . x . . | \n" + "--+---------- --+----------\n" + "3 |. . . . x ⟶ 3 |. . . . x \n" + "--+---------- --+----------\n" + "2 |. x . x . ⟶ 2 |. x . x . \n")) + +(def- sm3 + (make-smeasure ctx1 ctx3 identity)) + +(def- sm4 + (make-smeasure ctx1 ctx4 identity)) + +(def- no-sm + (make-smeasure-nc ctx1 ctx2 #(case % 1))) + +(def- sm5 + (make-smeasure-nc ctx-equality ctx-inequality identity)) + +(deftest test-smeasure-to-string + (is (= (smeasure-to-string sm1) + sm1-str)) + (is (= (smeasure-to-string sm2) + sm2-str))) + +(deftest test-pre-image-measure + (are [sm pre-image] (= (#'conexp.fca.smeasure/pre-image-measure sm) pre-image) + sm1 {1 #{1}, 2 #{2}, 3 #{3}, 4 #{4}} + sm2 {1 #{1 4}, 2 #{2}, 3 #{3}} + sm3 {1 #{1}, 2 #{2}, 3 #{3}, 4 #{4}} + sm4 {1 #{1}, 2 #{2}, 3 #{3}, 4 #{4}} + sm5 {1 #{1}, 2 #{2}, 3 #{3}})) + +(deftest test-original-extents + (is (= (original-extents sm2) + (list #{} #{2} #{3} #{1 4} #{1 4 3 2})))) + +(deftest test-smeasure? + (is (smeasure? sm1)) + (is (smeasure? sm2)) + (is (not (smeasure? sm5))) + (is (not (smeasure? ctx1)))) + +(deftest test-make-smeasure + (is (= (smeasure-to-string (make-smeasure ctx1 ctx2 #(case % 1 1 4 1 2 2 3 3))) + sm2-str)) + (is (thrown-with-msg? + AssertionError + #"The Input is no valid Scale Measure" + (make-smeasure ctx1 ctx2 #(case % 1))))) + +(deftest test-make-smeasure-nc + (is (= (smeasure-to-string (make-smeasure-nc ctx1 ctx2 #(case % 1 1 4 1 2 2 3 3))) + sm2-str)) + (is (= (smeasure-to-string (make-smeasure-nc ctx1 ctx2 #(case % 1)))) + (str " |1 4 3 2 5 |1 4 3 2 5 \n" + "--+---------- --+----------\n" + "1 |x . x . . ⟶ 1 |x . x . . \n" + "--+---------- --+----------\n" + "4 |x . x . . ⟶ | \n" + "--+---------- --+----------\n" + "3 |. . . . x ⟶ | \n" + "--+---------- --+----------\n" + "2 |. x . x . ⟶ | \n"))) + +(deftest test-make-id-smeasure + (is (= (smeasure-to-string (make-id-smeasure ctx1)) + sm1-str))) + +(deftest test-smeasure-by-exts + (let [exts #{#{} #{1}}] + (is (= (scale (smeasure-by-exts ctx1 exts)) + (make-context (objects ctx1) exts #{[1 #{1}]}))))) + +(deftest test-canonical-smeasure-representation + (is (= (scale (canonical-smeasure-representation sm2)) + (make-context (objects ctx1) + #{#{} #{1 4} #{2} #{3} #{1 2 3 4}} + #{[1 #{1 4}] [1 #{1 2 3 4}] + [2 #{2}] [2 #{1 2 3 4}] + [3 #{3}] [3 #{1 2 3 4}] + [4 #{1 4}] [4 #{1 2 3 4}]})))) + +(deftest test-scale-apposition + (is (thrown? java.lang.AssertionError + (scale-apposition sm1 sm5))) + (is (= (scale (scale-apposition sm3 sm4)) + (make-context (objects ctx1) + #{[1 0] [2 0] [5 0] [3 1] [5 1]} + #{[1 [1 0]] [4 [1 0]] [2 [2 0]] [1 [3 1]] [4 [3 1]] [3 [5 0]] [3 [5 1]]}))) + (is (= (scale (scale-apposition sm2 sm4)) + (make-context (objects ctx1) + #{[#{} 0] [#{} 1] [#{1 4} 0] [#{1 4} 1] [#{2} 0] [#{3} 0] [#{3} 1] [#{1 2 3 4} 0] [#{1 2 3 4} 1]} + #{[1 [#{1 4} 0]] [1 [#{1 4} 1]] [4 [#{1 4} 0]] [4 [#{1 4} 1]] [2 [#{2} 0]] [3 [#{3} 0]] [3 [#{3} 1]] [1 [#{1 2 3 4} 0]] [1 [#{1 2 3 4} 1]] [2 [#{1 2 3 4} 0]] [2 [#{1 2 3 4} 1]] [3 [#{1 2 3 4} 0]] [3 [#{1 2 3 4} 1]] [4 [#{1 2 3 4} 0]] [4 [#{1 2 3 4} 1]]})))) + +(deftest test-meet-smeasure + (is (thrown? java.lang.AssertionError + (meet-smeasure sm1 sm5))) + (let [sm-meet-1 (meet-smeasure sm1 sm2) + sm-meet-2 (meet-smeasure sm3 sm4)] + (is (= (context sm-meet-1) ctx1)) + (is (= (scale sm-meet-1) + (make-context (objects ctx1) #{#{} #{2} #{3} #{1 4} #{1 2 3 4}} + #{[1 #{1 4}] [1 #{1 2 3 4}] [2 #{2}] [2 #{1 2 3 4}] [3 #{3}] [3 #{1 2 3 4}] [4 #{1 4}] [4 #{1 2 3 4}]}))) + (is (= (context sm-meet-2) ctx1)) + (is (= (scale sm-meet-2) + (make-context (objects ctx3) #{#{} #{3} #{1 4} #{1 2 3 4}} + #{[1 #{1 4}] [1 #{1 2 3 4}] [2 #{1 2 3 4}] [3 #{3}] [3 #{1 2 3 4}] [4 #{1 4}] [4 #{1 2 3 4}]}))))) + +(deftest test-join-complement + (is (= (join-complement sm2) + (make-smeasure ctx1 + (make-context [1 2 3 4] + [#{1 2 3 4}] + (fn [o a] (contains? a o))) + identity))) + (let [sm (make-smeasure ctx5 ctx6 #(case % 1 1 2 2 3 1))] + (is (= (join-complement sm) + (make-smeasure ctx5 + (make-context [1 2 3] + [#{1} #{1 2 3}] + (fn [o a] (contains? a o))) + identity))))) + +(deftest test-error-in-smeasure + (are [sm] (= (error-in-smeasure sm) []) + sm1 sm2 sm3 sm4) + (is (= (error-in-smeasure sm5) + [#{3 2} #{1 2} #{1 3}]))) + +(deftest test-valid-attributes + (are [sm valid-atts] (= (set (valid-attributes sm)) (set valid-atts)) + sm1 [1 2 3 4 5] + sm2 [1 2 3 4 5] + sm3 [1 2 5] + sm4 [3 5] + sm5 [])) + +(deftest test-invalid-attributes + (are [sm invalid-atts] (= (set (invalid-attributes sm)) (set invalid-atts)) + sm1 [] + sm2 [] + sm3 [] + sm4 [] + sm5 [1 2 3])) + +(deftest test-conceptual-scaling-error + (are [sm error] (= (conceptual-scaling-error sm) error) + sm1 0 + sm2 0 + sm3 0 + sm4 0 + sm5 3) + (are [sm error] (= (conceptual-scaling-error sm :relative true) error) + sm1 0 + sm2 0 + sm3 0 + sm4 0 + sm5 (/ 3 8))) + +(deftest test-attribute-scaling-error + (are [sm error] (= (attribute-scaling-error sm) error) + sm1 0 + sm2 0 + sm3 0 + sm4 0 + sm5 3) + (are [sm error] (= (attribute-scaling-error sm :relative true) error) + sm1 0 + sm2 0 + sm3 0 + sm4 0 + sm5 1)) + +(deftest test-remove-attributes + (let [ctx (rand-context (range 6) 0.5) + sm (make-id-smeasure ctx) + removed (remove-attributes-sm sm #{1 2})] + (is (= (attributes (scale removed)) #{0 3 4 5})) + (is (smeasure? removed)))) + +(deftest test-smeasure-valid-attr + (are [sm scale-ctx] (= (scale (smeasure-valid-attr sm)) scale-ctx) + sm1 ctx1 + sm2 ctx2 + sm3 ctx3 + sm4 ctx4) + (is (= (scale (smeasure-valid-attr sm5)) + (make-context (objects ctx-equality) #{} identity)))) + +(deftest test-smeasure-invalid-attr + (are [sm ctx] (= (scale (smeasure-invalid-attr sm)) + (make-context (objects ctx) #{} identity)) + sm1 ctx1 + sm2 ctx2 + sm3 ctx3 + sm4 ctx4) + (is (= (scale (smeasure-invalid-attr sm5)) + ctx-inequality))) + +(deftest test-smeasure-valid-exts + (are [sm] (= (attributes (scale (smeasure-valid-exts sm))) + #{#{} #{2} #{3} #{1 4} #{1 2 3 4}}) + sm1 + sm2 + sm3 + sm4) + (is (= (attributes (scale (smeasure-valid-exts sm5))) + #{#{} #{1} #{2} #{3} #{1 2 3}}))) + +(deftest test-smeasure-invalid-exts + (are [sm] (= (attributes (scale (smeasure-invalid-exts sm))) + #{}) + sm1 + sm2 + sm3 + sm4) + (is (= (attributes (scale (smeasure-invalid-exts sm5))) + #{#{1 2} #{1 3} #{2 3}}))) + +(deftest test-rename-scale-objects + ;; rename all objects in scale with a map + (is (= (objects (scale (#'conexp.fca.smeasure/rename-scale :objects sm1 {1 "A" 2 "B" 3 "C" 4 "D"}))) + #{"A" "B" "C" "D"})) + (is (= (scale (#'conexp.fca.smeasure/rename-scale :objects sm4 {1 "A" 2 "B" 3 "C" 4 "D"}))) + (make-context #{"A" "B" "C" "D"} #{3 5} #{["A" 3] ["C" 5] ["D" 3]})) + ;; rename one object in scale + (is (= (objects (scale (#'conexp.fca.smeasure/rename-scale :objects sm2 1 5))) + #{5 2 3})) + (is (= (scale (#'conexp.fca.smeasure/rename-scale :objects sm3 1 5)) + (make-context #{5 2 3 4} #{1 2 5} #{[5 1] [2 2] [3 5] [4 1]}))) + ;; rename object that does not exist + (is (= (scale (#'conexp.fca.smeasure/rename-scale :objects sm5 4 5)) + (scale sm5)))) + +(deftest test-rename-scale-attributes + ;; rename all attributes in scale with a map + (is (= (attributes (scale (#'conexp.fca.smeasure/rename-scale :attributes sm1 {1 "A" 2 "B" 3 "C" 4 "D" 5 "E"}))) + #{"A" "B" "C" "D" "E"})) + (is (= (scale (#'conexp.fca.smeasure/rename-scale :attributes sm2 {1 10 2 20 3 30 4 40 5 50})) + (make-context #{1 2 3} #{10 20 30 40 50} #{[1 10] [1 30] [2 20] [2 40] [3 50]}))) + ;; rename one attribute in scale + (is (= (attributes (scale (#'conexp.fca.smeasure/rename-scale :attributes sm3 2 "C"))) + #{1 "C" 5})) + (is (= (scale (#'conexp.fca.smeasure/rename-scale :attributes sm4 3 1)) + (make-context #{1 2 3 4} #{1 5} #{[1 1] [3 5] [4 1]}))) + ;; rename attribute that does not exist + (is (= (scale (#'conexp.fca.smeasure/rename-scale :attributes sm5 4 5)) + (scale sm5)))) + +(def- cnf-1 + (str " |1 4 3 2 5 |() (5) (4 :and 2) (1 :and 3) (1 :and 4 :and 3 :and 2 :and 5) \n" + "--+---------- --+-------------------------------------------------------------\n" + "1 |x . x . . ⟶ 1 |x . . x . \n" + "--+---------- --+-------------------------------------------------------------\n" + "4 |x . x . . ⟶ 4 |x . . x . \n" + "--+---------- --+-------------------------------------------------------------\n" + "3 |. . . . x ⟶ 3 |x x . . . \n" + "--+---------- --+-------------------------------------------------------------\n" + "2 |. x . x . ⟶ 2 |x . x . . \n")) + +(def- cnf-2 + (str " |1 4 3 2 5 |() (5) (1 :and 3) (1 :and 4 :and 3 :and 2 :and 5) \n" + "--+---------- --+--------------------------------------------------\n" + "1 |x . x . . ⟶ 1 |x . x . \n" + "--+---------- --+--------------------------------------------------\n" + "4 |x . x . . ⟶ 4 |x . x . \n" + "--+---------- --+--------------------------------------------------\n" + "3 |. . . . x ⟶ 3 |x x . . \n" + "--+---------- --+--------------------------------------------------\n" + "2 |. x . x . ⟶ 2 |x . . . \n")) + +(def- cnf-3 + (str " |1 3 2 |(1 :and 3 :and 2) () (3) (2) (1) \n" + "--+------ --+---------------------------------\n" + "1 |x . . ⟶ 1 |. x . . x \n" + "--+------ --+---------------------------------\n" + "3 |. x . ⟶ 3 |. x x . . \n" + "--+------ --+---------------------------------\n" + "2 |. . x ⟶ 2 |. x . x . \n")) + +(deftest test-conjunctive-normalform-smeasure-representation + (are [sm cnf] (= (smeasure-to-string (conjunctive-normalform-smeasure-representation sm)) cnf) + sm1 cnf-1 + sm2 cnf-1 + sm3 cnf-1 + sm4 cnf-2 + sm5 cnf-3)) + +(defn- no-print + "Prevents console output. Can be used in tests to redefine print / println." + [& body] + nil) + +(deftest test-recommend-by-importance + (with-redefs [print no-print, + println no-print] + (are [ctx n recommended] (= (scale (recommend-by-importance + ctx + (fn [context concept] (count (first concept))) + n)) + recommended) + ctx1 3 (make-context #{1 2 3 4} #{#{1 4} #{2} #{3}} + (fn [o a] (contains? a o))) + ctx-inequality 4 (make-context #{1 2 3} #{#{1} #{2} #{3} #{2 3}} + (fn [o a] (contains? a o))) + ctx5 3 (make-context #{1 2 3} #{#{1} #{2} #{1 3}} + (fn [o a] (contains? a o)))))) + +(defn- command-iteration + "Creates an iterable list of commands that can be used for redefinition of read-line in tests that expect user input." + [data] + (let [iter (.iterator (sequence data))] + #(when (.hasNext iter) (.next iter)))) + +(deftest test-conceptual-navigation + (is (thrown? java.lang.AssertionError + (conceptual-navigation sm1))) + (with-redefs [read-line (fn [] "done"), + print no-print, + println no-print] + (let [result (conceptual-navigation ctx1)] + (is (= result (make-id-smeasure ctx1)))))) + +(deftest test-conceptual-navigation-rename + (let [next-command (command-iteration ["rename" ":objects" "1;\"a\"" "done"])] + (with-redefs [read-line (fn [] (next-command)), + print no-print, + println no-print] + (let [result (conceptual-navigation ctx1) + renamed-ctx (make-context-nc #{"a" 2 3 4} #{1 2 3 4 5} + #{["a" 1][2 2]["a" 3][2 4][3 5][4 1][4 3]})] + (is (= result (make-smeasure ctx1 + renamed-ctx + #(case % 1 "a" 4 4 2 2 3 3)))))))) + +(deftest test-conceptual-navigation-logical-attr + (are [commands new-ctx] + (let [next-command (command-iteration commands)] + (with-redefs [read-line (fn [] (next-command)), + print no-print, + println no-print] + (let [result (conceptual-navigation ctx1)] + (= result (make-smeasure ctx1 new-ctx identity))))) + ["logical-attr" "[1 :and 3]" "6" "done"] (make-context-nc #{1 2 3 4} + #{1 2 3 4 5 6} + #{[1 1][2 2][1 3][2 4][3 5][4 1][4 3][1 6][4 6]}) + ["logical-attr" "[1 :or 5]" "6" "done"] (make-context-nc #{1 2 3 4} + #{1 2 3 4 5} + #{[1 1][2 2][1 3][2 4][3 5][4 1][4 3]}))) + +(deftest test-conceptual-navigation-truncate + (let [next-command (command-iteration ["truncate" "1;3" "done"])] + (with-redefs [read-line (fn [] (next-command)), + print no-print, + println no-print] + (let [result (conceptual-navigation ctx1) + new-ctx (make-context-nc #{1 2 3 4} #{2 4 5} + #{[2 2][2 4][3 5]})] + (is (= result (make-smeasure ctx1 new-ctx identity))))))) + +(deftest test-conceptual-navigation-clear + (let [next-command (command-iteration ["rename" ":objects" "1;\"a\"" "clear" "done"])] + (with-redefs [read-line (fn [] (next-command)), + print no-print, + println no-print] + (let [result (conceptual-navigation ctx1)] + (is (= result (make-id-smeasure ctx1))))))) + +(def- test-state-1 + {:scale (make-context [1 2 3 4] [#{} #{2}] [[2 #{2}]]) + :cur #{3} + :object-order [1 4 3 2] + :imps #{(make-implication #{2 3} #{1 2 3 4}) + (make-implication #{4} #{1 4}) + (make-implication #{1} #{1 4}) + (make-implication #{1 2 4} #{1 2 3 4}) + (make-implication #{1 3 4} #{1 2 3 4})} + :inc (fn ([a b] (contains? b a)) + ([[a b]] (contains? b a))) + :context ctx1}) + +(def- test-state-2 + {:scale (make-context [1 2 3 4] [] []) + :cur #{} + :object-order [1 4 3 2] + ;; canonical base of dual-context + :imps #{(make-implication #{2 3} #{1 2 3 4}) + (make-implication #{4} #{1 4}) + (make-implication #{1} #{1 4}) + (make-implication #{1 2 4} #{1 2 3 4}) + (make-implication #{1 3 4} #{1 2 3 4})} + :inc (fn ([a b] (contains? b a)) + ([[a b]] (contains? b a))) + :context ctx1}) + +(deftest test-exploration-of-scales-iteration + (with-redefs-fn {#'conexp.fca.smeasure/coarsened-by-imp? + (fn [state cur cur-conclusion] "all")} + #(let [new-state (exploration-of-scales-iteration test-state-1)] + (is (= (:scale new-state) + (make-context [1 2 3 4] [#{} #{2} #{3}] [[2 #{2}] [3 #{3}]]))) + (is (= (:cur new-state) + #{1 4})) + (is (= (:imps new-state) + (:imps test-state-1))))) + (with-redefs-fn {#'conexp.fca.smeasure/coarsened-by-imp? + (fn [state cur cur-conclusion] "none")} + #(let [new-state (exploration-of-scales-iteration test-state-1)] + (is (= (:scale new-state) + (:scale test-state-1))) + (is (= (:cur new-state) + #{1 4})) + (is (= (:imps new-state) + (union (:imps test-state-1) #{(make-implication #{3} #{1 2 3 4})}))))) + (let [next-command (command-iteration ["yes" "none"])] + (with-redefs-fn {#'conexp.fca.smeasure/coarsened-by-imp? + (fn [state cur cur-conclusion] (next-command)), + #'conexp.fca.smeasure/provide-counter-example + (fn [state cur cur-conclusion] #{1 4})} + #(let [new-state (exploration-of-scales-iteration test-state-2)] + (is (= (:scale new-state) + (make-context [1 2 3 4] [#{1 4}] [[1 #{1 4}] [4 #{1 4}]]))) + (is (= (:cur new-state) + #{1 4})) + (is (= (:imps new-state) + (union (:imps test-state-2) #{(make-implication #{} #{1 4})}))))))) + +(deftest test-exploration-of-scales + (let [next-command (command-iteration ["all" "all" "all" "none"])] + (with-redefs-fn {#'conexp.fca.smeasure/coarsened-by-imp? + (fn [state cur cur-conclusion] (next-command))} + #(is (= (scale (exploration-of-scales ctx1)) + (make-context [1 2 3 4] [#{} #{2} #{3}] [[2 #{2}] [3 #{3}]])))))) + +(def- ctx-lattice-filter+covering-concepts + (make-context #{1 2 3 4} #{1 2 3 4 5} + #{[1 1] [1 3] [2 2] [2 3] [2 4] [3 1] [3 5] [4 1] [4 3]})) + +(deftest test-concept-lattice-filter+covering-concepts + (are [g concepts] + (= (concept-lattice-filter+covering-concepts ctx-lattice-filter+covering-concepts g) + concepts) + 1 #{[#{} #{1 2 3 4 5}] [#{3} #{1 5}] [#{1 4} #{1 3}] [#{2} #{2 3 4}] + [#{1 3 4} #{1}] [#{1 2 4} #{3}] [#{1 2 3 4} #{}]} + 2 #{[#{} #{1 2 3 4 5}] [#{1 4} #{1 3}] [#{2} #{2 3 4}] + [#{1 3 4} #{1}] [#{1 2 4} #{3}] [#{1 2 3 4} #{}]} + 3 #{[#{} #{1 2 3 4 5}] [#{3} #{1 5}] [#{1 4} #{1 3}] + [#{1 3 4} #{1}] [#{1 2 4} #{3}] [#{1 2 3 4} #{}]})) + +nil diff --git a/src/test/clojure/conexp/gui/draw_test.clj b/src/test/clojure/conexp/gui/draw_test.clj new file mode 100644 index 000000000..9ec2d13a1 --- /dev/null +++ b/src/test/clojure/conexp/gui/draw_test.clj @@ -0,0 +1,42 @@ +;; 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.gui.draw-test + (:use conexp.fca.contexts + conexp.fca.lattices + conexp.gui.draw) + (:use clojure.test)) + +(def test-context + (rand-context 4 0.5)) + +(def test-lattice + (concept-lattice test-context)) + +(defn- mock-draw-layout + "Mock the draw-layout function, so that the GUI is not shown." + [layout & args] + {:frame nil :scene nil}) + +(deftest test-draw-lattice + "Check that draw-lattice does not throw an exception." + (with-redefs [draw-layout mock-draw-layout] + (let [result (draw-lattice test-lattice)] + (is (= result result))))) + +(deftest test-draw-lattice-with-valuations + "Check that draw-lattice with valuations does not throw an exception." + (with-redefs [draw-layout mock-draw-layout] + (let [result (draw-lattice test-lattice :value-fn (fn [c] 0))] + (is (= result result))))) + +(deftest test-draw-protoconcepts + "Check that draw-protoconcepts does not throw an exception." + (with-redefs [draw-layout mock-draw-layout] + (let [result (draw-protoconcepts test-lattice)] + (is (= result result))))) diff --git a/src/test/clojure/conexp/io/contexts_test.clj b/src/test/clojure/conexp/io/contexts_test.clj index a9931fc0c..253a88e77 100644 --- a/src/test/clojure/conexp/io/contexts_test.clj +++ b/src/test/clojure/conexp/io/contexts_test.clj @@ -24,13 +24,43 @@ ["b" "2"] ["c" "3"]}), (null-context #{})]) +(def- contexts-oi-fcalgs + [(make-context #{0 1} + #{0 1} + #{[0 1] [1 0] [1 1]})]) + (deftest test-context-out-in (with-testing-data [ctx contexts-oi, - fmt (remove #{:binary-csv} + fmt (remove #{:binary-csv :anonymous-burmeister :fcalgs :named-binary-csv} (list-context-formats))] - (try (= ctx (out-in ctx 'context fmt)) - (catch UnsupportedOperationException _ true)))) + (= ctx (out-in ctx 'context fmt))) + ;; named-binary-csv cannot export empty context + (with-testing-data [ctx [(first contexts-oi)], + fmt #{:named-binary-csv}] + (= ctx (out-in ctx 'context fmt))) + ;; fcalgs can only store contexts with integral objects and attributes >=0, + ;; and thus needs to be tested with another context + (with-testing-data [ctx contexts-oi-fcalgs, + fmt #{:fcalgs}] + (= ctx (out-in ctx 'context fmt)))) + +(defn- possible-isomorphic? + "Test for equality of some criteria for context isomorphy. Namely, number of objects and attributes, the size of the incidence relation, and the number of concepts. Only use with small contexts." + [ctx1 ctx2] + (are [x y] (= (count x) (count y)) + (objects ctx1) (objects ctx2) + (attributes ctx1) (attributes ctx2) + (incidence-relation ctx1) (incidence-relation ctx2) + (concepts ctx1) (concepts ctx2))) +(deftest test-isomorphic-out-in + (with-testing-data [ctx contexts-oi + fmt #{:anonymous-burmeister}] + (possible-isomorphic? ctx (out-in ctx 'context fmt))) + ;; binary-csv cannot export empty context + (with-testing-data [ctx [(first contexts-oi)] + fmt #{ :binary-csv}] + (possible-isomorphic? ctx (out-in ctx 'context fmt)))) ;; (def- contexts-oioi @@ -40,32 +70,63 @@ (deftest test-context-out-in-out-in (with-testing-data [ctx contexts-oioi, - fmt (list-context-formats)] - (try (out-in-out-in-test ctx 'context fmt) - (catch UnsupportedOperationException _ true)))) + fmt (remove #{:anonymous-burmeister :fcalgs} (list-context-formats))] + (out-in-out-in-test ctx 'context fmt)) + ;; fcalgs can only store contexts with integral objects and attributes >=0, + ;; and thus needs to be tested with another context + (with-testing-data [ctx contexts-oi-fcalgs, + fmt #{:fcalgs}] + (out-in-out-in-test ctx 'context fmt))) + +(deftest test-anonymous-burmeister-out-in-out-in + (with-testing-data [ctx contexts-oioi + fmt #{:anonymous-burmeister}] + (let [ctx1 (out-in ctx 'context fmt) + ctx2 (out-in ctx1 'context fmt)] + (possible-isomorphic? ctx1 ctx2 )))) ;; (def- contexts-with-empty-columns "Context with empty columns, to test for corner cases" - [(null-context #{1 2 3 4}), + [(null-context #{}), + (null-context #{1 2 3 4}), (null-context #{1 2 3}), - (null-context #{}), (make-context #{1 2 3} #{1 2 3} #{[1 2] [2 3] [3 2]})]) (deftest test-empty-columns (with-testing-data [ctx contexts-with-empty-columns, - fmt (list-context-formats)] - (try (out-in-out-in-test ctx 'context fmt) - (catch UnsupportedOperationException _ true)))) + ;; colibri and fcalgs cannot handle empty rows or columns + ;; empty contexts cannot be exported to named-binary-csv or binary-csv + fmt (remove #{:colibri :fcalgs :named-binary-csv :binary-csv} (list-context-formats))] + (out-in-out-in-test ctx 'context fmt)) + (with-testing-data [ctx (rest contexts-with-empty-columns), + fmt #{:named-binary-csv :binary-csv}] + (out-in-out-in-test ctx 'context fmt))) ;; +(def- random-test-contexts + (vec (random-contexts 20 50))) + (deftest test-for-random-contexts - (with-testing-data [ctx (random-contexts 20 50), - fmt (list-context-formats)] - (try (out-in-out-in-test ctx 'context fmt) - (catch UnsupportedOperationException _ true)))) + (with-testing-data [ctx random-test-contexts, + ;; colibri and fcalgs cannot handle empty rows or columns + ;; binary-csv and named-binary-csv cannot handle empty contexts + fmt (remove #{:anonymous-burmeister :colibri :fcalgs :binary-csv :named-binary-csv} + (list-context-formats))] + (out-in-out-in-test ctx 'context fmt)) + (with-testing-data [ctx (filter #(not (empty? (incidence-relation %))) + random-test-contexts), + fmt #{:binary-csv :named-binary-csv}] + (out-in-out-in-test ctx 'context fmt))) + +(deftest test-anonymous-burmeister-out-in-out-in-for-random-contexts + (with-testing-data [ctx (random-contexts 20 10), + fmt #{:anonymous-burmeister}] + (let [ctx1 (out-in ctx 'context fmt) + ctx2 (out-in ctx1 'context fmt)] + (possible-isomorphic? ctx1 ctx2 )))) ;;; GraphML (seperate testing as it can only be read but not written) @@ -119,8 +180,36 @@ (make-context #{"n0" "n1"} #{"0" "1"} #{["n0" "0"]["n0" "1"] - ["n1" "0"]["n1" "1"]}))) - ) + ["n1" "0"]["n1" "1"]})))) + +;;; + +(deftest test-automatically-identify-input-format-galicia + "Test if other input formats throw an error when searching for an input format matching the input file." + (if-not (.exists (java.io.File. "testing-data/galicia2.bin.xml")) + (warn "Could not verify identifying :galicia input format. Testing file not found.") + (let [ctx (read-context "testing-data/galicia2.bin.xml")] + (is (= 10 (count (attributes ctx)))) + (is (= 10 (count (objects ctx)))) + (is (= 27 (count (incidence-relation ctx))))))) + +(deftest test-json-not-matching-schema + "Read a json format that does not match the given schema." + (if-not (.exists (java.io.File. "testing-data/digits-lattice.json")) + (warn "Could not verify failing validation of context schema. Testing file not found.") + (is (thrown? + AssertionError + (read-context "testing-data/digits-lattice.json" :json))))) + +(deftest test-json-matching-schema + "Read a json format that matches the given schema." + (let [file "testing-data/digits-context1.1.json"] + (if-not (.exists (java.io.File. file)) + (warn "Could not verify validation of context schema. Testing file not found.") + (let [ctx (read-context file :json)] + (is (= 7 (count (attributes ctx)))) + (is (= 10 (count (objects ctx)))) + (is (= 47 (count (incidence-relation ctx)))))))) ;;; @@ -134,4 +223,44 @@ ;;; +(deftest test-identify-input-format + "Test if the automatic identification of the file format works correctly." + (with-testing-data [ctx contexts-oi, + fmt (remove #{:named-binary-csv :anonymous-burmeister + :binary-csv :fcalgs} + (list-context-formats))] + (= ctx (out-in-without-format ctx 'context fmt))) + + ;; The null-context in contexts-io cannot be exported to :named-binary-csv + ;; format. + (with-testing-data [ctx [(first contexts-oi)], + fmt #{:named-binary-csv}] + (= ctx (out-in-without-format ctx 'context fmt))) + + ;; During writing / reading in :anonymous-burmeister format, object and + ;; attribute names get lost and equality cannot be tested any more. + (with-testing-data [ctx contexts-oi, + fmt #{:anonymous-burmeister}] + (possible-isomorphic? ctx (out-in-without-format ctx 'context fmt))) + + ;; The null-context in contexts-io cannot be exported to :binary-csv format. + ;; During writing / reading in :binary-csv format, object and attribute names + ;; get lost and equality cannot be tested any more. + (with-testing-data [ctx [(first contexts-oi)], + fmt #{:binary-csv}] + (possible-isomorphic? ctx (out-in-without-format ctx 'context fmt))) + + ;; fcalgs test with another context + (with-testing-data [ctx contexts-oi-fcalgs, + fmt #{:fcalgs}] + (= ctx (out-in-without-format ctx 'context fmt)))) + +(deftest test-ctx->json + ;; test that attributes with empty column are not dropped + (let [K (make-context-from-matrix [1 2] [:a :b] [1 0 0 0])] + (is (= (ctx->json K) + {:attributes #{:a :b} + :objects #{1 2} + :incidence #{[1 :a]}})))) + nil diff --git a/src/test/clojure/conexp/io/fcas_test.clj b/src/test/clojure/conexp/io/fcas_test.clj new file mode 100644 index 000000000..db97f4539 --- /dev/null +++ b/src/test/clojure/conexp/io/fcas_test.clj @@ -0,0 +1,191 @@ +;; 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.io.fcas-test + (:use conexp.base + conexp.fca.contexts + conexp.fca.lattices + conexp.fca.implications + conexp.io.fcas + conexp.io.util-test + conexp.layouts) + (:use clojure.test)) + +;;; + +(def- context-oi + "Context to use for out-in testing" + (make-context #{"a" "b" "c"} + #{"1" "2" "3"} + #{["a" "1"] ["a" "3"] + ["b" "2"] ["c" "3"]})) + +(def- fca-ctx-oi + "FCA with context" + {:context context-oi}) + +(deftest test-fca-only-context-out-in + "Context is the only input" + (with-testing-data [fca [fca-ctx-oi], + fmt (list-fca-formats)] + (= fca (out-in fca 'fca fmt)))) + +(def- lattice-oi + "Lattice to use for out-in testing" + (concept-lattice context-oi)) + +(def- fca-lat-oi + "FCA with context and lattice" + {:context context-oi :lattice lattice-oi}) + +(deftest test-fca-with-lattice-out-in + "Context and lattice as input" + (with-testing-data [fca [fca-lat-oi], + fmt (list-fca-formats)] + (= fca (out-in fca 'fca fmt)))) + +(def- implications-oi + "Implications to use for out-in testing" + (canonical-base context-oi)) + +(def- fca-impl-oi + "FCA with context and implications" + {:context context-oi :implication-sets [implications-oi]}) + +(deftest test-fca-with-implication-out-in + "Context and implications as input" + (with-testing-data [fca [fca-impl-oi], + fmt (list-fca-formats)] + (= fca (out-in fca 'fca fmt)))) + +(def- fca-oi + "FCA with context, lattice and implications" + {:context context-oi :lattice lattice-oi :implication-sets [implications-oi]}) + +(deftest test-fca-out-in + "Context, lattice and implications as input" + (with-testing-data [fca [fca-oi], + fmt (list-fca-formats)] + (= fca (out-in fca 'fca fmt)))) + +(def- fca-several-implication-sets-oi + "FCA with context, lattice and several implication sets" + {:context context-oi :lattice lattice-oi :implication-sets [implications-oi implications-oi]}) + +(deftest test-fca-several-implication-sets-out-in + "Context, lattice and several implication sets as input" + (with-testing-data [fca [fca-several-implication-sets-oi], + fmt (list-fca-formats)] + (= fca (out-in fca 'fca fmt)))) + +(def- contexts-oioi + "Contexts to use for out-in-out-in testing" + [(make-context #{1 2 3} #{4 5 6} <), + (make-context #{"a"} #{"+"} #{["a" "+"]})]) + +(def- fca-ctx-oioi + "FCAs for out-in-out-in testing" + (into [] (for [ctx contexts-oioi] {:context ctx}))) + +(deftest test-fca-only-context-out-in-out-in + "Several tests with only-context input" + (with-testing-data [fca fca-ctx-oioi, + fmt (list-fca-formats)] + (out-in-out-in-test fca 'fca fmt))) + +(def- lattice-oioi + "Lattice to use for out-in-out-in testing" + (mapv concept-lattice contexts-oioi)) + +(def- fca-lat-oioi + "FCAs for out-in-out-in testing with context and lattice" + (into [] (for [[ctx lat] + (map list contexts-oioi lattice-oioi)] + {:context ctx :lattice lat}))) + +(deftest test-fca-with-lattice-out-in-out-in + "Several tests with context and lattice input" + (with-testing-data [fca fca-lat-oioi, + fmt (list-fca-formats)] + (out-in-out-in-test fca 'fca fmt))) + +(def- implications-oioi + "Implications to use for out-in-out-in testing" + (mapv canonical-base contexts-oioi)) + +(def- fca-impl-oioi + "FCAs for out-in-out-in testing with context and implications" + (into [] (for [[ctx impl] + (map list contexts-oioi implications-oioi)] + {:context ctx :implication-sets [impl]}))) + +(deftest test-fca-with-implications-out-in-out-in + "Several tests with context and implication input" + (with-testing-data [fca fca-impl-oioi, + fmt (list-fca-formats)] + (out-in-out-in-test fca 'fca fmt))) + +(def- fca-oioi + "FCAs for out-in-out-in testing with context, concepts and implications" + (into [] (for [[ctx lattice impl] + (map list contexts-oioi lattice-oioi implications-oioi)] + {:context ctx :lattice lattice :implication-sets [impl]}))) + +(deftest test-fca-out-in-out-in + "Several tests with complete FCA" + (with-testing-data [fca fca-oioi, + fmt (list-fca-formats)] + (out-in-out-in-test fca 'fca fmt))) + +(def- layouts-oioi + "Layout for out-in-out-in testing" + (mapv standard-layout lattice-oioi)) + +(def- fca-layout-oioi + "FCAs for out-in-out-in testing with context and layout" + (into [] (for [[ctx layout] + (map list contexts-oioi layouts-oioi)] + {:context ctx :layouts [layout]}))) + +(deftest test-fca-with-layouts-out-in-out-in + "Several tests with context and layout input" + (with-testing-data [fca fca-layout-oioi, + fmt (list-fca-formats)] + (out-in-out-in-test fca 'fca fmt))) + +;;; + +(deftest test-json-not-matching-schema + "Read a json format that does not match the given schema." + (let [file "testing-data/digits-lattice.json"] + (if-not (.exists (java.io.File. file)) + (warn "Could not verify failing validation of fca schema. Testing file not found.") + (is (thrown? + AssertionError + (read-fca file :json)))))) + +(deftest test-json-matching-schema + "Read a json format that matches the given schema." + (let [file "testing-data/digits-fca-2.json"] + (if-not (.exists (java.io.File. file)) + (warn "Could not verify validation of fca schema. Testing file not found.") + (let [fca (read-fca file :json)] + (is (= 6 (count (first (:implication-sets fca))))))))) + +(deftest test-identify-input-format + "Test if the automatic identification of the file format works correctly." + (with-testing-data [fca [fca-oi], + fmt (list-fca-formats)] + (= fca (out-in-without-format fca 'fca fmt))) + (with-testing-data [fca [(first fca-oioi)], + fmt (list-fca-formats)] + (= fca (out-in-without-format fca 'fca fmt)))) + +;;; + +nil diff --git a/src/test/clojure/conexp/io/implications_test.clj b/src/test/clojure/conexp/io/implications_test.clj new file mode 100644 index 000000000..183b9eb6e --- /dev/null +++ b/src/test/clojure/conexp/io/implications_test.clj @@ -0,0 +1,78 @@ +;; 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.io.implications-test + (:use conexp.base + conexp.fca.contexts + conexp.fca.implications + conexp.io.implications + conexp.io.util-test) + (:use clojure.test)) + +;;; + +(def- contexts-oi + "Context to use for out-in testing" + (make-context #{"a" "b" "c"} + #{"1" "2" "3"} + #{["a" "1"] ["a" "3"] + ["b" "2"] ["c" "3"]})) + +(def- implications-oi + "Implications to use for out-in testing" + [(canonical-base contexts-oi)]) + +(deftest test-implications-out-in + (with-testing-data [impl implications-oi, + fmt (list-implication-formats)] + (try (= impl (out-in impl 'implication fmt)) + (catch UnsupportedOperationException _ true)))) + +(def- contexts-oioi + "Contexts to use for out-in-out-in testing" + [(make-context #{1 2 3} #{4 5 6} <), + (make-context #{'a} #{'+} #{['a '+]})]) + +(def- implications-oioi + (mapv canonical-base contexts-oioi)) + +(deftest test-implications-out-in-out-in + (with-testing-data [impl implications-oioi, + fmt (list-implication-formats)] + (try (out-in-out-in-test impl 'implication fmt) + (catch UnsupportedOperationException _ true)))) + +;;; + +(deftest test-json-not-matching-schema + "Read a json format that does not match the given schema." + (if-not (.exists (java.io.File. "testing-data/digits-lattice.json")) + (warn "Could not verify failing validation of implications schema. Testing file not found.") + (is (thrown? + AssertionError + (read-implication "testing-data/digits-lattice.json" :json))))) + +(deftest test-json-matching-schema + "Read a json format that matches the given schema." + (if-not (.exists (java.io.File. "testing-data/digits-implication.json")) + (warn "Could not verify validation of implications schema. Testing file not found.") + (let [implication-set (read-implication "testing-data/digits-implication.json" :json)] + (is (= 6 (count implication-set)))))) + +(deftest test-identify-input-format + "Test if the automatic identification of the file format works correctly." + (with-testing-data [implication implications-oi, + fmt (list-implication-formats)] + (= implication (out-in-without-format implication 'implication fmt))) + (with-testing-data [implication [(first implications-oioi)], + fmt (list-implication-formats)] + (= implication (out-in-without-format implication 'implication fmt)))) + +;;; + +nil diff --git a/src/test/clojure/conexp/io/lattices_test.clj b/src/test/clojure/conexp/io/lattices_test.clj index ed178652f..c8dcbd40a 100644 --- a/src/test/clojure/conexp/io/lattices_test.clj +++ b/src/test/clojure/conexp/io/lattices_test.clj @@ -31,4 +31,28 @@ ;;; +(deftest test-json-matching-schema + "Read a json format that matches the given schema." + (let [file "testing-data/digits-lattice1.1.json"] + (if-not (.exists (java.io.File. file)) + (warn "Could not verify validation of lattice schema. Testing file not found.") + (let [lattice (read-lattice file :json)] + (is (= 48 (count (lattice-base-set lattice)))))))) + +(deftest test-json-not-matching-schema + "Read a json format that does not match the given schema." + (if-not (.exists (java.io.File. "testing-data/digits-context.json")) + (warn "Could not verify failing validation of lattice schema. Testing file not found.") + (is (thrown? + AssertionError + (read-lattice "testing-data/digits-context.json" :json))))) + +(deftest test-identify-input-format + "Test if the automatic identification of the input format works correctly." + (with-testing-data [lat testing-lattices, + fmt (list-lattice-formats)] + (= lat (out-in-without-format lat 'lattice fmt)))) + +;;; + nil diff --git a/src/test/clojure/conexp/io/layouts_test.clj b/src/test/clojure/conexp/io/layouts_test.clj index df3938823..e8278643f 100644 --- a/src/test/clojure/conexp/io/layouts_test.clj +++ b/src/test/clojure/conexp/io/layouts_test.clj @@ -11,6 +11,7 @@ conexp.fca.contexts conexp.fca.lattices conexp.layouts + conexp.layouts.base conexp.io.layouts conexp.io.util-test) (:use clojure.test)) @@ -21,12 +22,32 @@ (map (comp standard-layout concept-lattice) (random-contexts 20 10))) -(deftest test-layout-oioi +(deftest test-layouts-oioi (with-testing-data [lay testing-layouts, fmt (list-layout-formats)] - (try - (out-in-out-in-test lay 'layout fmt) - (catch IllegalArgumentException _ true)))) + (out-in-out-in-test lay 'layout fmt))) + +(deftest test-layouts-oi + (with-testing-data [lay testing-layouts, + fmt (remove #{:simple :text} (list-layout-formats))] + (let [out-in-lay (out-in lay 'layout fmt)] + ;; only test equality of lattice, positions and connections, as upper- and lower-labels are not saved in json layout format + (and (= (poset lay) (poset out-in-lay)) + (= (positions lay) (positions out-in-lay)) + (= (connections lay) (connections out-in-lay)))))) + +(def- testing-layouts-with-valuations + (map #(update-valuations % (comp count first)) testing-layouts)) + +(deftest test-layout-with-valuation-oi + (with-testing-data [lay testing-layouts-with-valuations, + fmt (remove #{:text :simple} (list-layout-formats))] + (= (valuations lay) (valuations (out-in lay 'layout fmt))))) + +(deftest test-layout-annotations + (with-testing-data [lay testing-layouts + fmt (remove #{:text} (list-layout-formats))] + (= (annotation lay) (annotation (out-in lay 'layout fmt))))) ;;; diff --git a/src/test/clojure/conexp/io/many_valued_contexts_test.clj b/src/test/clojure/conexp/io/many_valued_contexts_test.clj index 7e7e8d87f..7f17446c6 100644 --- a/src/test/clojure/conexp/io/many_valued_contexts_test.clj +++ b/src/test/clojure/conexp/io/many_valued_contexts_test.clj @@ -23,22 +23,51 @@ (deftest test-mv-context-out-in (with-testing-data [mv-ctx mv-contexts-oi, - fmt (list-mv-context-formats)] - (try (= mv-ctx (out-in mv-ctx 'many-valued-context fmt)) - (catch UnsupportedOperationException _ true)))) + fmt (remove #{:data-table} (list-mv-context-formats))] + (= mv-ctx (out-in mv-ctx 'many-valued-context fmt))) + ;; Cannot store many-valued contexts with less than 2 attributes in format :data-table. + ;; Cannot store many-valued context without objects in format :data-table. + (with-testing-data [mv-ctx (mapv mv-contexts-oi [0]), + fmt #{:data-table}] + (= mv-ctx (out-in mv-ctx 'many-valued-context fmt)))) (def- mv-contexts-oioi "Contexts to use for out-in-out-in testing" [(make-mv-context (range 10) (range 10) +), (make-mv-context (range 10) (range 10) (fn [_ _] (rand))) - (make-mv-context [] [] +)]) + (make-mv-context [] [] +) + (make-mv-context [1 2 3] + '[color size] + '#{[1 color blue] [1 size large] + [2 color green] [2 size very-large] + [3 color red] [3 size small]}) + (make-mv-context '[a b c] + '[d e] + '#{[a d 1] [a e 2] + [b d 3] [b e 4] + [c d 5] [c e 6]}) + (make-mv-context '[a b] + [1 2] + '#{[a 1 d] [a 2 e] + [b 1 1] [b 2 2]})]) (deftest test-mv-context-out-in-out-in (with-testing-data [mv-ctx mv-contexts-oioi, - fmt (list-mv-context-formats)] - (try (out-in-out-in-test mv-ctx 'many-valued-context fmt) - (catch UnsupportedOperationException _ true)))) + fmt (remove #{:data-table} (list-mv-context-formats))] + (out-in-out-in-test mv-ctx 'many-valued-context fmt)) + ;; Cannot store many-valued contexts with less than 2 attributes in format :data-table. + (with-testing-data [mv-ctx (mapv mv-contexts-oioi [0 1]), + fmt #{:data-table}] + (out-in-out-in-test mv-ctx 'many-valued-context fmt))) +(deftest test-automatically-identify-input-format-json + "Test if other input formats throw an error when searching for an input format matching the json input file." + (if-not (.exists (java.io.File. "testing-data/mv-context.json")) + (warn "Could not verify identifying :json input format. Testing file not found.") + (let [mv-ctx (read-mv-context "testing-data/mv-context.json")] + (comment (is (= '[color size] (attributes mv-ctx))) + (is (= #{1 2 3} (objects mv-ctx)))) + (is (= #{"blue" "large"} (values-of-object mv-ctx 1)))))) ;;; nil diff --git a/src/test/clojure/conexp/io/smeasure_test.clj b/src/test/clojure/conexp/io/smeasure_test.clj new file mode 100644 index 000000000..94bff1078 --- /dev/null +++ b/src/test/clojure/conexp/io/smeasure_test.clj @@ -0,0 +1,50 @@ +;; 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.io.smeasure-test + (:use conexp.base + [conexp.fca.contexts :refer [make-context-nc]] + conexp.fca.lattices + [conexp.fca.smeasure :refer [make-smeasure-nc]] + conexp.layouts.base + conexp.io.latex + conexp.io.smeasure) + (:use clojure.test)) + +;;; + +(def ctx1 (make-context-nc #{1 2 3 4} #{1 2 3 4 5} + #{[1 1][2 2][1 3][2 4][3 5][4 1][4 3]})) +(def ctx2 (make-context-nc #{1 2 3} #{1 2 3 4 5} #{[1 1][2 2][1 3][2 4][3 5]})) +(def ctx3 (make-context-nc #{1 2 3 4 5 6 7 8 9} #{1 2 3 4 5} + #{[1 1][2 2][1 3][2 4][3 5]})) + +(def sm1 (make-smeasure-nc ctx1 ctx2 #(case % 1 1 4 1 2 1 3 2))) +(def sm2 (make-smeasure-nc ctx1 ctx3 #(case % 1 1 4 1 2 1 3 2))) +(def sm3 (make-smeasure-nc ctx3 ctx1 + #(case % 1 1 4 1 2 1 3 2 5 1 6 2 7 1 8 4 9 3))) + +(deftest test-smeasure->tikz + (is (= (latex sm1 :tikz) + (slurp "testing-data/latex/tikz-smeasure1-2.tex"))) + (is (= (latex sm2 :tikz) + (slurp "testing-data/latex/tikz-smeasure1-3.tex"))) + (is (= (latex sm3 :tikz) + (slurp "testing-data/latex/tikz-smeasure3-1.tex")))) + +(deftest test-smeasure->tikz + (is (= (latex sm1 :lattice) + (slurp "testing-data/latex/lattice-smeasure1-2.tex"))) + (is (= (latex sm2 :lattice) + (slurp "testing-data/latex/lattice-smeasure1-3.tex"))) + (is (= (latex sm3 :lattice) + (slurp "testing-data/latex/lattice-smeasure3-1.tex")))) + +;;; + +nil diff --git a/src/test/clojure/conexp/io/util_test.clj b/src/test/clojure/conexp/io/util_test.clj index 6eb4a3c8e..2e1c2bfd6 100644 --- a/src/test/clojure/conexp/io/util_test.clj +++ b/src/test/clojure/conexp/io/util_test.clj @@ -25,7 +25,7 @@ (illegal-argument "out-in called with invalid type " type ".")) (let [tmp (.getAbsolutePath ^java.io.File (tmpfile))] (@writer format object tmp) - (@reader tmp))))) + (@reader tmp format))))) (defn out-in-out-in-test "Checks for object of type type whether it passes out-in-out-in, @@ -37,4 +37,20 @@ ;;; +(defn out-in-without-format + "Returns object, treates as type, read in from a file where it has + previously written to. Format is used for output, but not for input." + [object type format] + (let [namespace (str "conexp.io." type "s")] + (require (symbol namespace)) + (let [writer (resolve (symbol namespace (str "write-" type))), + reader (resolve (symbol namespace (str "read-" type)))] + (when (or (nil? writer) (nil? reader)) + (illegal-argument "out-in called with invalid type " type ".")) + (let [tmp (.getAbsolutePath ^java.io.File (tmpfile))] + (@writer format object tmp) + (@reader tmp))))) + +;;; + nil diff --git a/src/test/clojure/conexp/layouts/base_test.clj b/src/test/clojure/conexp/layouts/base_test.clj index e2a299df2..89c143331 100644 --- a/src/test/clojure/conexp/layouts/base_test.clj +++ b/src/test/clojure/conexp/layouts/base_test.clj @@ -8,8 +8,9 @@ (ns conexp.layouts.base-test (:use conexp.base - conexp.fca.contexts conexp.math.algebra + conexp.fca.contexts + conexp.fca.posets conexp.fca.lattices conexp.layouts.base conexp.layouts.layered) @@ -19,10 +20,14 @@ (deftest test-make-layout (is (layout? (make-layout {} []))) + ;; lattice layouts (is (layout? (make-layout {1 [0,0], 2 [1,1], 3 [2,2]} #{[1 2] [2 3]}))) (is (layout? (make-layout {1 [0 0], 2 [1 1], 3 [2 2]} [[1 2] [2 3]]))) + ;; poset layout + (is (layout? (make-layout {1 [0 0], 2 [1 0], 3 [1 1]} + [[1 3] [2 3]]))) (is (thrown-with-msg? IllegalArgumentException #"Positions must be a map." (make-layout 1 2))) @@ -37,13 +42,13 @@ #"Connections must be given between positioned points." (make-layout {1 [0,0]} [[1 2]]))) (is (thrown-with-msg? IllegalArgumentException - #"Positioned points must be the elements of the given lattice." - (make-layout (make-lattice-nc [1 2 3] <) + #"Positioned points must be the elements of the given poset." + (make-layout (make-poset-nc [1 2 3] <) {} []))) (is (thrown-with-msg? IllegalArgumentException - #"The given connections must represent the edges of the given lattice." - (make-layout (make-lattice-nc [1 2 3] <) + #"The given connections must represent the edges of the given poset." + (make-layout (make-poset-nc [1 2 3] <) {1 [0 0] 2 [1 1] 3 [2 2]} []))) (is (thrown-with-msg? IllegalArgumentException @@ -61,27 +66,27 @@ (is (thrown-with-msg? IllegalArgumentException #"Labels must be given as map." (make-layout {1 [0 0], 2 [0 1]} - #{[1 2]} - [1 ["1u" nil], 2 ["2u" [0 2]]] - {1 ["1l" [0 -1]], 2 ["2l" nil]}))) + #{[1 2]} + [1 ["1u" nil], 2 ["2u" [0 2]]] + {1 ["1l" [0 -1]], 2 ["2l" nil]}))) (is (thrown-with-msg? IllegalArgumentException #"Nodes in layout and given labeled nodes are different." (make-layout {1 [0 0], 2 [0 1]} - #{[1 2]} - {1 ["1u" nil], 2 ["2u" [0 2]], 3 ["3u" [0 3]]} - {1 ["1l" [0 -1]]}))) + #{[1 2]} + {1 ["1u" nil], 2 ["2u" [0 2]], 3 ["3u" [0 3]]} + {1 ["1l" [0 -1]]}))) (is (thrown-with-msg? IllegalArgumentException #"Nodes must be labeled with pairs" (make-layout {1 [0 0], 2 [0 1]} - #{[1 2]} - {1 ["1u" nil], 2 ["2u" 0 2]} - {1 ["1l" [0 -1]], 2 ["2l" nil]}))) + #{[1 2]} + {1 ["1u" nil], 2 ["2u" 0 2]} + {1 ["1l" [0 -1]], 2 ["2l" nil]}))) (is (thrown-with-msg? IllegalArgumentException #"Labels must be above the labeled node" (make-layout {1 [0 0], 2 [0 1]} - #{[1 2]} - {1 ["1u" nil], 2 ["2u" [0 1]]} - {1 ["1l" [0 1]], 2 ["2l" nil]})))) + #{[1 2]} + {1 ["1u" nil], 2 ["2u" [0 1]]} + {1 ["1l" [0 1]], 2 ["2l" nil]})))) (deftest test-positions-and-connections (let [pos {1 [0,0], 2 [1,1], 3 [2,2]}, @@ -113,25 +118,28 @@ (first (indexes y)))))] (simple-layered-layout lattice))) -(def- testing-layouts - (concat - [(make-layout {1 [0,0], 2 [1,1], 3 [2,2]} - #{[1 2] [2 3]})] - (repeatedly 10 rand-layout))) +(def- testing-poset-layouts + [(make-layout {1 [0,0], 2 [1,1], 3 [2,2]} + #{[1 2] [2 3]}) + (make-layout {1 [0 0], 2 [1 0], 3 [1 1]} + [[1 3] [2 3]])]) + +(def- testing-lattice-layouts + (repeatedly 10 rand-layout)) ;;; -(deftest test-lattice - (with-testing-data [lay testing-layouts] - (let [lattice (lattice lay)] - (and (= (nodes lay) (base-set lattice)) +(deftest test-connections + (with-testing-data [lay (concat testing-poset-layouts testing-lattice-layouts)] + (let [poset (poset lay)] + (and (= (nodes lay) (base-set poset)) (forall [x (nodes lay), y (nodes lay)] - (<=> (directly-neighboured? lattice x y) + (<=> (directly-neighboured? poset x y) (contains? (connections lay) [x y]))))))) (deftest test-update-positions - (with-testing-data [layout testing-layouts] + (with-testing-data [layout (concat testing-poset-layouts testing-lattice-layouts)] (let [updated (update-positions layout (map-by-fn (constantly [0 0]) (keys (positions layout))))] @@ -141,15 +149,35 @@ (map-by-fn (constantly [0 0]) (keys (positions layout)))))))) +(deftest test-update-valuations + (with-testing-data [layout (concat testing-poset-layouts testing-lattice-layouts)] + (let [updated (update-valuations layout + (map-by-fn (constantly 0) + (nodes layout)))] + (and (= (connections layout) + (connections updated)) + (= (positions layout) + (positions updated)) + (= (valuations updated) + (map-by-fn (constantly 0) + (nodes layout))))))) + +(deftest test-update-valuations-err + (with-testing-data [layout (concat testing-poset-layouts testing-lattice-layouts)] + (let [updated (update-valuations-error layout)] + (and (= (valuations updated) + (map-by-fn (constantly "err") + (nodes layout))))))) + (deftest test-nodes - (with-testing-data [layout testing-layouts] + (with-testing-data [layout (concat testing-poset-layouts testing-lattice-layouts)] (= (nodes layout) (set (keys (positions layout)))))) ;;; (deftest test-layout-memoization - (with-testing-data [lay testing-layouts] + (with-testing-data [lay (concat testing-poset-layouts testing-lattice-layouts)] (let [layout (make-layout-nc (positions lay) ;get fresh layout (connections lay)), counter (atom 0), @@ -166,48 +194,62 @@ ;;; (deftest test-upper-neighbours - (with-testing-data [lay testing-layouts] + (with-testing-data [lay (concat testing-poset-layouts testing-lattice-layouts)] (let [uppers (upper-neighbours lay)] (forall [x (nodes lay)] (= (set (uppers x)) - (lattice-upper-neighbours (lattice lay) x)))))) + (poset-upper-neighbours (poset lay) x)))))) (deftest test-lower-neighbours - (with-testing-data [lay testing-layouts] + (with-testing-data [lay (concat testing-poset-layouts testing-lattice-layouts)] (let [lowers (lower-neighbours lay)] (forall [x (nodes lay)] (= (set (lowers x)) - (lattice-lower-neighbours (lattice lay) x)))))) + (poset-lower-neighbours (poset lay) x)))))) + +(def- test-poset-layout + (make-layout {1 [0 0] 2 [-1 1] 3 [1 1] 4 [-1 2] 5 [1 2]} + #{[1 2] [1 3] [2 4] [2 5] [3 4] [3 5]})) (deftest test-upper-neighbours-of-inf-irreducibles - (with-testing-data [lay testing-layouts] + (with-testing-data [lay testing-lattice-layouts] (let [uppers (upper-neighbours-of-inf-irreducibles lay), - infs (lattice-inf-irreducibles (lattice lay))] + infs (lattice-inf-irreducibles (poset lay))] (forall [x infs] (= (set (list (uppers x))) - (lattice-upper-neighbours (lattice lay) x)))))) + (lattice-upper-neighbours (poset lay) x))))) + (is (thrown? AssertionError + (upper-neighbours-of-inf-irreducibles test-poset-layout)))) (deftest test-inf-irreducibles - (with-testing-data [lay testing-layouts] + (with-testing-data [lay testing-lattice-layouts] (= (set (inf-irreducibles lay)) - (set (lattice-inf-irreducibles (lattice lay)))))) + (set (lattice-inf-irreducibles (poset lay))))) + (is (thrown? AssertionError + (inf-irreducibles test-poset-layout)))) (deftest test-sup-irreducibles - (with-testing-data [lay testing-layouts] + (with-testing-data [lay testing-lattice-layouts] (= (set (sup-irreducibles lay)) - (set (lattice-sup-irreducibles (lattice lay)))))) + (set (lattice-sup-irreducibles (poset lay))))) + (is (thrown? AssertionError + (sup-irreducibles test-poset-layout)))) (deftest test-full-order-relation - (let [lay (make-layout {1 [0,0], 2 [1,1], 3 [2,2]} - #{[1 2] [2 3]})] - (is (= (full-order-relation lay) - #{[1 1] [2 2] [3 3] - [1 2] [2 3] [1 3]})))) + (is (= (full-order-relation (first testing-poset-layouts)) + #{[1 1] [2 2] [3 3] + [1 2] [2 3] [1 3]})) + (is (= (full-order-relation (second testing-poset-layouts)) + #{[1 1] [2 2] [3 3] + [1 3] [2 3]}))) (deftest test-context - (with-testing-data [lay testing-layouts] + (with-testing-data [lay testing-poset-layouts] + (= (context lay) + (poset-context (poset lay)))) + (with-testing-data [lay testing-lattice-layouts] (= (context lay) - (standard-context (lattice lay))))) + (standard-context (poset lay))))) (deftest test-concept-lattice-layout? (is (concept-lattice-layout? @@ -225,7 +267,14 @@ [6 6] [1 1] [5 6] [1 3] [3 6] [0 3] [1 4] [2 6] [0 4] [1 5] [0 5] [5 1] [6 2] [3 0] [4 1] - [2 0]})] + [2 0]}) + poset (make-poset #{[#{1} #{5}] + [#{4} #{5 6}] + [#{1 3 4} #{}] + [#{1 2 4} #{5}]} + (fn [[A B] [C D]] + (and (subset? A C) + (subset? D B))))] (is (= (concept-lattice-annotation (simple-layered-layout (concept-lattice ctx))) {[#{1 4} #{1 3}] [#{} #{4}], [#{1 5} #{1 4}] [#{} #{}], @@ -243,7 +292,16 @@ [#{1 2 3} #{0}] [#{0} #{}], [#{1 2} #{0 1}] [#{} #{}], [#{0 1 4} #{3}] [#{3} #{}], - [#{0 1 5} #{4}] [#{4} #{}]})))) + [#{0 1 5} #{4}] [#{4} #{}]})) + (is (= (concept-lattice-annotation + (simple-layered-layout poset)) + {[#{1} #{5}] [#{} #{1}], + [#{4} #{5 6}] [#{6} #{4}], + [#{1 3 4} #{}] [#{} #{3}], + [#{1 2 4} #{5}] [#{5} #{2}]})) + (is (thrown-with-msg? AssertionError + #"Layout must be that of a concept lattice." + (concept-lattice-annotation test-poset-layout))))) (deftest test-annotation (let [ctx (make-context (range 7) @@ -282,7 +340,19 @@ (constantly ['y nil]))) {1 ['x 'y], 2 ['x 'y], - 3 ['x 'y]}))) + 3 ['x 'y]})) + (is (= (annotation (make-layout {1 [0 0], 2 [1 0], 3 [1 1]} + #{[1 3] [2 3]})) + {1 [1, ""], + 2 [2, ""], + 3 [3, ""]})) + (is (= (annotation (simple-layered-layout + (make-poset [[#{2} #{2 3}] [#{1 2 3} #{3}] [#{1} #{1 3}]] + (fn [A B] + (subset? (first A) (first B)))))) + {[#{1} #{1 3}] ["1" "1"], + [#{2} #{2 3}] ["2" "2"], + [#{1 2 3} #{3}] ["3" "3"]}))) ;;; diff --git a/src/test/clojure/conexp/layouts/common_test.clj b/src/test/clojure/conexp/layouts/common_test.clj index aeaa6eb63..e0309b00d 100644 --- a/src/test/clojure/conexp/layouts/common_test.clj +++ b/src/test/clojure/conexp/layouts/common_test.clj @@ -10,18 +10,36 @@ (:use conexp.base conexp.math.algebra conexp.fca.lattices + conexp.fca.posets conexp.layouts.base conexp.layouts.common) (:use clojure.test)) ;;; +(def- test-lattice (make-lattice (subsets #{1 2 3}) + subset?)) + +(def- test-poset (make-poset [1 2 3 4 5] + (fn [A B] + (contains? #{[1 1] [1 3] [1 4] [1 5] + [2 2] [2 3] [2 4] [2 5] + [3 3] [3 5] + [4 4] [4 5] + [5 5]} + [A B])))) + (deftest test-placement-by-initials - (let [placement (placement-by-initials (make-lattice (subsets #{1 2 3}) - subset?) - [1 1] - {#{1 2} [-2 0], #{1 3} [0 0], #{2 3} [2 0]})] - (is (= placement + (let [lattice-placement (placement-by-initials test-lattice + [1 1] + {#{1 2} [-2 0], + #{1 3} [0 0], + #{2 3} [2 0]}) + poset-placement (placement-by-initials test-poset + [1 1] + {3 [0 0], + 4 [2 0]})] + (is (= lattice-placement {#{} [-2 -2], #{1} [-3 -1], #{2} [-1 -1], @@ -29,29 +47,41 @@ #{1 2} [-2 0], #{1 3} [0 0], #{2 3} [2 0], - #{1 2 3} [1 1]})))) + #{1 2 3} [1 1]})) + (is (= poset-placement + {5 [1 1], + 4 [2 0], + 3 [0 0], + 2 [1 -1] + 1 [1 -1]})))) -(def- test-lattice (make-lattice (subsets #{1 2 3}) - subset?)) +(def- test-layout-1 (make-layout {#{} [-2 -6], + #{1} [-5 -1], + #{2} [-1 -1], + #{3} [0 -1], + #{1 2} [-2 0], + #{1 3} [0 0], + #{2 3} [2 0], + #{1 2 3} [1 1]} + [[#{} #{1}], [#{} #{2}], [#{} #{3}], + [#{1} #{1 2}], [#{1} #{1 3}], + [#{2} #{1 2}], [#{2} #{2 3}], + [#{3} #{1 3}], [#{3} #{2 3}], + [#{1 2} #{1 2 3}], + [#{1 3} #{1 2 3}], + [#{2 3} #{1 2 3}]])) -(def- test-layout (make-layout {#{} [-2 -6], - #{1} [-5 -1], - #{2} [-1 -1], - #{3} [0 -1], - #{1 2} [-2 0], - #{1 3} [0 0], - #{2 3} [2 0], - #{1 2 3} [1 1]} - [[#{} #{1}], [#{} #{2}], [#{} #{3}], - [#{1} #{1 2}], [#{1} #{1 3}], - [#{2} #{1 2}], [#{2} #{2 3}], - [#{3} #{1 3}], [#{3} #{2 3}], - [#{1 2} #{1 2 3}], - [#{1 3} #{1 2 3}], - [#{2 3} #{1 2 3}]])) +(def- test-layout-2 (make-layout {1 [1 1], + 2 [-2 2], + 3 [0 2], + 4 [4 -1], + 5 [4 4], + 6 [-1 3]} + [[1 2] [1 3] [1 5] + [2 6] [3 6] [4 5]])) (deftest test-to-inf-additive-layout - (is (= (positions (to-inf-additive-layout test-layout)) + (is (= (positions (to-inf-additive-layout test-layout-1)) {#{} [-2 -2], #{1} [-3 -1], #{2} [-1 -1], @@ -59,11 +89,21 @@ #{1 2} [-2 0], #{1 3} [0 0], #{2 3} [2 0], - #{1 2 3} [1 1]}))) + #{1 2 3} [1 1]})) + (is (= (nodes (to-inf-additive-layout test-layout-2)) + (nodes test-layout-2))) + (is (= (connections (to-inf-additive-layout test-layout-2)) + (connections test-layout-2))) + (is (= (positions (to-inf-additive-layout test-layout-2)) + {1 [1 -4], + 2 [-2 2], + 3 [0 2], + 4 [4 -1], + 5 [4 4], + 6 [-1 3]}))) (deftest test-layout-by-placement - (is (= (positions (layout-by-placement (make-lattice (subsets #{1 2 3}) - subset?) + (is (= (positions (layout-by-placement test-lattice [1 1] {#{1 2} [-2 0], #{1 3} [0 0], #{2 3} [2 0]})) {#{} [-2 -2], @@ -73,8 +113,15 @@ #{1 2} [-2 0], #{1 3} [0 0], #{2 3} [2 0], - #{1 2 3} [1 1]}))) - + #{1 2 3} [1 1]})) + (is (= (positions (layout-by-placement test-poset + [1 1] + {3 [0 0], 4 [2 0]})) + {5 [1 1], + 4 [2 0], + 3 [0 0], + 2 [1 -1], + 1 [1 -1]}))) ;;; diff --git a/src/test/clojure/conexp/layouts/dim_draw_test.clj b/src/test/clojure/conexp/layouts/dim_draw_test.clj index db15f79c5..fab1aa38c 100644 --- a/src/test/clojure/conexp/layouts/dim_draw_test.clj +++ b/src/test/clojure/conexp/layouts/dim_draw_test.clj @@ -1,13 +1,11 @@ (ns conexp.layouts.dim-draw-test (:require [clojure.test :refer :all] - [conexp.fca.lattices :as lat] [conexp.fca.graph :refer :all] [conexp.util.graph :refer :all] [conexp.base :exclude [transitive-closure] :refer :all] [loom.graph :as lg] [loom.alg :as la] - [rolling-stones.core :as sat :refer :all] - [conexp.layouts.base :as lay]) + [rolling-stones.core :as sat :refer :all]) (:use conexp.layouts.dim-draw)) ;;; @@ -95,6 +93,12 @@ [9 12] [10 15] [10 17] [11 13] [11 16] [12 15] [13 17] [14 18] [15 18] [16 18] [17 18]))))) +(def g-poset + (transitive-closure + (add-loops + (lg/digraph + [1 2] [1 3] [1 5] [2 6] [3 6] [4 5])))) + ;;; (deftest test-compute-conjugate-order @@ -164,6 +168,17 @@ [7 3] [7 2] [7 4] [7 8] [8 2] [8 3] [8 4] [9 4]}] (is (la/dag? graph)) (is (= graph (transitive-closure graph))) + (is (= (set (map set edgelist)) + (set (map set (lg/edges graph)))))) + (let [graph + (lg/add-edges* + (lg/digraph) + (compute-conjugate-order (nodes g-poset) + #(lg/has-edge? g-poset %1 %2))) + edgelist + #{[1 4] [2 3] [2 4] [2 5] [3 4] [3 5] [4 6] [5 6]}] + (is (la/dag? graph)) + (is (= graph (transitive-closure graph))) (is (= (set (map set edgelist)) (set (map set (lg/edges graph))))))) @@ -317,7 +332,9 @@ #{[0 [0 0]] [7 [3 9]] [1 [4 4]] [4 [10 10]] [6 [2 8]] [3 [6 6]] [2 [5 5]] [9 [8 2]] [5 [1 7]] [10 [9 3]] [8 [7 1]]})) (is (= (set (compute-coordinates gParallel nil)) - #{[0 [0 0]] [1 [1 4]] [5 [5 5]] [2 [4 1]] [3 [3 2]] [4 [2 3]]}))) + #{[0 [0 0]] [1 [1 4]] [5 [5 5]] [2 [4 1]] [3 [3 2]] [4 [2 3]]})) + (is (= (set (compute-coordinates g-poset nil)) + #{[1 [0 1]] [2 [2 3]] [3 [1 4]] [4 [4 0]] [5 [5 2]] [6 [3 5]]}))) ;; test drawing layout diff --git a/src/test/clojure/conexp/layouts/force_test.clj b/src/test/clojure/conexp/layouts/force_test.clj index 4c2586a91..35ef7c1d2 100644 --- a/src/test/clojure/conexp/layouts/force_test.clj +++ b/src/test/clojure/conexp/layouts/force_test.clj @@ -7,18 +7,32 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.layouts.force-test - (:use conexp.fca.contexts + (:use conexp.base + conexp.fca.contexts conexp.fca.lattices + conexp.fca.posets + conexp.layouts.base conexp.layouts.layered conexp.layouts.force) (:use clojure.test)) ;;; +(def- test-poset (make-poset [1 2 3 4 5 6] + (fn [A B] + (contains? #{[1 1] [1 2] [1 3] [1 5] [1 6] + [2 2] [2 6] [3 3] [3 6] + [4 4] [4 5] [5 5] [6 6]} + [A B])))) + (deftest test-layout-energy (is (pos? (layout-energy (simple-layered-layout (concept-lattice - (rand-context [1 2 3 4] 0.5))))))) + (rand-context [1 2 3 4] 0.5)))))) + (is (thrown-with-msg? AssertionError + #"The given layout does not contain a lattice." + (layout-energy + (simple-layered-layout test-poset))))) (deftest test-force-layout (let [lattice (concept-lattice (make-context-from-matrix 5 5 @@ -29,7 +43,12 @@ 0 1 1 0 0])), layout (simple-layered-layout lattice), layouts (take 10 (iterate #(force-layout % 100) layout))] - (is (apply > (map layout-energy layouts))))) + (is (apply > (map layout-energy layouts)))) + (let [poset-layout (simple-layered-layout test-poset)] + (is (= (nodes (force-layout poset-layout)) + (nodes poset-layout))) + (is (= (connections (force-layout poset-layout)) + (connections poset-layout))))) ;;; diff --git a/src/test/clojure/conexp/layouts/freese_test.clj b/src/test/clojure/conexp/layouts/freese_test.clj index c9cea7852..b7c91d771 100644 --- a/src/test/clojure/conexp/layouts/freese_test.clj +++ b/src/test/clojure/conexp/layouts/freese_test.clj @@ -9,6 +9,7 @@ (ns conexp.layouts.freese-test (:use conexp.fca.contexts conexp.fca.lattices + conexp.fca.posets conexp.layouts.base conexp.layouts.freese) (:use clojure.test)) @@ -17,8 +18,15 @@ (deftest test-freese (let [lat (concept-lattice (rand-context 10 10 0.5))] - (layout? (freese-layout lat)) - (layout? ((interactive-freese-layout lat) 0.0)))) + (is (layout? (freese-layout lat))) + (is (layout? ((interactive-freese-layout lat) 0.0)))) + (let [poset (make-poset [1 2 3 4 5 6] + (fn [A B] + (contains? #{[1 1] [1 2] [1 3] [1 5] [1 6] + [2 2] [2 6] [3 3] [3 6] + [4 4] [4 5] [5 5] [6 6]} [A B])))] + (is (layout? (freese-layout poset))) + (is (layout? ((interactive-freese-layout poset) 0.0))))) ;;; diff --git a/src/test/clojure/conexp/layouts/layered_test.clj b/src/test/clojure/conexp/layouts/layered_test.clj index c5d0d3ac1..7c06d4d98 100644 --- a/src/test/clojure/conexp/layouts/layered_test.clj +++ b/src/test/clojure/conexp/layouts/layered_test.clj @@ -9,6 +9,8 @@ (ns conexp.layouts.layered-test (:use conexp.base conexp.math.algebra + conexp.fca.contexts + conexp.fca.protoconcepts conexp.fca.lattices conexp.layouts.base conexp.layouts.util @@ -18,13 +20,20 @@ ;;; +(def test-ctx-1 + (make-context-from-matrix [1 2 3] + ['a 'b 'c] + [1 0 0 + 0 1 0 + 0 1 1])) + (deftest test-simple-layered-layout - (with-testing-data [lattice test-lattices] - (let [simply-layered (simple-layered-layout lattice), - layers (layers lattice), + (with-testing-data [poset (apply conj test-lattices test-posets)] + (let [simply-layered (simple-layered-layout poset), + layers (layers poset), simple-layers (sort-by first (reduce! (fn [map [a [x y]]] - (assoc! map y (conj (get map y) x))) + (assoc! map y (conj (get map y) x))) {} (positions simply-layered)))] (and (layout? simply-layered) @@ -33,20 +42,32 @@ (= (map count layers) (map (comp count second) simple-layers)) (forall [[_ coords] simple-layers] - (forall [x coords] - (exists [y coords] - (= x (- y))))))))) + (forall [x coords] + (exists [y coords] + (= x (- y)))))))) + ;; for protoconcepts + (let [test-protoconcepts (protoconcepts-order test-ctx-1) + protoconcept-layout (simple-layered-layout test-protoconcepts) + layers (layers test-protoconcepts) + protoconcept-layers (sort + (group-by #(second (second %)) + (positions protoconcept-layout)))] + (is (layout? protoconcept-layout)) + (is (= (count layers) + (count protoconcept-layers))) + (is (= (map count layers) + (map (comp count second) protoconcept-layers))))) (deftest test-as-chain - (with-testing-data [lattice test-lattices] - (let [chain (as-chain lattice)] + (with-testing-data [poset (apply conj test-lattices test-posets)] + (let [chain (as-chain poset)] (and (layout? chain) (>= 1 (count (set-of a | [a _] (vals (positions chain))))) (forall [[x [_ b]] (positions chain), [y [_ d]] (positions chain)] - (=> (and ((order lattice) x y) - (not= x y)) - (< b d))))))) + (=> (and ((order poset) x y) + (not= x y)) + (< b d))))))) ;;; diff --git a/src/test/clojure/conexp/layouts/util_test.clj b/src/test/clojure/conexp/layouts/util_test.clj index 674ac5805..b198f57e2 100644 --- a/src/test/clojure/conexp/layouts/util_test.clj +++ b/src/test/clojure/conexp/layouts/util_test.clj @@ -11,14 +11,22 @@ conexp.fca.contexts conexp.math.algebra conexp.fca.lattices + conexp.fca.posets + conexp.layouts.common + conexp.layouts.force conexp.layouts.util conexp.layouts.base) - (:use clojure.test)) + (:use clojure.test) + (:import [conexp.fca.posets Poset])) (def test-lattices [(make-lattice [1 2 3 4 5] <=), (concept-lattice (rand-context 10 10 0.7))]) +(def test-posets + [(make-poset [1 2 3 4] (fn [A B] (contains? #{[1 1] [2 2] [3 3] [4 4] + [1 3] [2 3] [1 4] [2 4]} [A B])))]) + (def test-layouts [(make-layout {1 [0 0], 2 [1 2], @@ -28,7 +36,15 @@ 2 [-1 1], 3 [1 1], 4 [0 2]} - #{[1 2] [1 3] [2 4] [3 4]})]) + #{[1 2] [1 3] [2 4] [3 4]}) + (make-layout {1 [0 0], + 2 [-1 1], + 3 [1 1]} + #{[1 2] [1 3]}) + (make-layout {2 [-1 1], + 3 [1 1], + 4 [0 2]} + #{[2 4] [3 4]})]) ;;; @@ -45,8 +61,8 @@ [0 0 100 100])) (is (= (connections layout) (connections scaled-layout))) - (is (= (lattice layout) - (lattice scaled-layout))) + (is (= (poset layout) + (poset scaled-layout))) (is (= (upper-labels layout) (upper-labels scaled-layout))) (is (= (lower-labels layout) @@ -55,9 +71,9 @@ ;;; (deftest test-layers - (with-testing-data [lattice test-lattices] - (let [<= (order lattice), - layers (layers lattice)] + (with-testing-data [poset (apply conj test-lattices test-posets)] + (let [<= (order poset), + layers (layers poset)] (every? (fn [[lower upper]] (forall [x lower] (exists [y upper] @@ -65,12 +81,12 @@ (partition 2 1 layers))))) (deftest test-edges - (with-testing-data [lattice test-lattices] - (let [edges (edges lattice)] - (forall [a (base-set lattice), - b (base-set lattice)] + (with-testing-data [poset (apply conj test-lattices test-posets)] + (let [edges (edges poset)] + (forall [a (base-set poset), + b (base-set poset)] (<=> (contains? edges [a b]) - (directly-neighboured? lattice a b)))))) + (directly-neighboured? poset a b)))))) (deftest test-top-down-elements-in-layout (with-testing-data [layout test-layouts] @@ -118,6 +134,28 @@ 2 #{2 3 4 5}, 1 #{1 2 3 4 5}}]))) +;;; + +(deftest test-layout-fn-on-poset + (let [poset-layouts (filter #(= Poset (type (poset %))) test-layouts)] + (with-testing-data [layout poset-layouts] + (let [new-layout (layout-fn-on-poset force-layout layout)] + (= (nodes layout) + (nodes new-layout)) + (= (connections layout) + (connections new-layout)))) + (with-testing-data [layout poset-layouts] + (let [new-layout (layout-fn-on-poset force-layout layout 10)] + (= (nodes layout) + (nodes new-layout)) + (= (connections layout) + (connections new-layout)))) + (with-testing-data [layout poset-layouts] + (let [new-layout (layout-fn-on-poset to-inf-additive-layout layout)] + (= (nodes layout) + (nodes new-layout)) + (= (connections layout) + (connections new-layout)))))) ;;; diff --git a/src/test/clojure/conexp/math/algebra_test.clj b/src/test/clojure/conexp/math/algebra_test.clj index 8067d7243..835a4e090 100644 --- a/src/test/clojure/conexp/math/algebra_test.clj +++ b/src/test/clojure/conexp/math/algebra_test.clj @@ -5,60 +5,12 @@ ;; 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.math.algebra-test (:use conexp.math.algebra) (:use clojure.test)) ;;; -(def adj0 #{}) - -(def adj1 #{['a 'a]['b 'b]['c 'c]['d 'd] - ['a 'c]['a 'd]['b 'd]}) - -(def adj2 #{['a 'c]['a 'd]['b 'd]}) - -(def adj3 #{[1 2][2 3][3 4][4 1] - [1 1][2 2][3 3][4 4]}) - -(def adj4 #{[1 2][2 3][3 4] - [1 'a][2 'b][3 'c][4 'd] - [1 1][2 2][3 3][4 4] - [1 3][1 4][2 4] - [1 'b][1 'c][1 'd] - [2 'c][2 'd][3 'd] - ['a 'a]['b 'b]['c 'c]['d 'd]}) - -(def fast-poset (fn [a] (make-poset (set (apply concat a)) - (fn [x y] (some #{[x y]} a))))) - -(deftest test-make-poset - (is (fast-poset adj0)) - (is (fast-poset adj1)) - (is (thrown? IllegalArgumentException - (fast-poset adj2))) - (is (thrown? IllegalArgumentException - (fast-poset adj3))) - (is (fast-poset adj4))) - -(deftest test-poset-to-matrix - (is (= [] - (poset-to-matrix (fast-poset adj0)))) - (is (= [1 0 1 1 ;a - 0 1 0 1 ;b - 0 0 1 0 ;c - 0 0 0 1] ;d - (poset-to-matrix (fast-poset adj1) - ['a 'b 'c 'd]))) - (is (= [1 1 1 1 1 1 1 1 ;1 - 0 1 1 1 0 1 1 1 ;2 - 0 0 1 1 0 0 1 1 ;3 - 0 0 0 1 0 0 0 1 ;4 - 0 0 0 0 1 0 0 0 ;a - 0 0 0 0 0 1 0 0 ;b - 0 0 0 0 0 0 1 0 ;c - 0 0 0 0 0 0 0 1] ;d - (poset-to-matrix (fast-poset adj4) - [1 2 3 4 'a 'b 'c 'd])))) + diff --git a/src/test/clojure/conexp/math/sampling_test.clj b/src/test/clojure/conexp/math/sampling_test.clj index 5da5b8927..323094eab 100644 --- a/src/test/clojure/conexp/math/sampling_test.clj +++ b/src/test/clojure/conexp/math/sampling_test.clj @@ -8,6 +8,7 @@ (ns conexp.math.sampling-test (:use conexp.math.algebra) + (:use conexp.fca.posets) (:use conexp.math.sampling) (:use clojure.test)) diff --git a/testing-data/digits-context.json b/testing-data/digits-context.json new file mode 100644 index 000000000..2c593cc76 --- /dev/null +++ b/testing-data/digits-context.json @@ -0,0 +1,12 @@ +{"attributes":["a","b","c","d","e","f","g"], +"adjacency-list":[ + {"object":"9","attributes":["d","f","a","b","g"]}, + {"object":"3","attributes":["f","a","b","g","c"]}, + {"object":"4","attributes":["d","f","b","g"]}, + {"object":"8","attributes":["d","f","e","a","b","g","c"]}, + {"object":"7","attributes":["f","a","g"]}, + {"object":"5","attributes":["d","a","b","g","c"]}, + {"object":"6","attributes":["d","e","b","g","c"]}, + {"object":"1","attributes":["f","g"]}, + {"object":"0","attributes":["d","f","e","a","g","c"]}, + {"object":"2","attributes":["f","e","a","b","c"]}]} diff --git a/testing-data/digits-context1.1.json b/testing-data/digits-context1.1.json new file mode 100644 index 000000000..2d2c00ff9 --- /dev/null +++ b/testing-data/digits-context1.1.json @@ -0,0 +1,13 @@ +{"objects": ["0","1","2","3","4","5","6","7","8","9"], + "attributes": ["a","b","c","d","e","f","g"], + "incidence": [["0","a"],["0","c"],["0","d"],["0","e"],["0","f"],["0","g"], + ["1","f"],["1","g"], + ["2","a"],["2","b"],["2","c"],["2","e"],["2","f"], + ["3","a"],["3","b"],["3","c"],["3","f"],["3","g"], + ["4","b"],["4","d"],["4","f"],["4","g"], + ["5","a"],["5","b"],["5","c"],["5","d"],["5","g"], + ["6","b"],["6","c"],["6","d"],["6","e"],["6","g"], + ["7","a"],["7","f"],["7","g"], + ["8","a"],["8","b"],["8","c"],["8","d"],["8","e"],["8","f"],["8","g"], + ["9","a"],["9","b"],["9","d"],["9","f"],["9","g"]] +} diff --git a/testing-data/digits-fca-2.json b/testing-data/digits-fca-2.json new file mode 100644 index 000000000..97c16a39a --- /dev/null +++ b/testing-data/digits-fca-2.json @@ -0,0 +1,591 @@ +{ + "context": { + "objects": ["0","1","2","3","4","5","6","7","8","9"], + "attributes": ["a","b","c","d","e","f","g"], + "incidence": [["0","a"],["0","c"],["0","d"],["0","e"],["0","f"],["0","g"], + ["1","f"],["1","g"], + ["2","a"],["2","b"],["2","c"],["2","e"],["2","f"], + ["3","a"],["3","b"],["3","c"],["3","f"],["3","g"], + ["4","b"],["4","d"],["4","f"],["4","g"], + ["5","a"],["5","b"],["5","c"],["5","d"],["5","g"], + ["6","b"],["6","c"],["6","d"],["6","e"],["6","g"], + ["7","a"],["7","f"],["7","g"], + ["8","a"],["8","b"],["8","c"],["8","d"],["8","e"],["8","f"],["8","g"], + ["9","a"],["9","b"],["9","d"],["9","f"],["9","g"]] + }, + "lattice": { + "nodes": [[["8","6","2"],["e","b","c"]], + [["9","4","8","5","6"],["d","b","g"]], + [["9","3","4","8","7","5","6","1","0","2"],[]], + [["8","5","6","0"],["d","g","c"]], + [["8","5","0"],["d","a","g","c"]], + [["3","8","2"],["f","a","b","c"]], + [["9","3","4","8"],["f","b","g"]], + [["9","3","8","2"],["f","a","b"]], + [["3","8","5","6"],["b","g","c"]], + [["3","8","0"],["f","a","g","c"]], + [["9","3","4","8","7","5","6","1","0"],["g"]], + [["3","8"],["f","a","b","g","c"]], + [["8","2"],["f","e","a","b","c"]], + [["9","8","5","0"],["d","a","g"]], + [["8","5","6"],["d","b","g","c"]], + [["9","8"],["d","f","a","b","g"]], + [["9","3","8"],["f","a","b","g"]], + [["8","5"],["d","a","b","g","c"]], + [["9","3","4","8","5","6","2"],["b"]], + [["9","3","4","8","7","1","0"],["f","g"]], + [["9","3","4","8","5","6"],["b","g"]], + [["3","8","5","0"],["a","g","c"]], + [["9","3","4","8","2"],["f","b"]], + [["8","0"],["d","f","e","a","g","c"]], + [["3","8","0","2"],["f","a","c"]], + [["3","8","5","6","0"],["g","c"]], + [["9","3","8","5","2"],["a","b"]], + [["9","8","5"],["d","a","b","g"]], + [["9","3","8","7","5","0"],["a","g"]], + [["9","3","4","8","7","1","0","2"],["f"]], + [["8","0","2"],["f","e","a","c"]], + [["8","6"],["d","e","b","g","c"]], + [["3","8","5","2"],["a","b","c"]], + [["9","3","8","7","0"],["f","a","g"]], + [["3","8","5","6","2"],["b","c"]], + [["9","3","8","5"],["a","b","g"]], + [["8","6","0","2"],["e","c"]], + [["9","8","0"],["d","f","a","g"]], + [["3","8","5"],["a","b","g","c"]], + [["3","8","5","6","0","2"],["c"]], + [["9","4","8","5","6","0"],["d","g"]], + [["9","4","8","0"],["d","f","g"]], + [["9","3","8","7","0","2"],["f","a"]], + [["9","4","8"],["d","f","b","g"]], + [["8","6","0"],["d","e","g","c"]], + [["8"],["d","f","e","a","b","g","c"]], + [["3","8","5","0","2"],["a","c"]], + [["9","3","8","7","5","0","2"],["a"]]], + "edges": [ + [[["8"],["d","f","e","a","b","g","c"]],[["8","5"],["d","a","b","g","c"]]], + [[["3","8","2"],["f","a","b","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["3","8","2"],["f","a","b","c"]],[["9","3","4","8","2"],["f","b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","8","5","0"],["d","a","g"]]], + [[["8","2"],["f","e","a","b","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","2"],["f","e","a","b","c"]],[["3","8","5","2"],["a","b","c"]]], + [[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","2"],["f","b"]]], + [[["8","6","0"],["d","e","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","4","8","0"],["d","f","g"]],[["9","4","8","0"],["d","f","g"]]], + [[["3","8","2"],["f","a","b","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["9","3","8","7","0","2"],["f","a"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["8","6"],["d","e","b","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8","0"],["f","a","g","c"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8","0","2"],["f","e","a","c"]],[["8","6","0","2"],["e","c"]]], + [[["8","6"],["d","e","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","6","0"],["d","e","g","c"]]], + [[["8","5","6","0"],["d","g","c"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["8","2"],["f","e","a","b","c"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["3","8","0","2"],["f","a","c"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","2"],["f","a","b","c"]],[["9","3","8","5","2"],["a","b"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["8","6","0","2"],["e","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","6"],["b","g","c"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["3","8","5","2"],["a","b","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","8","7","0"],["f","a","g"]]], + [[["3","8","5","0","2"],["a","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","5","2"],["a","b","c"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","3","8","5"],["a","b","g"]]], + [[["3","8","5","6","2"],["b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","8","2"],["f","a","b"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["3","8","5","6"],["b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8","6"],["d","e","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","0","2"],["f","e","a","c"]]], + [[["8","5","6"],["d","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","8","5","2"],["a","b"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","2"],["f","e","a","b","c"]],[["3","8","2"],["f","a","b","c"]]], + [[["3","8","5","2"],["a","b","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","5","6"],["d","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","8"],["f","a","b","g"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["3","8","5"],["a","b","g","c"]],[["9","3","8","5","2"],["a","b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","4","8","5","6"],["d","b","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","5"],["a","b","g","c"]]], + [[["9","3","8","7","0"],["f","a","g"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","5"],["a","b","g","c"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","8","5","0"],["d","a","g"]]], + [[["8","2"],["f","e","a","b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","6"],["d","e","b","g","c"]],[["9","4","8","5","6"],["d","b","g"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","8","0"],["d","f","a","g"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["8","5"],["d","a","b","g","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["9","3","4","8","5","6"],["b","g"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","6"],["d","e","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","0"],["d","f","e","a","g","c"]],[["8","0"],["d","f","e","a","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","8","0"],["d","f","a","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","6","0","2"],["e","c"]]], + [[["9","3","8","7","0"],["f","a","g"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["9","3","4","8","2"],["f","b"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","6","2"],["e","b","c"]],[["8","6","2"],["e","b","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8"],["d","f","e","a","b","g","c"]]], + [[["3","8","2"],["f","a","b","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","2"],["f","e","a","b","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["8","6","0"],["d","e","g","c"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","3","8","5","2"],["a","b"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","4","8","5","6"],["d","b","g"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","5","0","2"],["a","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5","6","2"],["b","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["3","8","5","6"],["b","g","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","8","5"],["d","a","b","g"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["3","8","5","0"],["a","g","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","8","2"],["f","a","b"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["3","8","5","0"],["a","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","4","8","5","6","0"],["d","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","4","8","0"],["d","f","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8","2"],["f","a","b","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["3","8","2"],["f","a","b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","8"],["f","a","b","g"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","8","5","0"],["d","a","g"]]], + [[["8","6","0","2"],["e","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["3","8","0"],["f","a","g","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["8","5","6"],["d","b","g","c"]],[["8","5","6","0"],["d","g","c"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","2"],["f","a","b","c"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["9","3","8","7","5","0"],["a","g"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["8","5","0"],["d","a","g","c"]],[["8","5","0"],["d","a","g","c"]]], + [[["8","2"],["f","e","a","b","c"]],[["9","3","8","5","2"],["a","b"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","3","8","7","0"],["f","a","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","8"],["d","f","a","b","g"]]], + [[["9","3","8","5"],["a","b","g"]],[["9","3","8","5","2"],["a","b"]]], + [[["9","3","8","5","2"],["a","b"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","5","6"],["d","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["3","8","0"],["f","a","g","c"]],[["3","8","5","0"],["a","g","c"]]], + [[["8","6","0","2"],["e","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5","0","2"],["a","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","3","8","7","5","0","2"],["a"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","4","8","7","1","0","2"],["f"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","6"],["d","e","b","g","c"]],[["8","5","6","0"],["d","g","c"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","3","8","7","0"],["f","a","g"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["9","3","8","2"],["f","a","b"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","8","2"],["f","a","b"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","6","2"],["e","b","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["3","8","5","2"],["a","b","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","3","4","8","2"],["f","b"]],[["9","3","4","8","2"],["f","b"]]], + [[["9","3","8","7","5","0"],["a","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8","5","0"],["a","g","c"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["3","8","2"],["f","a","b","c"]],[["9","3","8","2"],["f","a","b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["8","5"],["d","a","b","g","c"]],[["8","5"],["d","a","b","g","c"]]], + [[["9","3","8","7","5","0","2"],["a"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","3","4","8"],["f","b","g"]],[["9","3","4","8"],["f","b","g"]]], + [[["8","5","6"],["d","b","g","c"]],[["9","4","8","5","6"],["d","b","g"]]], + [[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["8","5","6","0"],["d","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","2"],["f","b"]]], + [[["8","6","2"],["e","b","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","0"],["a","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","6"],["d","e","b","g","c"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","8","5"],["a","b","g"]]], + [[["3","8","5","2"],["a","b","c"]],[["3","8","5","2"],["a","b","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","7","0"],["f","a","g"]]], + [[["8","5","6"],["d","b","g","c"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["9","8","5","0"],["d","a","g"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","8","5","0"],["d","a","g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","4","8"],["f","b","g"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8","5","0"],["d","a","g","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","2"],["f","e","a","b","c"]],[["3","8","0","2"],["f","a","c"]]], + [[["9","4","8","5","6"],["d","b","g"]],[["9","4","8","5","6"],["d","b","g"]]], + [[["8","0","2"],["f","e","a","c"]],[["3","8","0","2"],["f","a","c"]]], + [[["8","0","2"],["f","e","a","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5","6","2"],["b","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["8","5","0"],["d","a","g","c"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["9","3","4","8","2"],["f","b"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","4","8","5","6"],["b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8","5"],["a","b","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","8","2"],["f","a","b"]]], + [[["3","8","0"],["f","a","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["8","5"],["d","a","b","g","c"]],[["8","5","6","0"],["d","g","c"]]], + [[["8","5","0"],["d","a","g","c"]],[["3","8","5","0"],["a","g","c"]]], + [[["8","5","6"],["d","b","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","4","8"],["f","b","g"]]], + [[["3","8","5","2"],["a","b","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["3","8","5"],["a","b","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["8","5","6","0"],["d","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","8"],["d","f","a","b","g"]]], + [[["8","5"],["d","a","b","g","c"]],[["3","8","5"],["a","b","g","c"]]], + [[["3","8","0"],["f","a","g","c"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["8","2"],["f","e","a","b","c"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["9","3","8","2"],["f","a","b"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["8","5","6"],["d","b","g","c"]],[["3","8","5","6"],["b","g","c"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","5"],["a","b","g","c"]],[["3","8","5","2"],["a","b","c"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["8","6","0"],["d","e","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8","5","0"],["a","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8","5"],["a","b","g","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["3","8","5"],["a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","4","8"],["d","f","b","g"]]], + [[["3","8","0","2"],["f","a","c"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["3","8","0","2"],["f","a","c"]],[["3","8","0","2"],["f","a","c"]]], + [[["9","3","8","5"],["a","b","g"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","6"],["d","e","b","g","c"]],[["8","6","0","2"],["e","c"]]], + [[["9","4","8","0"],["d","f","g"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","4","8","5","6"],["d","b","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8","0","2"],["f","a","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["8","5","6"],["d","b","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["9","3","4","8","5","6","2"],["b"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","8","2"],["f","a","b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","2"],["f","e","a","b","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["3","8","5","0"],["a","g","c"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","4","8","0"],["d","f","g"]]], + [[["9","3","8","7","0"],["f","a","g"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","6","0","2"],["e","c"]],[["8","6","0","2"],["e","c"]]], + [[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","8","5"],["a","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5"],["a","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8","6"],["d","e","b","g","c"]],[["3","8","5","6"],["b","g","c"]]], + [[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","2"],["f","b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8"],["f","a","b","g","c"]]], + [[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","5"],["d","a","b","g","c"]],[["8","5","0"],["d","a","g","c"]]], + [[["3","8","5","6"],["b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","6","2"],["e","b","c"]],[["8","6","0","2"],["e","c"]]], + [[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","5","6","2"],["b","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["3","8","5","6","0"],["g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","8","5"],["a","b","g"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","5","6"],["b","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","3","8","7","5","0"],["a","g"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","6","0"],["d","e","g","c"]],[["8","6","0"],["d","e","g","c"]]], + [[["8","6"],["d","e","b","g","c"]],[["8","5","6"],["d","b","g","c"]]], + [[["8","2"],["f","e","a","b","c"]],[["8","0","2"],["f","e","a","c"]]], + [[["9","3","4","8","5","6"],["b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","4","8","0"],["d","f","g"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","4","8","5","6"],["d","b","g"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["9","3","4","8","7","5","6","1","0"],["g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","0"],["f","a","g","c"]],[["9","3","8","7","0"],["f","a","g"]]], + [[["8","2"],["f","e","a","b","c"]],[["8","6","0","2"],["e","c"]]], + [[["9","3","4","8","7","5","6","1","0","2"],[]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","6","0"],["d","e","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["8","6"],["d","e","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","2"],["f","b"]]], + [[["8","5","6"],["d","b","g","c"]],[["8","5","6"],["d","b","g","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["9","3","8","7","0"],["f","a","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["9","3","8","2"],["f","a","b"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","5"],["a","b","g","c"]],[["9","3","8","5"],["a","b","g"]]], + [[["8","2"],["f","e","a","b","c"]],[["9","3","8","2"],["f","a","b"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","0","2"],["f","e","a","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","2"],["f","a","b","c"]],[["3","8","5","2"],["a","b","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","6","2"],["e","b","c"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","8","5","0"],["d","a","g"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8","5"],["d","a","b","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8","5","6","0","2"],["c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","4","8","5","6","0"],["d","g"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","5","0"],["d","a","g","c"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["8","5","0"],["d","a","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["9","8","5","0"],["d","a","g"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["9","3","8","7","0","2"],["f","a"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5","0","2"],["a","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["3","8","5","6"],["b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["3","8","0","2"],["f","a","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["3","8","5"],["a","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["9","3","4","8","7","1","0","2"],["f"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","5","6"],["b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","8"],["f","a","b","g"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","4","8","0"],["d","f","g"]]], + [[["9","4","8","5","6"],["d","b","g"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["3","8","0"],["f","a","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","5","0"],["d","a","g","c"]]], + [[["9","3","8","7","0"],["f","a","g"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["3","8","2"],["f","a","b","c"]],[["3","8","2"],["f","a","b","c"]]], + [[["8","6"],["d","e","b","g","c"]],[["8","6"],["d","e","b","g","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","3","8","5"],["a","b","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","2"],["a","b","c"]]], + [[["3","8","5","2"],["a","b","c"]],[["9","3","8","5","2"],["a","b"]]], + [[["9","8","5","0"],["d","a","g"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","6","0"],["d","e","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","2"],["f","e","a","b","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","0","2"],["f","e","a","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","5","0"],["a","g","c"]]], + [[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["9","4","8","0"],["d","f","g"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["9","3","8","5"],["a","b","g"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["3","8","5","0"],["a","g","c"]],[["3","8","5","0"],["a","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","2"],["f","a","b"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","8","7","0"],["f","a","g"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","3","4","8"],["f","b","g"]]], + [[["3","8","5","6","0"],["g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","8","5"],["d","a","b","g"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8","5"],["d","a","b","g","c"]],[["3","8","5","6"],["b","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8"],["f","b","g"]]], + [[["3","8","5","2"],["a","b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8","5","6"],["b","g","c"]],[["3","8","5","6"],["b","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["3","8","5","6","0"],["g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8","6"],["d","e","b","g","c"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["8","5","6","0"],["d","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","0"],["f","a","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","2"],["f","a","b","c"]]], + [[["9","3","8","5"],["a","b","g"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8","5","0"],["d","a","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["3","8","5","6","0","2"],["c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","8","5"],["a","b","g"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["9","8","5","0"],["d","a","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","8","5","2"],["a","b"]]], + [[["3","8","0","2"],["f","a","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","4","8","5","6","2"],["b"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["9","3","8","7","0","2"],["f","a"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","4","8"],["d","f","b","g"]]], + [[["9","8","5","0"],["d","a","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","8","2"],["f","a","b"]],[["9","3","8","5","2"],["a","b"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8"],["f","a","b","g","c"]]], + [[["9","3","8","5"],["a","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","2"],["f","a","b","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["8","5","6"],["d","b","g","c"]]], + [[["3","8","0"],["f","a","g","c"]],[["3","8","0","2"],["f","a","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","6"],["d","e","b","g","c"]],[["8","6","2"],["e","b","c"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["8","6","0"],["d","e","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","5","6","0"],["d","g","c"]]], + [[["3","8","2"],["f","a","b","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["3","8","5","2"],["a","b","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["8","6","2"],["e","b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5"],["a","b","g","c"]],[["3","8","5"],["a","b","g","c"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","8"],["f","a","b","g"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","8","7","0"],["f","a","g"]]], + [[["8","6"],["d","e","b","g","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","5"],["a","b","g"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","5","6"],["d","b","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","0","2"],["f","a","c"]]], + [[["8","6","2"],["e","b","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["9","3","4","8","5","6"],["b","g"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","0"],["f","a","g","c"]]], + [[["8","0","2"],["f","e","a","c"]],[["8","0","2"],["f","e","a","c"]]], + [[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["9","3","8","2"],["f","a","b"]],[["9","3","4","8","2"],["f","b"]]], + [[["8","5","0"],["d","a","g","c"]],[["8","5","6","0"],["d","g","c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["3","8","5","0"],["a","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","8","0"],["d","f","a","g"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["8","2"],["f","e","a","b","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","8","5"],["a","b","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","2"],["f","b"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["3","8","2"],["f","a","b","c"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["9","4","8","5","6","0"],["d","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","8"],["d","f","a","b","g"]],[["9","8","5","0"],["d","a","g"]]], + [[["8","6"],["d","e","b","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["9","3","4","8","7","5","6","1","0"],["g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8","5"],["a","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8","5"],["d","a","b","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["8","0"],["d","f","e","a","g","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","4","8"],["f","b","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","4","8","0"],["d","f","g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","8","5","2"],["a","b"]]], + [[["9","3","8","5","2"],["a","b"]],[["9","3","8","5","2"],["a","b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","5","2"],["a","b"]]], + [[["8","6","0"],["d","e","g","c"]],[["8","5","6","0"],["d","g","c"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","4","8","0"],["d","f","g"]]], + [[["8","5","6","0"],["d","g","c"]],[["8","5","6","0"],["d","g","c"]]], + [[["9","3","8","7","5","0"],["a","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","8","5"],["d","a","b","g"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","0","2"],["f","e","a","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","5","6"],["b","g","c"]]], + [[["3","8","0","2"],["f","a","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","3","8","5","2"],["a","b"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["9","4","8","0"],["d","f","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8","2"],["f","e","a","b","c"]],[["8","2"],["f","e","a","b","c"]]], + [[["3","8","5"],["a","b","g","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["3","8","0"],["f","a","g","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","0"],["d","f","e","a","g","c"]],[["8","5","0"],["d","a","g","c"]]], + [[["8","2"],["f","e","a","b","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["9","3","8","7","0","2"],["f","a"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["8","0","2"],["f","e","a","c"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["8","5","6","0"],["d","g","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["3","8","5","2"],["a","b","c"]]], + [[["8","5","0"],["d","a","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5","6"],["b","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","4","8","7","1","0"],["f","g"]]], + [[["8","5","0"],["d","a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5"],["a","b","g","c"]],[["3","8","5","0"],["a","g","c"]]], + [[["8","6"],["d","e","b","g","c"]],[["8","6","0"],["d","e","g","c"]]], + [[["9","4","8","0"],["d","f","g"]],[["9","4","8","5","6","0"],["d","g"]]], + [[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","6","0"],["d","e","g","c"]],[["8","6","0","2"],["e","c"]]], + [[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","8","0"],["d","f","a","g"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","3","4","8","2"],["f","b"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["8","5","6"],["d","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["3","8","5"],["a","b","g","c"]],[["3","8","5","6"],["b","g","c"]]], + [[["9","3","8","5"],["a","b","g"]],[["9","3","8","5"],["a","b","g"]]], + [[["8","5","0"],["d","a","g","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","2"],["f","e","a","b","c"]],[["8","6","2"],["e","b","c"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","4","8","5","6"],["d","b","g"]]], + [[["8","5","6","0"],["d","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5"],["a","b","g","c"]],[["9","3","8","7","5","0"],["a","g"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["8","0","2"],["f","e","a","c"]]], + [[["9","3","8","7","0"],["f","a","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["3","8","5"],["a","b","g","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["8","5","0"],["d","a","g","c"]],[["9","8","5","0"],["d","a","g"]]], + [[["3","8","5","6","0"],["g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]], + [[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","3","8","5","2"],["a","b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","8","5"],["d","a","b","g"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8","5","0"],["a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["9","3","8","2"],["f","a","b"]],[["9","3","8","2"],["f","a","b"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["8","2"],["f","e","a","b","c"]],[["9","3","4","8","2"],["f","b"]]], + [[["9","8"],["d","f","a","b","g"]],[["9","4","8"],["d","f","b","g"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","8","5"],["d","a","b","g"]]], + [[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["3","8","5","0"],["a","g","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["8","5","6"],["d","b","g","c"]],[["3","8","5","6","2"],["b","c"]]], + [[["3","8","0"],["f","a","g","c"]],[["3","8","5","6","0"],["g","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]], + [[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","7","5","0","2"],["a"]]], + [[["9","8","0"],["d","f","a","g"]],[["9","8","0"],["d","f","a","g"]]], + [[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","2"],["f","b"]]], + [[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]], + [[["8","0","2"],["f","e","a","c"]],[["9","3","8","7","0","2"],["f","a"]]], + [[["9","8","5"],["d","a","b","g"]],[["9","3","8","5","2"],["a","b"]]], + [[["9","8","5","0"],["d","a","g"]],[["9","8","5","0"],["d","a","g"]]], + [[["3","8","2"],["f","a","b","c"]],[["3","8","0","2"],["f","a","c"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["3","8","5","6","0","2"],["c"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]], + [[["3","8"],["f","a","b","g","c"]],[["3","8","0","2"],["f","a","c"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["9","8","0"],["d","f","a","g"]]], + [[["3","8","0"],["f","a","g","c"]],[["3","8","0"],["f","a","g","c"]]], + [[["3","8","5","0"],["a","g","c"]],[["3","8","5","0","2"],["a","c"]]], + [[["8","0"],["d","f","e","a","g","c"]],[["3","8","0","2"],["f","a","c"]]], + [[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","7","1","0","2"],["f"]]], + [[["9","3","8","7","0"],["f","a","g"]],[["9","3","8","7","0"],["f","a","g"]]]]}, + "implication_sets": [ + { + "implications": [ + { + "premise": [ + "e" + ], + "conclusion": [ + "c" + ] + }, + { + "premise": [ + "e", + "g", + "c" + ], + "conclusion": [ + "d" + ] + }, + { + "premise": [ + "e", + "a", + "c" + ], + "conclusion": [ + "f" + ] + }, + { + "premise": [ + "f", + "c" + ], + "conclusion": [ + "a" + ] + }, + { + "premise": [ + "d" + ], + "conclusion": [ + "g" + ] + }, + { + "premise": [ + "d", + "f", + "a", + "g", + "c" + ], + "conclusion": [ + "e" + ] + } + ] + } + ] +} diff --git a/testing-data/digits-fca.json b/testing-data/digits-fca.json new file mode 100644 index 000000000..f55dfa826 --- /dev/null +++ b/testing-data/digits-fca.json @@ -0,0 +1,14318 @@ +{ + "context": { + "attributes": ["a","b","c","d","e","f","g"], + "adjacency-list": [ + { + "object": "9", + "attributes": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + { + "object": "3", + "attributes": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + { + "object": "4", + "attributes": [ + "d", + "f", + "b", + "g" + ] + }, + { + "object": "8", + "attributes": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + { + "object": "7", + "attributes": [ + "f", + "a", + "g" + ] + }, + { + "object": "5", + "attributes": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + { + "object": "6", + "attributes": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + { + "object": "1", + "attributes": [ + "f", + "g" + ] + }, + { + "object": "0", + "attributes": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + { + "object": "2", + "attributes": [ + "f", + "e", + "a", + "b", + "c" + ] + } + ] + }, + "lattice": { + "formal_concepts": [ + { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + }, + { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + }, + { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + }, + { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + }, + { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + }, + { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + }, + { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + }, + { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + }, + { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + }, + { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + }, + { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + }, + { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + }, + { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + }, + { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + }, + { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + }, + { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + }, + { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + }, + { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + }, + { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + }, + { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + }, + { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + }, + { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + }, + { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + ], + "lattice_structure": [ + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6" + ], + "intent": [ + "d", + "e", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8", + "0" + ], + "intent": [ + "d", + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "6", + "0" + ], + "intent": [ + "d", + "e", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "0", + "2" + ], + "intent": [ + "e", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "6", + "2" + ], + "intent": [ + "e", + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6", + "0" + ], + "intent": [ + "d", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0" + ], + "intent": [ + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5" + ], + "intent": [ + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0", + "2" + ], + "intent": [] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8" + ], + "intent": [ + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "2" + ], + "intent": [ + "f", + "e", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8" + ], + "intent": [ + "d", + "f", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0" + ], + "intent": [ + "f", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5", + "6" + ], + "intent": [ + "d", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0" + ], + "intent": [ + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "5", + "6", + "1", + "0" + ], + "intent": [ + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "8" + ], + "intent": [ + "d", + "f", + "e", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "5", + "0", + "2" + ], + "intent": [ + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "4", + "8" + ], + "intent": [ + "d", + "f", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "2" + ], + "intent": [ + "f", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6", + "2" + ], + "intent": [ + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0", + "2" + ], + "intent": [ + "f", + "e", + "a", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0", + "2" + ], + "intent": [ + "f", + "a" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5" + ], + "intent": [ + "d", + "a", + "b", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "5", + "2" + ], + "intent": [ + "a", + "b" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "5", + "0" + ], + "intent": [ + "d", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "2" + ], + "intent": [ + "f", + "a", + "b", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "6", + "0", + "2" + ], + "intent": [ + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "5", + "6" + ], + "intent": [ + "b", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "8", + "0" + ], + "intent": [ + "d", + "f", + "a", + "g" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0" + ], + "intent": [ + "f", + "a", + "g", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8", + "5", + "0" + ], + "intent": [ + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "5", + "0", + "2" + ], + "intent": [ + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "8", + "0" + ], + "intent": [ + "d", + "f", + "e", + "a", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "3", + "8", + "0", + "2" + ], + "intent": [ + "f", + "a", + "c" + ] + } + }, + { + "start_concept": { + "extent": [ + "3", + "8" + ], + "intent": [ + "f", + "a", + "b", + "g", + "c" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "4", + "8", + "7", + "1", + "0", + "2" + ], + "intent": [ + "f" + ] + } + }, + { + "start_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + }, + "end_concept": { + "extent": [ + "9", + "3", + "8", + "7", + "0" + ], + "intent": [ + "f", + "a", + "g" + ] + } + } + ] + }, + "implication_sets": [ + { + "implications": [ + { + "premise": [ + "e" + ], + "conclusion": [ + "c" + ] + }, + { + "premise": [ + "e", + "g", + "c" + ], + "conclusion": [ + "d" + ] + }, + { + "premise": [ + "e", + "a", + "c" + ], + "conclusion": [ + "f" + ] + }, + { + "premise": [ + "f", + "c" + ], + "conclusion": [ + "a" + ] + }, + { + "premise": [ + "d" + ], + "conclusion": [ + "g" + ] + }, + { + "premise": [ + "d", + "f", + "a", + "g", + "c" + ], + "conclusion": [ + "e" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/testing-data/digits-implication.json b/testing-data/digits-implication.json new file mode 100644 index 000000000..1f349e13f --- /dev/null +++ b/testing-data/digits-implication.json @@ -0,0 +1 @@ +{"implications":[{"premise":["e"],"conclusion":["c"]},{"premise":["e","g","c"],"conclusion":["d"]},{"premise":["e","a","c"],"conclusion":["f"]},{"premise":["f","c"],"conclusion":["a"]},{"premise":["d"],"conclusion":["g"]},{"premise":["d","f","a","g","c"],"conclusion":["e"]}]} \ No newline at end of file diff --git a/testing-data/digits-lattice.json b/testing-data/digits-lattice.json new file mode 100644 index 000000000..3f590a10c --- /dev/null +++ b/testing-data/digits-lattice.json @@ -0,0 +1 @@ +{"formal_concepts":[{"extent":["8","6","2"],"intent":["e","b","c"]},{"extent":["9","4","8","5","6"],"intent":["d","b","g"]},{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]},{"extent":["8","5","6","0"],"intent":["d","g","c"]},{"extent":["8","5","0"],"intent":["d","a","g","c"]},{"extent":["3","8","2"],"intent":["f","a","b","c"]},{"extent":["9","3","4","8"],"intent":["f","b","g"]},{"extent":["9","3","8","2"],"intent":["f","a","b"]},{"extent":["3","8","5","6"],"intent":["b","g","c"]},{"extent":["3","8","0"],"intent":["f","a","g","c"]},{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]},{"extent":["3","8"],"intent":["f","a","b","g","c"]},{"extent":["8","2"],"intent":["f","e","a","b","c"]},{"extent":["9","8","5","0"],"intent":["d","a","g"]},{"extent":["8","5","6"],"intent":["d","b","g","c"]},{"extent":["9","8"],"intent":["d","f","a","b","g"]},{"extent":["9","3","8"],"intent":["f","a","b","g"]},{"extent":["8","5"],"intent":["d","a","b","g","c"]},{"extent":["9","3","4","8","5","6","2"],"intent":["b"]},{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]},{"extent":["9","3","4","8","5","6"],"intent":["b","g"]},{"extent":["3","8","5","0"],"intent":["a","g","c"]},{"extent":["9","3","4","8","2"],"intent":["f","b"]},{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},{"extent":["3","8","0","2"],"intent":["f","a","c"]},{"extent":["3","8","5","6","0"],"intent":["g","c"]},{"extent":["9","3","8","5","2"],"intent":["a","b"]},{"extent":["9","8","5"],"intent":["d","a","b","g"]},{"extent":["9","3","8","7","5","0"],"intent":["a","g"]},{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]},{"extent":["8","0","2"],"intent":["f","e","a","c"]},{"extent":["8","6"],"intent":["d","e","b","g","c"]},{"extent":["3","8","5","2"],"intent":["a","b","c"]},{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},{"extent":["3","8","5","6","2"],"intent":["b","c"]},{"extent":["9","3","8","5"],"intent":["a","b","g"]},{"extent":["8","6","0","2"],"intent":["e","c"]},{"extent":["9","8","0"],"intent":["d","f","a","g"]},{"extent":["3","8","5"],"intent":["a","b","g","c"]},{"extent":["3","8","5","6","0","2"],"intent":["c"]},{"extent":["9","4","8","5","6","0"],"intent":["d","g"]},{"extent":["9","4","8","0"],"intent":["d","f","g"]},{"extent":["9","3","8","7","0","2"],"intent":["f","a"]},{"extent":["9","4","8"],"intent":["d","f","b","g"]},{"extent":["8","6","0"],"intent":["d","e","g","c"]},{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},{"extent":["3","8","5","0","2"],"intent":["a","c"]},{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}],"lattice_structure":[{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]}},{"start_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]},"end_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["8","6","0","2"],"intent":["e","c"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]}},{"start_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["8","6","0","2"],"intent":["e","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]}},{"start_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]}},{"start_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]}},{"start_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]}},{"start_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","6","0","2"],"intent":["e","c"]}},{"start_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","6","2"],"intent":["e","b","c"]},"end_concept":{"extent":["8","6","2"],"intent":["e","b","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]}},{"start_concept":{"extent":["8","6","0","2"],"intent":["e","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]}},{"start_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]}},{"start_concept":{"extent":["8","6","0","2"],"intent":["e","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","6","2"],"intent":["e","b","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]}},{"start_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]},"end_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]}},{"start_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["8","6","2"],"intent":["e","b","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]}},{"start_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]},"end_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]}},{"start_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]},"end_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]}},{"start_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]}},{"start_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]},"end_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]}},{"start_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["8","6","0","2"],"intent":["e","c"]}},{"start_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]}},{"start_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","6","0","2"],"intent":["e","c"]},"end_concept":{"extent":["8","6","0","2"],"intent":["e","c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]}},{"start_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","6","2"],"intent":["e","b","c"]},"end_concept":{"extent":["8","6","0","2"],"intent":["e","c"]}},{"start_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]},"end_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]}},{"start_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["8","6","0","2"],"intent":["e","c"]}},{"start_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","6","2"],"intent":["e","b","c"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]}},{"start_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]}},{"start_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]}},{"start_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]}},{"start_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]},"end_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]}},{"start_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]}},{"start_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]},"end_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]}},{"start_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]}},{"start_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]}},{"start_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["8","6","2"],"intent":["e","b","c"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["8","6","2"],"intent":["e","b","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]}},{"start_concept":{"extent":["8","6","2"],"intent":["e","b","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]}},{"start_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]},"end_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]}},{"start_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]},"end_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]}},{"start_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]}},{"start_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["3","8","5","2"],"intent":["a","b","c"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]}},{"start_concept":{"extent":["8","6"],"intent":["d","e","b","g","c"]},"end_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]}},{"start_concept":{"extent":["9","4","8","0"],"intent":["d","f","g"]},"end_concept":{"extent":["9","4","8","5","6","0"],"intent":["d","g"]}},{"start_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","6","0"],"intent":["d","e","g","c"]},"end_concept":{"extent":["8","6","0","2"],"intent":["e","c"]}},{"start_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["3","8","5","6"],"intent":["b","g","c"]}},{"start_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]},"end_concept":{"extent":["9","3","8","5"],"intent":["a","b","g"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["8","6","2"],"intent":["e","b","c"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","4","8","5","6"],"intent":["d","b","g"]}},{"start_concept":{"extent":["8","5","6","0"],"intent":["d","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","3","8"],"intent":["f","a","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0"],"intent":["a","g"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]}},{"start_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["3","8","5"],"intent":["a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["8","5","0"],"intent":["d","a","g","c"]},"end_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]}},{"start_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0","2"],"intent":[]}},{"start_concept":{"extent":["9","3","4","8"],"intent":["f","b","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]},"end_concept":{"extent":["9","3","8","2"],"intent":["f","a","b"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["8","2"],"intent":["f","e","a","b","c"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["9","8"],"intent":["d","f","a","b","g"]},"end_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]}},{"start_concept":{"extent":["9","3","4","8","7","1","0"],"intent":["f","g"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["8","5","6"],"intent":["d","b","g","c"]},"end_concept":{"extent":["3","8","5","6","2"],"intent":["b","c"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["3","8","5","6","0"],"intent":["g","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","5","6","1","0"],"intent":["g"]}},{"start_concept":{"extent":["8"],"intent":["d","f","e","a","b","g","c"]},"end_concept":{"extent":["9","3","8","7","5","0","2"],"intent":["a"]}},{"start_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]},"end_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]}},{"start_concept":{"extent":["9","4","8"],"intent":["d","f","b","g"]},"end_concept":{"extent":["9","3","4","8","2"],"intent":["f","b"]}},{"start_concept":{"extent":["8","5"],"intent":["d","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6","2"],"intent":["b"]}},{"start_concept":{"extent":["8","0","2"],"intent":["f","e","a","c"]},"end_concept":{"extent":["9","3","8","7","0","2"],"intent":["f","a"]}},{"start_concept":{"extent":["9","8","5"],"intent":["d","a","b","g"]},"end_concept":{"extent":["9","3","8","5","2"],"intent":["a","b"]}},{"start_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]},"end_concept":{"extent":["9","8","5","0"],"intent":["d","a","g"]}},{"start_concept":{"extent":["3","8","2"],"intent":["f","a","b","c"]},"end_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["3","8","5","6","0","2"],"intent":["c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","5","6"],"intent":["b","g"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["9","8","0"],"intent":["d","f","a","g"]}},{"start_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]},"end_concept":{"extent":["3","8","0"],"intent":["f","a","g","c"]}},{"start_concept":{"extent":["3","8","5","0"],"intent":["a","g","c"]},"end_concept":{"extent":["3","8","5","0","2"],"intent":["a","c"]}},{"start_concept":{"extent":["8","0"],"intent":["d","f","e","a","g","c"]},"end_concept":{"extent":["3","8","0","2"],"intent":["f","a","c"]}},{"start_concept":{"extent":["3","8"],"intent":["f","a","b","g","c"]},"end_concept":{"extent":["9","3","4","8","7","1","0","2"],"intent":["f"]}},{"start_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]},"end_concept":{"extent":["9","3","8","7","0"],"intent":["f","a","g"]}}]} \ No newline at end of file diff --git a/testing-data/digits-lattice1.1.json b/testing-data/digits-lattice1.1.json new file mode 100644 index 000000000..488809ce7 --- /dev/null +++ b/testing-data/digits-lattice1.1.json @@ -0,0 +1 @@ +{"nodes":[[["8","6","2"],["e","b","c"]],[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]],[["8","5","6","0"],["d","g","c"]],[["8","5","0"],["d","a","g","c"]],[["3","8","2"],["f","a","b","c"]],[["9","3","4","8"],["f","b","g"]],[["9","3","8","2"],["f","a","b"]],[["3","8","5","6"],["b","g","c"]],[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]],[["3","8"],["f","a","b","g","c"]],[["8","2"],["f","e","a","b","c"]],[["9","8","5","0"],["d","a","g"]],[["8","5","6"],["d","b","g","c"]],[["9","8"],["d","f","a","b","g"]],[["9","3","8"],["f","a","b","g"]],[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]],[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","5","6"],["b","g"]],[["3","8","5","0"],["a","g","c"]],[["9","3","4","8","2"],["f","b"]],[["8","0"],["d","f","e","a","g","c"]],[["3","8","0","2"],["f","a","c"]],[["3","8","5","6","0"],["g","c"]],[["9","3","8","5","2"],["a","b"]],[["9","8","5"],["d","a","b","g"]],[["9","3","8","7","5","0"],["a","g"]],[["9","3","4","8","7","1","0","2"],["f"]],[["8","0","2"],["f","e","a","c"]],[["8","6"],["d","e","b","g","c"]],[["3","8","5","2"],["a","b","c"]],[["9","3","8","7","0"],["f","a","g"]],[["3","8","5","6","2"],["b","c"]],[["9","3","8","5"],["a","b","g"]],[["8","6","0","2"],["e","c"]],[["9","8","0"],["d","f","a","g"]],[["3","8","5"],["a","b","g","c"]],[["3","8","5","6","0","2"],["c"]],[["9","4","8","5","6","0"],["d","g"]],[["9","4","8","0"],["d","f","g"]],[["9","3","8","7","0","2"],["f","a"]],[["9","4","8"],["d","f","b","g"]],[["8","6","0"],["d","e","g","c"]],[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","0","2"],["a","c"]],[["9","3","8","7","5","0","2"],["a"]]],"edges":[[[["8"],["d","f","e","a","b","g","c"]],[["8","5"],["d","a","b","g","c"]]],[[["3","8","2"],["f","a","b","c"]],[["3","8","5","0","2"],["a","c"]]],[[["3","8","2"],["f","a","b","c"]],[["9","3","4","8","2"],["f","b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","8","5","0"],["d","a","g"]]],[[["8","2"],["f","e","a","b","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","2"],["f","e","a","b","c"]],[["3","8","5","2"],["a","b","c"]]],[[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","2"],["f","b"]]],[[["8","6","0"],["d","e","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","4","8","0"],["d","f","g"]],[["9","4","8","0"],["d","f","g"]]],[[["3","8","2"],["f","a","b","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["9","3","8","7","0","2"],["f","a"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","8","7","0","2"],["f","a"]]],[[["8","6"],["d","e","b","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8","0"],["f","a","g","c"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8","0","2"],["f","e","a","c"]],[["8","6","0","2"],["e","c"]]],[[["8","6"],["d","e","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","6","0"],["d","e","g","c"]]],[[["8","5","6","0"],["d","g","c"]],[["9","4","8","5","6","0"],["d","g"]]],[[["8","2"],["f","e","a","b","c"]],[["9","3","8","7","0","2"],["f","a"]]],[[["3","8","0","2"],["f","a","c"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","2"],["f","a","b","c"]],[["9","3","8","5","2"],["a","b"]]],[[["8","0"],["d","f","e","a","g","c"]],[["8","6","0","2"],["e","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","6"],["b","g","c"]]],[[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["3","8","5","2"],["a","b","c"]],[["3","8","5","6","0","2"],["c"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","8","7","0"],["f","a","g"]]],[[["3","8","5","0","2"],["a","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","5","2"],["a","b","c"]]],[[["9","8","5"],["d","a","b","g"]],[["9","3","8","5"],["a","b","g"]]],[[["3","8","5","6","2"],["b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","8","2"],["f","a","b"]],[["9","3","8","7","5","0","2"],["a"]]],[[["3","8","5","6"],["b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8","6"],["d","e","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","0","2"],["f","e","a","c"]]],[[["8","5","6"],["d","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","8","5","2"],["a","b"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","2"],["f","e","a","b","c"]],[["3","8","2"],["f","a","b","c"]]],[[["3","8","5","2"],["a","b","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","5","6"],["d","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","8"],["f","a","b","g"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","5","6"],["b","g"]]],[[["3","8","5"],["a","b","g","c"]],[["9","3","8","5","2"],["a","b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","4","8","5","6"],["d","b","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","5"],["a","b","g","c"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","5"],["a","b","g","c"]]],[[["9","8","0"],["d","f","a","g"]],[["9","8","5","0"],["d","a","g"]]],[[["8","2"],["f","e","a","b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","6"],["d","e","b","g","c"]],[["9","4","8","5","6"],["d","b","g"]]],[[["9","8"],["d","f","a","b","g"]],[["9","8","0"],["d","f","a","g"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","3","8","7","0","2"],["f","a"]]],[[["8","5"],["d","a","b","g","c"]],[["3","8","5","0","2"],["a","c"]]],[[["9","3","4","8","5","6"],["b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","6"],["d","e","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","0"],["d","f","e","a","g","c"]],[["8","0"],["d","f","e","a","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","8","0"],["d","f","a","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","6","0","2"],["e","c"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","8","7","0","2"],["f","a"]]],[[["9","3","4","8","2"],["f","b"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","6","2"],["e","b","c"]],[["8","6","2"],["e","b","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8"],["d","f","e","a","b","g","c"]]],[[["3","8","2"],["f","a","b","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","2"],["f","e","a","b","c"]],[["3","8","5","6","2"],["b","c"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","5","6"],["b","g"]]],[[["8","6","0"],["d","e","g","c"]],[["9","4","8","5","6","0"],["d","g"]]],[[["8","5"],["d","a","b","g","c"]],[["9","3","8","5","2"],["a","b"]]],[[["9","8","5"],["d","a","b","g"]],[["9","4","8","5","6"],["d","b","g"]]],[[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","5","0","2"],["a","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5","6","2"],["b","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["3","8","5","6"],["b","g","c"]],[["3","8","5","6","2"],["b","c"]]],[[["8","5"],["d","a","b","g","c"]],[["9","8","5"],["d","a","b","g"]]],[[["8","0"],["d","f","e","a","g","c"]],[["3","8","5","0"],["a","g","c"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","8","2"],["f","a","b"]]],[[["9","8","5"],["d","a","b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["3","8","5","0"],["a","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","4","8","5","6","0"],["d","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","4","8","0"],["d","f","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8","2"],["f","a","b","c"]],[["3","8","5","6","2"],["b","c"]]],[[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["3","8","2"],["f","a","b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","8"],["f","a","b","g"]]],[[["8","5"],["d","a","b","g","c"]],[["9","8","5","0"],["d","a","g"]]],[[["8","6","0","2"],["e","c"]],[["3","8","5","6","0","2"],["c"]]],[[["3","8","0"],["f","a","g","c"]],[["3","8","5","0","2"],["a","c"]]],[[["8","5","6"],["d","b","g","c"]],[["8","5","6","0"],["d","g","c"]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","2"],["f","a","b","c"]],[["9","3","8","7","0","2"],["f","a"]]],[[["9","3","8","7","5","0"],["a","g"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["8","5","0"],["d","a","g","c"]],[["8","5","0"],["d","a","g","c"]]],[[["8","2"],["f","e","a","b","c"]],[["9","3","8","5","2"],["a","b"]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","3","8","7","0"],["f","a","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","8"],["d","f","a","b","g"]]],[[["9","3","8","5"],["a","b","g"]],[["9","3","8","5","2"],["a","b"]]],[[["9","3","8","5","2"],["a","b"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","5","6"],["d","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","8","7","5","0"],["a","g"]]],[[["3","8","0"],["f","a","g","c"]],[["3","8","5","0"],["a","g","c"]]],[[["8","6","0","2"],["e","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5","0","2"],["a","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","3","8","7","5","0","2"],["a"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","4","8","7","1","0","2"],["f"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","6"],["d","e","b","g","c"]],[["8","5","6","0"],["d","g","c"]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","8","7","0"],["f","a","g"]]],[[["9","4","8"],["d","f","b","g"]],[["9","4","8","5","6","0"],["d","g"]]],[[["9","3","8","2"],["f","a","b"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","8","2"],["f","a","b"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","6","2"],["e","b","c"]],[["3","8","5","6","0","2"],["c"]]],[[["3","8","5","2"],["a","b","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","3","4","8","2"],["f","b"]],[["9","3","4","8","2"],["f","b"]]],[[["9","3","8","7","5","0"],["a","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8","5","0"],["a","g","c"]],[["9","3","8","7","5","0"],["a","g"]]],[[["3","8","2"],["f","a","b","c"]],[["9","3","8","2"],["f","a","b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","7","0","2"],["f","a"]]],[[["8","5"],["d","a","b","g","c"]],[["8","5"],["d","a","b","g","c"]]],[[["9","3","8","7","5","0","2"],["a"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","3","4","8"],["f","b","g"]],[["9","3","4","8"],["f","b","g"]]],[[["8","5","6"],["d","b","g","c"]],[["9","4","8","5","6"],["d","b","g"]]],[[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","5","6"],["b","g"]]],[[["8","5","6","0"],["d","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","2"],["f","b"]]],[[["8","6","2"],["e","b","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","0"],["a","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","6"],["d","e","b","g","c"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","8","5"],["a","b","g"]]],[[["3","8","5","2"],["a","b","c"]],[["3","8","5","2"],["a","b","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","7","0"],["f","a","g"]]],[[["8","5","6"],["d","b","g","c"]],[["9","4","8","5","6","0"],["d","g"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","4","8","5","6","0"],["d","g"]]],[[["9","8","5","0"],["d","a","g"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","8","5","0"],["d","a","g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","4","8"],["f","b","g"]]],[[["9","8","5"],["d","a","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8","5","0"],["d","a","g","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","2"],["f","e","a","b","c"]],[["3","8","0","2"],["f","a","c"]]],[[["9","4","8","5","6"],["d","b","g"]],[["9","4","8","5","6"],["d","b","g"]]],[[["8","0","2"],["f","e","a","c"]],[["3","8","0","2"],["f","a","c"]]],[[["8","0","2"],["f","e","a","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5","6","2"],["b","c"]],[["3","8","5","6","0","2"],["c"]]],[[["8","5","0"],["d","a","g","c"]],[["9","3","8","7","5","0"],["a","g"]]],[[["9","3","4","8","2"],["f","b"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","4","8","5","6"],["b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8","5"],["a","b","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","8","2"],["f","a","b"]]],[[["3","8","0"],["f","a","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["8","5"],["d","a","b","g","c"]],[["8","5","6","0"],["d","g","c"]]],[[["8","5","0"],["d","a","g","c"]],[["3","8","5","0"],["a","g","c"]]],[[["8","5","6"],["d","b","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","4","8"],["f","b","g"]]],[[["3","8","5","2"],["a","b","c"]],[["3","8","5","0","2"],["a","c"]]],[[["3","8","5"],["a","b","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["8","5","6","0"],["d","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","8"],["d","f","a","b","g"]],[["9","8"],["d","f","a","b","g"]]],[[["8","5"],["d","a","b","g","c"]],[["3","8","5"],["a","b","g","c"]]],[[["3","8","0"],["f","a","g","c"]],[["9","3","8","7","0","2"],["f","a"]]],[[["8","2"],["f","e","a","b","c"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["9","3","8","2"],["f","a","b"]],[["9","3","8","7","0","2"],["f","a"]]],[[["8","5"],["d","a","b","g","c"]],[["9","3","8","7","5","0"],["a","g"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","8","7","0","2"],["f","a"]]],[[["8","5","6"],["d","b","g","c"]],[["3","8","5","6"],["b","g","c"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","5"],["a","b","g","c"]],[["3","8","5","2"],["a","b","c"]]],[[["8","0"],["d","f","e","a","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["8","6","0"],["d","e","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8","5","0"],["a","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8","5"],["a","b","g","c"]],[["3","8","5","0","2"],["a","c"]]],[[["3","8","5"],["a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","4","8"],["d","f","b","g"]]],[[["3","8","0","2"],["f","a","c"]],[["9","3","8","7","0","2"],["f","a"]]],[[["9","8","5"],["d","a","b","g"]],[["9","4","8","5","6","0"],["d","g"]]],[[["3","8","0","2"],["f","a","c"]],[["3","8","0","2"],["f","a","c"]]],[[["9","3","8","5"],["a","b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","6"],["d","e","b","g","c"]],[["8","6","0","2"],["e","c"]]],[[["9","4","8","0"],["d","f","g"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["9","8"],["d","f","a","b","g"]],[["9","4","8","5","6"],["d","b","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8","0","2"],["f","a","c"]],[["3","8","5","0","2"],["a","c"]]],[[["8","5","6"],["d","b","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["9","3","4","8","5","6","2"],["b"]],[["9","3","4","8","5","6","2"],["b"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","8","2"],["f","a","b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","2"],["f","e","a","b","c"]]],[[["8","5"],["d","a","b","g","c"]],[["3","8","5","0"],["a","g","c"]]],[[["9","8"],["d","f","a","b","g"]],[["9","4","8","0"],["d","f","g"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","6","0","2"],["e","c"]],[["8","6","0","2"],["e","c"]]],[[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","8","5"],["a","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5"],["a","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8","6"],["d","e","b","g","c"]],[["3","8","5","6"],["b","g","c"]]],[[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","2"],["f","b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8"],["f","a","b","g","c"]]],[[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","5"],["d","a","b","g","c"]],[["8","5","0"],["d","a","g","c"]]],[[["3","8","5","6"],["b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","6","2"],["e","b","c"]],[["8","6","0","2"],["e","c"]]],[[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","5","6","2"],["b","c"]],[["3","8","5","6","2"],["b","c"]]],[[["3","8","5","6","0"],["g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","8","5"],["a","b","g"]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","5","6"],["b","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","3","8","7","5","0"],["a","g"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","6","0"],["d","e","g","c"]],[["8","6","0"],["d","e","g","c"]]],[[["8","6"],["d","e","b","g","c"]],[["8","5","6"],["d","b","g","c"]]],[[["8","2"],["f","e","a","b","c"]],[["8","0","2"],["f","e","a","c"]]],[[["9","3","4","8","5","6"],["b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","0"],["d","f","e","a","g","c"]],[["3","8","5","0","2"],["a","c"]]],[[["9","4","8"],["d","f","b","g"]],[["9","4","8","0"],["d","f","g"]]],[[["9","4","8"],["d","f","b","g"]],[["9","4","8","5","6"],["d","b","g"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["9","3","4","8","7","5","6","1","0"],["g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","0"],["f","a","g","c"]],[["9","3","8","7","0"],["f","a","g"]]],[[["8","2"],["f","e","a","b","c"]],[["8","6","0","2"],["e","c"]]],[[["9","3","4","8","7","5","6","1","0","2"],[]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","6","0"],["d","e","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["8","6"],["d","e","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","2"],["f","b"]]],[[["8","5","6"],["d","b","g","c"]],[["8","5","6"],["d","b","g","c"]]],[[["8","5"],["d","a","b","g","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","8"],["d","f","a","b","g"]],[["9","4","8","5","6","0"],["d","g"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","8","7","5","0"],["a","g"]]],[[["9","3","8","2"],["f","a","b"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","5"],["a","b","g","c"]],[["9","3","8","5"],["a","b","g"]]],[[["8","2"],["f","e","a","b","c"]],[["9","3","8","2"],["f","a","b"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","0","2"],["f","e","a","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","2"],["f","a","b","c"]],[["3","8","5","2"],["a","b","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","6","2"],["e","b","c"]]],[[["9","8","5"],["d","a","b","g"]],[["9","8","5","0"],["d","a","g"]]],[[["9","8","5"],["d","a","b","g"]],[["9","3","4","8","5","6"],["b","g"]]],[[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8","5"],["d","a","b","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8","5","6","0","2"],["c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","4","8","5","6","0"],["d","g"]],[["9","4","8","5","6","0"],["d","g"]]],[[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","5","0"],["d","a","g","c"]],[["9","4","8","5","6","0"],["d","g"]]],[[["8","5","0"],["d","a","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["9","8","5","0"],["d","a","g"]],[["9","4","8","5","6","0"],["d","g"]]],[[["9","3","8","7","0","2"],["f","a"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5","0","2"],["a","c"]],[["3","8","5","0","2"],["a","c"]]],[[["9","8","5"],["d","a","b","g"]],[["9","3","8","7","5","0"],["a","g"]]],[[["3","8","5","6"],["b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]],[[["3","8","0","2"],["f","a","c"]],[["3","8","5","6","0","2"],["c"]]],[[["3","8","5"],["a","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]],[[["9","3","4","8","7","1","0","2"],["f"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","5","6"],["b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","8","7","5","0","2"],["a"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","8"],["f","a","b","g"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","4","8","0"],["d","f","g"]]],[[["9","4","8","5","6"],["d","b","g"]],[["9","4","8","5","6","0"],["d","g"]]],[[["8","0"],["d","f","e","a","g","c"]],[["3","8","0"],["f","a","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","5","0"],["d","a","g","c"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["3","8","2"],["f","a","b","c"]],[["3","8","2"],["f","a","b","c"]]],[[["8","6"],["d","e","b","g","c"]],[["8","6"],["d","e","b","g","c"]]],[[["8","5"],["d","a","b","g","c"]],[["3","8","5","6","2"],["b","c"]]],[[["8","5"],["d","a","b","g","c"]],[["9","3","8","5"],["a","b","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","2"],["a","b","c"]]],[[["3","8","5","2"],["a","b","c"]],[["9","3","8","5","2"],["a","b"]]],[[["9","8","5","0"],["d","a","g"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","6","0"],["d","e","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","2"],["f","e","a","b","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","0","2"],["f","e","a","c"]],[["3","8","5","6","0","2"],["c"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","5","0"],["a","g","c"]]],[[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["9","4","8","0"],["d","f","g"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["9","3","8","5"],["a","b","g"]],[["9","3","8","7","5","0","2"],["a"]]],[[["3","8","5","0"],["a","g","c"]],[["3","8","5","0"],["a","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","2"],["f","a","b"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","8","7","0"],["f","a","g"]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","8","7","0","2"],["f","a"]]],[[["9","4","8"],["d","f","b","g"]],[["9","3","4","8"],["f","b","g"]]],[[["3","8","5","6","0"],["g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","0","2"],["a","c"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","5","0","2"],["a","c"]]],[[["9","8"],["d","f","a","b","g"]],[["9","8","5"],["d","a","b","g"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8","5"],["d","a","b","g","c"]],[["3","8","5","6"],["b","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8"],["f","b","g"]]],[[["3","8","5","2"],["a","b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","4","8","5","6"],["d","b","g"]],[["9","3","4","8","5","6"],["b","g"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8","5","6"],["b","g","c"]],[["3","8","5","6"],["b","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","8","5"],["d","a","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","5","6"],["b","g"]]],[[["3","8","5","6","0"],["g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8","6"],["d","e","b","g","c"]],[["9","4","8","5","6","0"],["d","g"]]],[[["8","5","6","0"],["d","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","0"],["f","a","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","2"],["f","a","b","c"]]],[[["9","3","8","5"],["a","b","g"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8","5","0"],["d","a","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["3","8","5","6","0","2"],["c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","8","5"],["a","b","g"]],[["9","3","4","8","5","6"],["b","g"]]],[[["9","8","5","0"],["d","a","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","8","5","2"],["a","b"]]],[[["3","8","0","2"],["f","a","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","4","8","5","6","2"],["b"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["9","3","8","7","0","2"],["f","a"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","4","8"],["d","f","b","g"]],[["9","4","8"],["d","f","b","g"]]],[[["9","8","5","0"],["d","a","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","8","2"],["f","a","b"]],[["9","3","8","5","2"],["a","b"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8"],["f","a","b","g","c"]]],[[["9","3","8","5"],["a","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","2"],["f","a","b","c"]]],[[["8","5"],["d","a","b","g","c"]],[["8","5","6"],["d","b","g","c"]]],[[["3","8","0"],["f","a","g","c"]],[["3","8","0","2"],["f","a","c"]]],[[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","6"],["d","e","b","g","c"]],[["8","6","2"],["e","b","c"]]],[[["8","0"],["d","f","e","a","g","c"]],[["8","6","0"],["d","e","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","5","6","0"],["d","g","c"]]],[[["3","8","2"],["f","a","b","c"]],[["3","8","5","6","0","2"],["c"]]],[[["3","8","5","2"],["a","b","c"]],[["3","8","5","6","2"],["b","c"]]],[[["8","6","2"],["e","b","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5"],["a","b","g","c"]],[["3","8","5"],["a","b","g","c"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","8"],["f","a","b","g"]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8","5"],["d","a","b","g","c"]],[["9","4","8","5","6","0"],["d","g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","8","7","0"],["f","a","g"]]],[[["8","6"],["d","e","b","g","c"]],[["3","8","5","6","2"],["b","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","5"],["a","b","g"]]],[[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","5","6"],["d","b","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","0","2"],["f","a","c"]]],[[["8","6","2"],["e","b","c"]],[["3","8","5","6","2"],["b","c"]]],[[["9","3","4","8","5","6"],["b","g"]],[["9","3","4","8","5","6"],["b","g"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","0"],["f","a","g","c"]]],[[["8","0","2"],["f","e","a","c"]],[["8","0","2"],["f","e","a","c"]]],[[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["9","3","8","2"],["f","a","b"]],[["9","3","4","8","2"],["f","b"]]],[[["8","5","0"],["d","a","g","c"]],[["8","5","6","0"],["d","g","c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","4","8","5","6","0"],["d","g"]]],[[["3","8","5","0"],["a","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","8","0"],["d","f","a","g"]],[["9","4","8","5","6","0"],["d","g"]]],[[["8","2"],["f","e","a","b","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","8","5"],["a","b","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","2"],["f","b"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["3","8","2"],["f","a","b","c"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["9","4","8","5","6","0"],["d","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","8"],["d","f","a","b","g"]],[["9","8","5","0"],["d","a","g"]]],[[["8","6"],["d","e","b","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","3","4","8","7","5","6","1","0"],["g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8","5"],["a","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8","5"],["d","a","b","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["8"],["d","f","e","a","b","g","c"]],[["8","0"],["d","f","e","a","g","c"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","4","8"],["f","b","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","4","8","0"],["d","f","g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","8","5","2"],["a","b"]]],[[["9","3","8","5","2"],["a","b"]],[["9","3","8","5","2"],["a","b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","5","2"],["a","b"]]],[[["8","6","0"],["d","e","g","c"]],[["8","5","6","0"],["d","g","c"]]],[[["9","8","0"],["d","f","a","g"]],[["9","4","8","0"],["d","f","g"]]],[[["8","5","6","0"],["d","g","c"]],[["8","5","6","0"],["d","g","c"]]],[[["9","3","8","7","5","0"],["a","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","8","5"],["d","a","b","g"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","0","2"],["f","e","a","c"]],[["3","8","5","0","2"],["a","c"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","5","6"],["b","g","c"]]],[[["3","8","0","2"],["f","a","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","3","8","5","2"],["a","b"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["9","4","8","0"],["d","f","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","0"],["f","a","g","c"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8","2"],["f","e","a","b","c"]],[["8","2"],["f","e","a","b","c"]]],[[["3","8","5"],["a","b","g","c"]],[["3","8","5","6","2"],["b","c"]]],[[["3","8","0"],["f","a","g","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","0"],["d","f","e","a","g","c"]],[["8","5","0"],["d","a","g","c"]]],[[["8","2"],["f","e","a","b","c"]],[["3","8","5","0","2"],["a","c"]]],[[["9","3","8","7","0","2"],["f","a"]],[["9","3","8","7","0","2"],["f","a"]]],[[["8","0","2"],["f","e","a","c"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["8","0"],["d","f","e","a","g","c"]],[["8","5","6","0"],["d","g","c"]]],[[["8","5"],["d","a","b","g","c"]],[["3","8","5","2"],["a","b","c"]]],[[["8","5","0"],["d","a","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5","6"],["b","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","4","8","7","1","0"],["f","g"]]],[[["8","5","0"],["d","a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5"],["a","b","g","c"]],[["3","8","5","0"],["a","g","c"]]],[[["8","6"],["d","e","b","g","c"]],[["8","6","0"],["d","e","g","c"]]],[[["9","4","8","0"],["d","f","g"]],[["9","4","8","5","6","0"],["d","g"]]],[[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","6","0"],["d","e","g","c"]],[["8","6","0","2"],["e","c"]]],[[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","8","0"],["d","f","a","g"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","3","4","8","2"],["f","b"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["8","5","6"],["d","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["3","8","5","6","2"],["b","c"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","5","6","2"],["b","c"]]],[[["3","8","5"],["a","b","g","c"]],[["3","8","5","6"],["b","g","c"]]],[[["9","3","8","5"],["a","b","g"]],[["9","3","8","5"],["a","b","g"]]],[[["8","5","0"],["d","a","g","c"]],[["3","8","5","0","2"],["a","c"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","2"],["f","e","a","b","c"]],[["8","6","2"],["e","b","c"]]],[[["8","5"],["d","a","b","g","c"]],[["9","4","8","5","6"],["d","b","g"]]],[[["8","5","6","0"],["d","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","3","8"],["f","a","b","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5"],["a","b","g","c"]],[["9","3","8","7","5","0"],["a","g"]]],[[["8","0"],["d","f","e","a","g","c"]],[["8","0","2"],["f","e","a","c"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["3","8","5"],["a","b","g","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["8","5","0"],["d","a","g","c"]],[["9","8","5","0"],["d","a","g"]]],[[["3","8","5","6","0"],["g","c"]],[["9","3","4","8","7","5","6","1","0","2"],[]]],[[["9","3","4","8"],["f","b","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","8"],["d","f","a","b","g"]],[["9","3","8","5","2"],["a","b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","8","5"],["d","a","b","g"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8","5","0"],["a","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["9","3","8","2"],["f","a","b"]],[["9","3","8","2"],["f","a","b"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]],[[["8","2"],["f","e","a","b","c"]],[["9","3","4","8","2"],["f","b"]]],[[["9","8"],["d","f","a","b","g"]],[["9","4","8"],["d","f","b","g"]]],[[["9","8","5"],["d","a","b","g"]],[["9","8","5"],["d","a","b","g"]]],[[["9","3","4","8","7","1","0"],["f","g"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["3","8","5","0"],["a","g","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","8","7","0","2"],["f","a"]]],[[["8","5","6"],["d","b","g","c"]],[["3","8","5","6","2"],["b","c"]]],[[["3","8","0"],["f","a","g","c"]],[["3","8","5","6","0"],["g","c"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","7","5","6","1","0"],["g"]]],[[["8"],["d","f","e","a","b","g","c"]],[["9","3","8","7","5","0","2"],["a"]]],[[["9","8","0"],["d","f","a","g"]],[["9","8","0"],["d","f","a","g"]]],[[["9","4","8"],["d","f","b","g"]],[["9","3","4","8","2"],["f","b"]]],[[["8","5"],["d","a","b","g","c"]],[["9","3","4","8","5","6","2"],["b"]]],[[["8","0","2"],["f","e","a","c"]],[["9","3","8","7","0","2"],["f","a"]]],[[["9","8","5"],["d","a","b","g"]],[["9","3","8","5","2"],["a","b"]]],[[["9","8","5","0"],["d","a","g"]],[["9","8","5","0"],["d","a","g"]]],[[["3","8","2"],["f","a","b","c"]],[["3","8","0","2"],["f","a","c"]]],[[["8","0"],["d","f","e","a","g","c"]],[["3","8","5","6","0","2"],["c"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","5","6"],["b","g"]]],[[["3","8"],["f","a","b","g","c"]],[["3","8","0","2"],["f","a","c"]]],[[["8","0"],["d","f","e","a","g","c"]],[["9","8","0"],["d","f","a","g"]]],[[["3","8","0"],["f","a","g","c"]],[["3","8","0"],["f","a","g","c"]]],[[["3","8","5","0"],["a","g","c"]],[["3","8","5","0","2"],["a","c"]]],[[["8","0"],["d","f","e","a","g","c"]],[["3","8","0","2"],["f","a","c"]]],[[["3","8"],["f","a","b","g","c"]],[["9","3","4","8","7","1","0","2"],["f"]]],[[["9","3","8","7","0"],["f","a","g"]],[["9","3","8","7","0"],["f","a","g"]]]]} \ No newline at end of file diff --git a/testing-data/latex/lattice-smeasure1-2.tex b/testing-data/latex/lattice-smeasure1-2.tex new file mode 100644 index 000000000..ce05e0971 --- /dev/null +++ b/testing-data/latex/lattice-smeasure1-2.tex @@ -0,0 +1,80 @@ +%necessary tikz libraries +%\usetikzlibrary{tikzmark,arrows,positioning} +\begin{figure} + \centering + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{\tikzmarknode{ca1}{1}}% + \att{\tikzmarknode{ca4}{4}}% + \att{\tikzmarknode{ca3}{3}}% + \att{\tikzmarknode{ca2}{2}}% + \att{\tikzmarknode{ca5}{5}}% + \obj{x.x..}{1} + \obj{x.x..}{4} + \obj{.x.x.}{2} + \obj{....x}{3} + \end{cxt} + \end{minipage}% + \begin{minipage}[c]{.5\textwidth} + \centering + \colorlet{mivertexcolor}{blue} + \colorlet{jivertexcolor}{red} + \colorlet{vertexcolor}{mivertexcolor!50} + \colorlet{bordercolor}{black!80} + \colorlet{linecolor}{gray} + \tikzset{vertexbase/.style={semithick, shape=circle, inner sep=2pt, outer sep=0pt, draw=bordercolor},% + vertex/.style={vertexbase, fill=vertexcolor!45},% + mivertex/.style={vertexbase, fill=mivertexcolor!45},% + jivertex/.style={vertexbase, fill=jivertexcolor!45},% + divertex/.style={vertexbase, top color=mivertexcolor!45, bottom color=jivertexcolor!45},% + conn/.style={-, thick, color=linecolor}% + } + \begin{tikzpicture} + \begin{scope} %for scaling and the like + \begin{scope} %draw vertices + \foreach \nodename/\nodetype/\xpos/\ypos in {% + 0/vertex/0/0, + 1/divertex/-2/4, + 2/divertex/0/4, + 3/divertex/2/4, + 4/vertex/0/8 + } \node[\nodetype] (\nodename) at (\xpos, \ypos) {}; + \end{scope} + \begin{scope} %draw connections + \path (1) edge[conn] (4); + \path (0) edge[conn] (1); + \path (0) edge[conn] (2); + \path (0) edge[conn] (3); + \path (2) edge[conn] (4); + \path (3) edge[conn] (4); + \end{scope} + \begin{scope} %add labels + \foreach \nodename/\labelpos/\labelopts/\labelcontent in {% + 1/below//{\tikzmarknode{n2}{}2}, + 1/above//{4, 2}, + 2/below//{\tikzmarknode{n1}{}1}, + 2/above//{1, 3}, + 3/below//{\tikzmarknode{n3}{}3}, + 3/above//{5} + } \coordinate[label={[\labelopts]\labelpos:{\labelcontent}}](c) at (\nodename); + \end{scope} + \end{scope} + \end{tikzpicture} + \end{minipage}% +\end{figure} +\begin{tikzpicture}[overlay,remember picture] + \node[right = 0.45cm of ca2] (za) {}; + \node[below = 0.25cm of za] (co1) {}; + \node[below = 0.16cm of co1] (co4) {}; + \node[below = 0.16cm of co4] (co2) {}; + \node[below = 0.16cm of co2] (co3) {}; + \node[above = 0.2cm of n1] (an1) {}; + \node[above = 0.2cm of n3] (an3) {}; + \node[above = 0.2cm of n2] (an2) {}; + \draw[->, >=stealth] (co1) -- (an1); + \draw[->, >=stealth] (co4) -- (an1); + \draw[->, >=stealth] (co2) -- (an1); + \draw[->, >=stealth] (co3) -- (an2); +\end{tikzpicture} diff --git a/testing-data/latex/lattice-smeasure1-3.tex b/testing-data/latex/lattice-smeasure1-3.tex new file mode 100644 index 000000000..af238f35e --- /dev/null +++ b/testing-data/latex/lattice-smeasure1-3.tex @@ -0,0 +1,87 @@ +%necessary tikz libraries +%\usetikzlibrary{tikzmark,arrows,positioning} +\begin{figure} + \centering + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{\tikzmarknode{ca1}{1}}% + \att{\tikzmarknode{ca4}{4}}% + \att{\tikzmarknode{ca3}{3}}% + \att{\tikzmarknode{ca2}{2}}% + \att{\tikzmarknode{ca5}{5}}% + \obj{x.x..}{1} + \obj{x.x..}{4} + \obj{.x.x.}{2} + \obj{....x}{3} + \end{cxt} + \end{minipage}% + \begin{minipage}[c]{.5\textwidth} + \centering + \colorlet{mivertexcolor}{blue} + \colorlet{jivertexcolor}{red} + \colorlet{vertexcolor}{mivertexcolor!50} + \colorlet{bordercolor}{black!80} + \colorlet{linecolor}{gray} + \tikzset{vertexbase/.style={semithick, shape=circle, inner sep=2pt, outer sep=0pt, draw=bordercolor},% + vertex/.style={vertexbase, fill=vertexcolor!45},% + mivertex/.style={vertexbase, fill=mivertexcolor!45},% + jivertex/.style={vertexbase, fill=jivertexcolor!45},% + divertex/.style={vertexbase, top color=mivertexcolor!45, bottom color=jivertexcolor!45},% + conn/.style={-, thick, color=linecolor}% + } + \begin{tikzpicture} + \begin{scope} %for scaling and the like + \begin{scope} %draw vertices + \foreach \nodename/\nodetype/\xpos/\ypos in {% + 0/vertex/0/0, + 1/divertex/-2/4, + 2/divertex/0/4, + 3/divertex/2/4, + 4/vertex/0/8 + } \node[\nodetype] (\nodename) at (\xpos, \ypos) {}; + \end{scope} + \begin{scope} %draw connections + \path (1) edge[conn] (4); + \path (0) edge[conn] (1); + \path (0) edge[conn] (2); + \path (0) edge[conn] (3); + \path (2) edge[conn] (4); + \path (3) edge[conn] (4); + \end{scope} + \begin{scope} %add labels + \foreach \nodename/\labelpos/\labelopts/\labelcontent in {% + 1/below//{\tikzmarknode{n2}{}2}, + 1/above//{4, 2}, + 2/below//{\tikzmarknode{n1}{}1}, + 2/above//{1, 3}, + 3/below//{\tikzmarknode{n3}{}3}, + 3/above//{5}, + 4/below//{\tikzmarknode{n7}{}\tikzmarknode{n4}{}\tikzmarknode{n6}{}\tikzmarknode{n9}{}\tikzmarknode{n5}{}\tikzmarknode{n8}{}7, 4, 6, 9, 5, 8} + } \coordinate[label={[\labelopts]\labelpos:{\labelcontent}}](c) at (\nodename); + \end{scope} + \end{scope} + \end{tikzpicture} + \end{minipage}% +\end{figure} +\begin{tikzpicture}[overlay,remember picture] + \node[right = 0.45cm of ca2] (za) {}; + \node[below = 0.25cm of za] (co1) {}; + \node[below = 0.16cm of co1] (co4) {}; + \node[below = 0.16cm of co4] (co2) {}; + \node[below = 0.16cm of co2] (co3) {}; + \node[above = 0.2cm of n7] (an7) {}; + \node[above = 0.2cm of n1] (an1) {}; + \node[above = 0.2cm of n4] (an4) {}; + \node[above = 0.2cm of n6] (an6) {}; + \node[above = 0.2cm of n3] (an3) {}; + \node[above = 0.2cm of n2] (an2) {}; + \node[above = 0.2cm of n9] (an9) {}; + \node[above = 0.2cm of n5] (an5) {}; + \node[above = 0.2cm of n8] (an8) {}; + \draw[->, >=stealth] (co1) -- (an1); + \draw[->, >=stealth] (co4) -- (an1); + \draw[->, >=stealth] (co2) -- (an1); + \draw[->, >=stealth] (co3) -- (an2); +\end{tikzpicture} diff --git a/testing-data/latex/lattice-smeasure3-1.tex b/testing-data/latex/lattice-smeasure3-1.tex new file mode 100644 index 000000000..9f2ce0dd6 --- /dev/null +++ b/testing-data/latex/lattice-smeasure3-1.tex @@ -0,0 +1,96 @@ +%necessary tikz libraries +%\usetikzlibrary{tikzmark,arrows,positioning} +\begin{figure} + \centering + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{\tikzmarknode{ca1}{1}}% + \att{\tikzmarknode{ca4}{4}}% + \att{\tikzmarknode{ca3}{3}}% + \att{\tikzmarknode{ca2}{2}}% + \att{\tikzmarknode{ca5}{5}}% + \obj{.....}{7} + \obj{x.x..}{1} + \obj{.....}{4} + \obj{.x.x.}{2} + \obj{.....}{5} + \obj{.....}{6} + \obj{....x}{3} + \obj{.....}{9} + \obj{.....}{8} + \end{cxt} + \end{minipage}% + \begin{minipage}[c]{.5\textwidth} + \centering + \colorlet{mivertexcolor}{blue} + \colorlet{jivertexcolor}{red} + \colorlet{vertexcolor}{mivertexcolor!50} + \colorlet{bordercolor}{black!80} + \colorlet{linecolor}{gray} + \tikzset{vertexbase/.style={semithick, shape=circle, inner sep=2pt, outer sep=0pt, draw=bordercolor},% + vertex/.style={vertexbase, fill=vertexcolor!45},% + mivertex/.style={vertexbase, fill=mivertexcolor!45},% + jivertex/.style={vertexbase, fill=jivertexcolor!45},% + divertex/.style={vertexbase, top color=mivertexcolor!45, bottom color=jivertexcolor!45},% + conn/.style={-, thick, color=linecolor}% + } + \begin{tikzpicture} + \begin{scope} %for scaling and the like + \begin{scope} %draw vertices + \foreach \nodename/\nodetype/\xpos/\ypos in {% + 0/vertex/0/0, + 1/divertex/-2/4, + 2/divertex/0/4, + 3/divertex/2/4, + 4/vertex/0/8 + } \node[\nodetype] (\nodename) at (\xpos, \ypos) {}; + \end{scope} + \begin{scope} %draw connections + \path (1) edge[conn] (4); + \path (2) edge[conn] (4); + \path (0) edge[conn] (1); + \path (0) edge[conn] (2); + \path (0) edge[conn] (3); + \path (3) edge[conn] (4); + \end{scope} + \begin{scope} %add labels + \foreach \nodename/\labelpos/\labelopts/\labelcontent in {% + 1/below//{\tikzmarknode{n2}{}2}, + 1/above//{4, 2}, + 2/below//{\tikzmarknode{n1}{}\tikzmarknode{n4}{}1, 4}, + 2/above//{1, 3}, + 3/below//{\tikzmarknode{n3}{}3}, + 3/above//{5} + } \coordinate[label={[\labelopts]\labelpos:{\labelcontent}}](c) at (\nodename); + \end{scope} + \end{scope} + \end{tikzpicture} + \end{minipage}% +\end{figure} +\begin{tikzpicture}[overlay,remember picture] + \node[right = 0.45cm of ca2] (za) {}; + \node[below = 0.25cm of za] (co7) {}; + \node[below = 0.16cm of co7] (co1) {}; + \node[below = 0.16cm of co1] (co4) {}; + \node[below = 0.16cm of co4] (co2) {}; + \node[below = 0.16cm of co2] (co5) {}; + \node[below = 0.16cm of co5] (co6) {}; + \node[below = 0.16cm of co6] (co3) {}; + \node[below = 0.16cm of co3] (co9) {}; + \node[below = 0.16cm of co9] (co8) {}; + \node[above = 0.2cm of n1] (an1) {}; + \node[above = 0.2cm of n4] (an4) {}; + \node[above = 0.2cm of n3] (an3) {}; + \node[above = 0.2cm of n2] (an2) {}; + \draw[->, >=stealth] (co7) -- (an1); + \draw[->, >=stealth] (co1) -- (an1); + \draw[->, >=stealth] (co4) -- (an1); + \draw[->, >=stealth] (co2) -- (an1); + \draw[->, >=stealth] (co5) -- (an1); + \draw[->, >=stealth] (co6) -- (an2); + \draw[->, >=stealth] (co3) -- (an2); + \draw[->, >=stealth] (co9) -- (an3); + \draw[->, >=stealth] (co8) -- (an4); +\end{tikzpicture} diff --git a/testing-data/latex/tikz-example.tex b/testing-data/latex/tikz-example.tex index 23254f154..82db2cade 100644 --- a/testing-data/latex/tikz-example.tex +++ b/testing-data/latex/tikz-example.tex @@ -3,22 +3,23 @@ \colorlet{vertexcolor}{mivertexcolor!50} \colorlet{bordercolor}{black!80} \colorlet{linecolor}{gray} -\tikzset{vertexbase/.style={semithick, shape=circle, inner sep=2pt, outer sep=0pt, draw=bordercolor},% - vertex/.style={vertexbase, fill=vertexcolor!45},% - mivertex/.style={vertexbase, fill=mivertexcolor!45},% - jivertex/.style={vertexbase, fill=jivertexcolor!45},% - divertex/.style={vertexbase, top color=mivertexcolor!45, bottom color=jivertexcolor!45},% +% parameter corresponds to the used valuation function and can be addressed by #1 +\tikzset{vertexbase/.style 2 args={semithick, shape=circle, inner sep=2pt, outer sep=0pt, draw=bordercolor},% + vertex/.style 2 args={vertexbase={#1}{}, fill=vertexcolor!45},% + mivertex/.style 2 args={vertexbase={#1}{}, fill=mivertexcolor!45},% + jivertex/.style 2 args={vertexbase={#1}{}, fill=jivertexcolor!45},% + divertex/.style 2 args={vertexbase={#1}{}, top color=mivertexcolor!45, bottom color=jivertexcolor!45},% conn/.style={-, thick, color=linecolor}% } \begin{tikzpicture} \begin{scope} %for scaling and the like \begin{scope} %draw vertices - \foreach \nodename/\nodetype/\xpos/\ypos in {% - 0/vertex/0/0, - 1/divertex/-1/1, - 2/divertex/1/1, - 3/vertex/0/2 - } \node[\nodetype] (\nodename) at (\xpos, \ypos) {}; + \foreach \nodename/\nodetype/\param/\xpos/\ypos in {% + 0/vertex//0/0, + 1/divertex//-1/1, + 2/divertex//1/1, + 3/vertex//0/2 + } \node[\nodetype={\param}{}] (\nodename) at (\xpos, \ypos) {}; \end{scope} \begin{scope} %draw connections \path (2) edge[conn] (3); @@ -28,14 +29,14 @@ \end{scope} \begin{scope} %add labels \foreach \nodename/\labelpos/\labelopts/\labelcontent in {% - 0/below//{\$e\%}, 0/above//{\textasciicircum a\&}, - 1/below//{\#f}, 1/above//{\textless b}, - 2/below//{\{g\}}, 2/above//{\_c\textasciitilde }, - 3/below//{\textbackslash h}, - 3/above//{\textgreater d} + 3/above//{\textgreater d}, + 0/below//{\$e\%}, + 1/below//{\#f}, + 2/below//{\{g\}}, + 3/below//{\textbackslash h} } \coordinate[label={[\labelopts]\labelpos:{\labelcontent}}](c) at (\nodename); \end{scope} \end{scope} diff --git a/testing-data/latex/tikz-smeasure1-2.tex b/testing-data/latex/tikz-smeasure1-2.tex new file mode 100644 index 000000000..6f91c8eea --- /dev/null +++ b/testing-data/latex/tikz-smeasure1-2.tex @@ -0,0 +1,48 @@ +%necessary tikz libraries +%\usetikzlibrary{tikzmark,arrows,positioning} +\begin{figure} + \centering + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{\tikzmarknode{ca1}{1}}% + \att{\tikzmarknode{ca4}{4}}% + \att{\tikzmarknode{ca3}{3}}% + \att{\tikzmarknode{ca2}{2}}% + \att{\tikzmarknode{ca5}{5}}% + \obj{x.x..}{1} + \obj{x.x..}{4} + \obj{.x.x.}{2} + \obj{....x}{3} + \end{cxt} + \end{minipage}% + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{1}% + \att{4}% + \att{3}% + \att{2}% + \att{5}% + \obj{....x}{\tikzmarknode{so3}{3}} + \obj{x.x..}{\tikzmarknode{so1}{1}} + \obj{.x.x.}{\tikzmarknode{so2}{2}} + \end{cxt} + \end{minipage}% +\end{figure} +\begin{tikzpicture}[overlay,remember picture] + \node[right = 0.45cm of ca2] (za) {}; + \node[below = 0.25cm of za] (co1) {}; + \node[below = 0.16cm of co1] (co4) {}; + \node[below = 0.16cm of co4] (co2) {}; + \node[below = 0.16cm of co2] (co3) {}; + \node[left = 0cm of so1](lso1){}; + \node[left = 0cm of so3](lso3){}; + \node[left = 0cm of so2](lso2){}; + \draw[->, >=stealth] (co1) -- (lso1); + \draw[->, >=stealth] (co4) -- (lso1); + \draw[->, >=stealth] (co2) -- (lso1); + \draw[->, >=stealth] (co3) -- (lso2); +\end{tikzpicture} diff --git a/testing-data/latex/tikz-smeasure1-3.tex b/testing-data/latex/tikz-smeasure1-3.tex new file mode 100644 index 000000000..045a672d0 --- /dev/null +++ b/testing-data/latex/tikz-smeasure1-3.tex @@ -0,0 +1,60 @@ +%necessary tikz libraries +%\usetikzlibrary{tikzmark,arrows,positioning} +\begin{figure} + \centering + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{\tikzmarknode{ca1}{1}}% + \att{\tikzmarknode{ca4}{4}}% + \att{\tikzmarknode{ca3}{3}}% + \att{\tikzmarknode{ca2}{2}}% + \att{\tikzmarknode{ca5}{5}}% + \obj{x.x..}{1} + \obj{x.x..}{4} + \obj{.x.x.}{2} + \obj{....x}{3} + \end{cxt} + \end{minipage}% + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{1}% + \att{4}% + \att{3}% + \att{2}% + \att{5}% + \obj{.....}{\tikzmarknode{so7}{7}} + \obj{.....}{\tikzmarknode{so4}{4}} + \obj{.....}{\tikzmarknode{so6}{6}} + \obj{x.x..}{\tikzmarknode{so1}{1}} + \obj{....x}{\tikzmarknode{so3}{3}} + \obj{.x.x.}{\tikzmarknode{so2}{2}} + \obj{.....}{\tikzmarknode{so9}{9}} + \obj{.....}{\tikzmarknode{so5}{5}} + \obj{.....}{\tikzmarknode{so8}{8}} + \end{cxt} + \end{minipage}% +\end{figure} +\begin{tikzpicture}[overlay,remember picture] + \node[right = 0.45cm of ca2] (za) {}; + \node[below = 0.25cm of za] (co1) {}; + \node[below = 0.16cm of co1] (co4) {}; + \node[below = 0.16cm of co4] (co2) {}; + \node[below = 0.16cm of co2] (co3) {}; + \node[left = 0cm of so7](lso7){}; + \node[left = 0cm of so1](lso1){}; + \node[left = 0cm of so4](lso4){}; + \node[left = 0cm of so6](lso6){}; + \node[left = 0cm of so3](lso3){}; + \node[left = 0cm of so2](lso2){}; + \node[left = 0cm of so9](lso9){}; + \node[left = 0cm of so5](lso5){}; + \node[left = 0cm of so8](lso8){}; + \draw[->, >=stealth] (co1) -- (lso1); + \draw[->, >=stealth] (co4) -- (lso1); + \draw[->, >=stealth] (co2) -- (lso1); + \draw[->, >=stealth] (co3) -- (lso2); +\end{tikzpicture} diff --git a/testing-data/latex/tikz-smeasure3-1.tex b/testing-data/latex/tikz-smeasure3-1.tex new file mode 100644 index 000000000..eabe4b978 --- /dev/null +++ b/testing-data/latex/tikz-smeasure3-1.tex @@ -0,0 +1,65 @@ +%necessary tikz libraries +%\usetikzlibrary{tikzmark,arrows,positioning} +\begin{figure} + \centering + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{\tikzmarknode{ca1}{1}}% + \att{\tikzmarknode{ca4}{4}}% + \att{\tikzmarknode{ca3}{3}}% + \att{\tikzmarknode{ca2}{2}}% + \att{\tikzmarknode{ca5}{5}}% + \obj{.....}{7} + \obj{x.x..}{1} + \obj{.....}{4} + \obj{.x.x.}{2} + \obj{.....}{5} + \obj{.....}{6} + \obj{....x}{3} + \obj{.....}{9} + \obj{.....}{8} + \end{cxt} + \end{minipage}% + \begin{minipage}[c]{.5\textwidth} + \centering + \begin{cxt}% + \cxtName{}% + \att{1}% + \att{4}% + \att{3}% + \att{2}% + \att{5}% + \obj{x.x..}{\tikzmarknode{so1}{1}} + \obj{.x.x.}{\tikzmarknode{so2}{2}} + \obj{....x}{\tikzmarknode{so3}{3}} + \obj{x.x..}{\tikzmarknode{so4}{4}} + \end{cxt} + \end{minipage}% +\end{figure} +\begin{tikzpicture}[overlay,remember picture] + \node[right = 0.45cm of ca2] (za) {}; + \node[below = 0.25cm of za] (co7) {}; + \node[below = 0.16cm of co7] (co1) {}; + \node[below = 0.16cm of co1] (co4) {}; + \node[below = 0.16cm of co4] (co2) {}; + \node[below = 0.16cm of co2] (co5) {}; + \node[below = 0.16cm of co5] (co6) {}; + \node[below = 0.16cm of co6] (co3) {}; + \node[below = 0.16cm of co3] (co9) {}; + \node[below = 0.16cm of co9] (co8) {}; + \node[left = 0cm of so1](lso1){}; + \node[left = 0cm of so4](lso4){}; + \node[left = 0cm of so3](lso3){}; + \node[left = 0cm of so2](lso2){}; + \draw[->, >=stealth] (co7) -- (lso1); + \draw[->, >=stealth] (co1) -- (lso1); + \draw[->, >=stealth] (co4) -- (lso1); + \draw[->, >=stealth] (co2) -- (lso1); + \draw[->, >=stealth] (co5) -- (lso1); + \draw[->, >=stealth] (co6) -- (lso2); + \draw[->, >=stealth] (co3) -- (lso2); + \draw[->, >=stealth] (co9) -- (lso3); + \draw[->, >=stealth] (co8) -- (lso4); +\end{tikzpicture} diff --git a/testing-data/mv-context.json b/testing-data/mv-context.json new file mode 100644 index 000000000..e2cc80998 --- /dev/null +++ b/testing-data/mv-context.json @@ -0,0 +1 @@ +{"objects":[1,3,2],"attributes":["color","size"],"incidence":{"[1 color]":"blue","[2 size]":"very-large","[3 size]":"small","[3 color]":"red","[1 size]":"large","[2 color]":"green"}} \ No newline at end of file diff --git a/testing-data/small-fca.json b/testing-data/small-fca.json new file mode 100644 index 000000000..c91435cba --- /dev/null +++ b/testing-data/small-fca.json @@ -0,0 +1 @@ +{"context":{"formal_context":[{"object":"a","attributes":[2]},{"object":"b","attributes":[1,2]}]},"lattice":{"formal_concepts":[{"extent":["b"],"intent":[1,2]},{"extent":["a","b"],"intent":[2]}],"lattice_structure":[{"start_concept":{"extent":["a","b"],"intent":[2]},"end_concept":{"extent":["a","b"],"intent":[2]}},{"start_concept":{"extent":["b"],"intent":[1,2]},"end_concept":{"extent":["b"],"intent":[1,2]}},{"start_concept":{"extent":["b"],"intent":[1,2]},"end_concept":{"extent":["a","b"],"intent":[2]}}]},"implication_sets":[{"implications":[{"premise":[],"conclusion":[2]}]}]} \ No newline at end of file From d36e3df7fa83a9ef5ab023cc0a71f40060d7e81e Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Mon, 17 Jul 2023 22:32:55 +0200 Subject: [PATCH 02/22] Updated version in flake --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index d2309e274..3767de07e 100644 --- a/flake.nix +++ b/flake.nix @@ -40,7 +40,7 @@ conexp = let pname = "conexp-clj"; - version = "2.3.1-SNAPSHOT"; + version = "2.4.0"; in mkCljBin rec { name = "conexp/${pname}"; inherit version; From 5254a10eab6759a7d0c60228892952de93f2aa58 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Mon, 17 Jul 2023 22:35:17 +0200 Subject: [PATCH 03/22] Updated project.clj for release --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 4dddc5da1..cf14b513c 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-SNAPSHOT" +(defproject conexp-clj "2.4.0" :min-lein-version "2.0.0" :description "A ConExp rewrite in clojure -- and so much more ..." From bc2fcb8b63e0ae5713b04a8beaa016b6e10d2217 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Sat, 30 Dec 2023 20:31:29 +0100 Subject: [PATCH 04/22] Clean up project dependencies --- project.clj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/project.clj b/project.clj index 3ef6089c5..389b7877e 100644 --- a/project.clj +++ b/project.clj @@ -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"] From b553a4216ffafb47686e972636f4f48374423cbf Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Sat, 30 Dec 2023 20:43:33 +0100 Subject: [PATCH 05/22] Added CSV load button --- src/main/clojure/conexp/fca/lattices.clj | 3 ++- src/main/clojure/conexp/gui/editors/contexts.clj | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/fca/lattices.clj b/src/main/clojure/conexp/fca/lattices.clj index 20a3bfcd2..3d9e43ac3 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 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)))]), From ed9c112c374cac9c5ffee8864663c0186dcb0cfa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 30 Dec 2023 20:53:45 +0100 Subject: [PATCH 06/22] Update deps-lock.json (#137) Co-authored-by: tomhanika --- deps-lock.json | 273 +------------------------------------------------ 1 file changed, 4 insertions(+), 269 deletions(-) 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/", From bb3775ccb11883b7aa9a53acf3146edc631504f8 Mon Sep 17 00:00:00 2001 From: JannikNordmeyer <93387255+JannikNordmeyer@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:43:56 +0200 Subject: [PATCH 07/22] Exploration Step Method (#143) * Added Exploration Step Method * Removed Print Function Calls. --- src/main/clojure/conexp/fca/exploration.clj | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) 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? From 50844475201b6b251a7d074fcbf49d60e8027436 Mon Sep 17 00:00:00 2001 From: JannikNordmeyer <93387255+JannikNordmeyer@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:53:33 +0200 Subject: [PATCH 08/22] Metric Context (#139) * Implemented Metric Context Class and Related Functions. * Refactoring and Documentation. * Improved Implementation of Multi Arity Functions. * Added Tests. * Added Wrappers for Common Context Operations. * Improved Confusion Matrix Functions. * Update metric_contexts.clj Added Methods for Generating Valuation Functions. * Improved Implementation of Mewtric Functions. * Fully Implemented Generation of Valueation Functions. * Removed Testing Statements. * Improved Implementation of Dual Metric Context. * Fixed Implementation for Dual-Metric-Context and Added Option to generate Metric Contexts without Default Metrics. * Added Example Context and Metric. * Improved Implementation of Hamming Metrics. --- .../clojure/conexp/fca/metric_contexts.clj | 339 ++++++++++++++++++ .../conexp/fca/metric_contexts_test.clj | 199 ++++++++++ 2 files changed, 538 insertions(+) create mode 100644 src/main/clojure/conexp/fca/metric_contexts.clj create mode 100644 src/test/clojure/conexp/fca/metric_contexts_test.clj diff --git a/src/main/clojure/conexp/fca/metric_contexts.clj b/src/main/clojure/conexp/fca/metric_contexts.clj new file mode 100644 index 000000000..1a1c895ea --- /dev/null +++ b/src/main/clojure/conexp/fca/metric_contexts.clj @@ -0,0 +1,339 @@ +(ns conexp.fca.metric-contexts + (: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/test/clojure/conexp/fca/metric_contexts_test.clj b/src/test/clojure/conexp/fca/metric_contexts_test.clj new file mode 100644 index 000000000..eddb0beaa --- /dev/null +++ b/src/test/clojure/conexp/fca/metric_contexts_test.clj @@ -0,0 +1,199 @@ +(ns conexp.fca.metric-context-test + (:use conexp.base + conexp.fca.contexts + conexp.fca.lattices + conexp.fca.metric-contexts) + (: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) +) From a60f7a3a0d35f6e873c7e0333b0c1d6a07925e15 Mon Sep 17 00:00:00 2001 From: JannikNordmeyer <93387255+JannikNordmeyer@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:57:08 +0200 Subject: [PATCH 09/22] Default Namespace (#135) * Added Analysis Namespace. * Added to Analysis Namespace. * Minor Edit to Documentation. * Expanded Analysis Namespace. --- doc/Getting-Started.org | 4 +- project.clj | 4 +- src/main/clojure/conexp/analysis.clj | 64 ++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/main/clojure/conexp/analysis.clj 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/project.clj b/project.clj index 389b7877e..20aec3f45 100644 --- a/project.clj +++ b/project.clj @@ -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])) + From 9984def4a8bc56ae90fe2c7709d04a6ad4c56075 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Thu, 6 Jun 2024 09:25:08 +0200 Subject: [PATCH 10/22] Corrected metric-context-naming / Version bump --- project.clj | 2 +- .../conexp/fca/{metric_contexts.clj => metric_context.clj} | 2 +- .../fca/{metric_contexts_test.clj => metric_context_test.clj} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/main/clojure/conexp/fca/{metric_contexts.clj => metric_context.clj} (99%) rename src/test/clojure/conexp/fca/{metric_contexts_test.clj => metric_context_test.clj} (99%) diff --git a/project.clj b/project.clj index 20aec3f45..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.1-SNAPSHOT" +(defproject conexp-clj "2.4.2-SNAPSHOT" :min-lein-version "2.0.0" :description "A ConExp rewrite in clojure -- and so much more ..." diff --git a/src/main/clojure/conexp/fca/metric_contexts.clj b/src/main/clojure/conexp/fca/metric_context.clj similarity index 99% rename from src/main/clojure/conexp/fca/metric_contexts.clj rename to src/main/clojure/conexp/fca/metric_context.clj index 1a1c895ea..f7c6f3a40 100644 --- a/src/main/clojure/conexp/fca/metric_contexts.clj +++ b/src/main/clojure/conexp/fca/metric_context.clj @@ -1,4 +1,4 @@ -(ns conexp.fca.metric-contexts +(ns conexp.fca.metric-context (:require [conexp.base :refer :all] [conexp.fca.contexts :refer :all] [clojure.set :as set])) diff --git a/src/test/clojure/conexp/fca/metric_contexts_test.clj b/src/test/clojure/conexp/fca/metric_context_test.clj similarity index 99% rename from src/test/clojure/conexp/fca/metric_contexts_test.clj rename to src/test/clojure/conexp/fca/metric_context_test.clj index eddb0beaa..f6e1e36df 100644 --- a/src/test/clojure/conexp/fca/metric_contexts_test.clj +++ b/src/test/clojure/conexp/fca/metric_context_test.clj @@ -2,7 +2,7 @@ (:use conexp.base conexp.fca.contexts conexp.fca.lattices - conexp.fca.metric-contexts) + conexp.fca.metric-context) (:use clojure.test)) (defn object-metric-1 [x y] identity) From 9e55c66b2704cfa08b96038700f902385de91487 Mon Sep 17 00:00:00 2001 From: JannikNordmeyer <93387255+JannikNordmeyer@users.noreply.github.com> Date: Thu, 6 Jun 2024 09:39:25 +0200 Subject: [PATCH 11/22] Causal Implications (#132) * Restored Causal Implications Functionality * Outsourced Functions to implications.clj * Refactoring, Removed Test Data. * Refactoring. Added Test File. * Implemented Test for Causal Implications. * Added Documentation for Causal Rule Discovery. * Minor Documentation Fixes. * Refactoring. * Refactoring. --- README.md | 1 + doc/Causal-Implications.org | 141 ++++++++++ .../conexp/fca/causal_implications.clj | 250 ++++++++++++++++++ src/main/clojure/conexp/fca/implications.clj | 43 ++- .../conexp/fca/causal_implications_test.clj | 166 ++++++++++++ 5 files changed, 599 insertions(+), 2 deletions(-) create mode 100644 doc/Causal-Implications.org create mode 100644 src/main/clojure/conexp/fca/causal_implications.clj create mode 100644 src/test/clojure/conexp/fca/causal_implications_test.clj 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/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/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/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/test/clojure/conexp/fca/causal_implications_test.clj b/src/test/clojure/conexp/fca/causal_implications_test.clj new file mode 100644 index 000000000..c79c80e2e --- /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]"}]))) + +) + From 8450fe6f7eafc6fc0f5bceaaf4323bde970c78cb Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Thu, 6 Jun 2024 11:01:49 +0200 Subject: [PATCH 12/22] Corrected file encoding in causal test --- src/test/clojure/conexp/fca/causal_implications_test.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/clojure/conexp/fca/causal_implications_test.clj b/src/test/clojure/conexp/fca/causal_implications_test.clj index c79c80e2e..b7460123e 100644 --- a/src/test/clojure/conexp/fca/causal_implications_test.clj +++ b/src/test/clojure/conexp/fca/causal_implications_test.clj @@ -83,7 +83,7 @@ (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-data-set (seq [#{"baumläufer" "wintergoldhähnchen"}])) (def birds-fair-odds-ratio 0) @@ -123,7 +123,7 @@ (is (= (fair-data-set birds-ctx birds-rule - #{"beeren" "hirse" "meisenring" "sonnenblume" "talg" "äpfel"}) + #{"beeren" "hirse" "meisenring" "sonnenblume" "talg" "äpfel"}) birds-fair-data-set)) ) @@ -139,7 +139,7 @@ (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 #{"hirse"} #{"äpfel"} 1.9))) (is (not (causally-relevant? birds-ctx #{"insekten"} #{"hirse"} 1.9))) ) From 3a64bf3948b486626d3e86fc64899c1e7b371c36 Mon Sep 17 00:00:00 2001 From: Tom Hanika Date: Thu, 6 Jun 2024 11:09:30 +0200 Subject: [PATCH 13/22] Feature/relevant attributes (#119) * 60% of relevant attributes implemented * Necessary functions for relevant attributes implemented * Fixed n-next functions, some typos * Adatped methods in cover to new parameter list order in fast bitwise From 3492d4b6943bbc05260555e379111b9508e97d29 Mon Sep 17 00:00:00 2001 From: JannikNordmeyer <93387255+JannikNordmeyer@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:10:52 +0200 Subject: [PATCH 14/22] Implemented Function for Generating Sublattice from Generators. (#140) --- src/main/clojure/conexp/fca/lattices.clj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/clojure/conexp/fca/lattices.clj b/src/main/clojure/conexp/fca/lattices.clj index 3d9e43ac3..4dcd0dd67 100644 --- a/src/main/clojure/conexp/fca/lattices.clj +++ b/src/main/clojure/conexp/fca/lattices.clj @@ -461,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 From 47af04cc11b4c4a7cb8e7422199d7f241cc89021 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Thu, 6 Jun 2024 11:45:38 +0200 Subject: [PATCH 15/22] Corrected typo in generated-sublattice --- src/main/clojure/conexp/fca/lattices.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/fca/lattices.clj b/src/main/clojure/conexp/fca/lattices.clj index 4dcd0dd67..5312e146c 100644 --- a/src/main/clojure/conexp/fca/lattices.clj +++ b/src/main/clojure/conexp/fca/lattices.clj @@ -468,7 +468,7 @@ (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 + (if (= X X-new) (make-lattice X lat-meet lat-join) (recur X-new))))) ) From b4151f9e5bfb4c46b5a686319b7f363d85ff6355 Mon Sep 17 00:00:00 2001 From: JannikNordmeyer <93387255+JannikNordmeyer@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:46:50 +0200 Subject: [PATCH 16/22] Completed AUTHORS.md . (#144) --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 332175d75..5d80c6073 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -19,3 +19,4 @@ Additional Contributors are * Maximilian Stubbemann (concept-robustness) * Anselm von Wangenheim (DimDraw) * Johannes Wollbold (bug reports, feature requests) +* Jannik Nordmeyer (Metric Contexts, Causal Implications) From 17aedf332f4a13dd3382d56301f527c45e788eda Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Thu, 6 Jun 2024 12:24:30 +0200 Subject: [PATCH 17/22] Typos etc. --- .github/workflows/update-deps-lock.yaml | 1 - deps-lock.json | 3 +-- flake.lock | 1 - flake.nix | 4 +--- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/update-deps-lock.yaml b/.github/workflows/update-deps-lock.yaml index d772c5599..5a0dd72d9 100644 --- a/.github/workflows/update-deps-lock.yaml +++ b/.github/workflows/update-deps-lock.yaml @@ -10,7 +10,6 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: DeterminateSystems/nix-installer-action@v4 - uses: DeterminateSystems/magic-nix-cache-action@v2 - name: Update deps-lock diff --git a/deps-lock.json b/deps-lock.json index b26b4421a..f85e54e16 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -93,7 +93,6 @@ "hash": "sha256-ZIsMIEuk8EDcQ8KfguRHHstmdWCK+wuzGp7BxCYTenQ=" }, { - "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-GCU/PtxlxREIczFMtETc+xxGGK+y7c+hZjeTGY/M/dk=" @@ -458,7 +457,7 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-2OgLA0KFMl6QX1RkmhWYtoe5pKmaOk9LlO7TWXyyEEg=" }, - + { "mvn-path": "hiccup/hiccup/1.0.5/hiccup-1.0.5.jar", "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-D9Sn5HRh29rnILaEsZcWTrtpO7Y1jI4UZ/+nhwdsB/w=" diff --git a/flake.lock b/flake.lock index ace868401..1b39c426f 100644 --- a/flake.lock +++ b/flake.lock @@ -189,7 +189,6 @@ "type": "github" } }, - "root": { "inputs": { "clj-nix": "clj-nix", diff --git a/flake.nix b/flake.nix index 693390e02..e7c9c5058 100644 --- a/flake.nix +++ b/flake.nix @@ -35,6 +35,7 @@ ]; channels.nixpkgs.overlaysBuilder = channels: [inputs.clj-nix.overlays.default]; + overlays.default = final: prev: { inherit (self.packages."${final.system}") conexp-clj; }; @@ -76,7 +77,6 @@ doCheck = true; checkPhase = "lein test"; }; - in rec { packages = { conexp-clj = conexp; @@ -94,12 +94,10 @@ deps = mk-deps-cache {lock-file = ./deps-lock.json;}; in mkApp { - drv = writeShellScriptBin "conexp-clj-tests" '' lein test $@ ''; }; - }; checks = { From 4a745b9352c43d7df4b66fe935c23cd51660bbf8 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Thu, 6 Jun 2024 15:14:58 +0200 Subject: [PATCH 18/22] Ugraded dependencies --- project.clj | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/project.clj b/project.clj index 4edc68c30..db7dba4f1 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.2-SNAPSHOT" +(defproject conexp-clj "2.5.0-SNAPSHOT" :min-lein-version "2.0.0" :description "A ConExp rewrite in clojure -- and so much more ..." @@ -15,16 +15,16 @@ :scm {:url "git@github.com:tomhanika/conexp-clj.git"} :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} - :dependencies [[org.clojure/clojure "1.10.1"] - [org.clojure/core.async "1.6.673"] - [org.clojure/data.int-map "1.2.1"] - [org.clojure/data.json "2.4.0"] + :dependencies [[org.clojure/clojure "1.11.3"] + [org.clojure/core.async "1.6.681"] + [org.clojure/data.int-map "1.3.0"] + [org.clojure/data.json "2.5.0"] [org.clojure/data.xml "0.0.8"] - [org.clojure/math.combinatorics "0.2.0"] - [org.clojure/math.numeric-tower "0.0.5"] - [org.clojure/tools.cli "1.0.219"] + [org.clojure/math.combinatorics "0.3.0"] + [org.clojure/math.numeric-tower "0.1.0"] + [org.clojure/tools.cli "1.1.230"] [org.apache.commons/commons-math "2.2"] - [org.clojure/algo.generic "0.1.3"] + [org.clojure/algo.generic "1.0.1"] [seesaw "1.5.0"] [reply "0.5.1" :exclusions [org.clojure/clojure @@ -33,27 +33,27 @@ [aysylu/loom "1.0.2"] [rolling-stones "1.0.3" :exclusions [org.clojure/clojure]] - [clj-http "3.12.3"] + [clj-http "3.13.0"] [clojure-complete "0.2.5"] - [ring/ring-devel "1.10.0"] - [ring/ring-core "1.10.0"] + [ring/ring-devel "1.12.1"] + [ring/ring-core "1.12.1"] [ring/ring-json "0.5.1"] [ring-cors "0.1.13"] - [http-kit "2.6.0"] + [http-kit "2.8.0"] [org.apache.commons/commons-math3 "3.6.1"] - [luposlip/json-schema "0.4.1"] - [org.clojure/data.csv "1.0.1"]] + [luposlip/json-schema "0.4.5"] + [org.clojure/data.csv "1.1.0"]] :profiles {:uberjar {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"] - [nrepl/nrepl "1.0.0"]] + [nrepl/nrepl "1.1.2"]] :plugins [[lein-aot-order "0.1.0"]] :aot :order} :dev {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"] - [nrepl/nrepl "1.0.0"] - [com.clojure-goes-fast/clj-async-profiler "1.0.5"]] + [nrepl/nrepl "1.1.2"] + [com.clojure-goes-fast/clj-async-profiler "1.2.2"]] :plugins [[lein-aot-order "0.1.0"]] :javac-options ["-Xlint:deprecation" "-Xlint:unchecked"] :jvm-opts ["-Djdk.attach.allowAttachSelf"]}} From 61df1a900096147b4ff6346f50dec53a90b6c6c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:49:12 +0200 Subject: [PATCH 19/22] Update deps-lock.json (#146) Co-authored-by: tomhanika --- deps-lock.json | 448 +++++++++++++++++++++++++++---------------------- 1 file changed, 249 insertions(+), 199 deletions(-) diff --git a/deps-lock.json b/deps-lock.json index f85e54e16..38f535a65 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -33,9 +33,9 @@ "hash": "sha256-l3PHeIYLYeUXTVeUfJQIGwaJY6aaL7ABmhGpGuN9l8w=" }, { - "mvn-path": "cheshire/cheshire/5.11.0/cheshire-5.11.0.pom", + "mvn-path": "cheshire/cheshire/5.13.0/cheshire-5.13.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-63Enn5Ak0AUpOBdVZKScWG8P/QAnWaVNPmIPS891Leo=" + "hash": "sha256-LaaIDz3x3lGTb/GfarGHaT5MAc3MVCiFWE5Z85qs6z4=" }, { "mvn-path": "cheshire/cheshire/5.8.1/cheshire-5.8.1.pom", @@ -43,14 +43,14 @@ "hash": "sha256-4ZmTVCJYkBoGvnALoB14QOInNNXqsoLWJN8ceAPQ2TU=" }, { - "mvn-path": "clj-http/clj-http/3.12.3/clj-http-3.12.3.jar", + "mvn-path": "clj-http/clj-http/3.13.0/clj-http-3.13.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-OJ0pdhKo6KzGbF0NZKbJO99lDe1iXG9q4Wk7kzaKkaU=" + "hash": "sha256-Il8ZfckEYBvydmkCnwNShFgig/Fn+m/vxTxmf2IR154=" }, { - "mvn-path": "clj-http/clj-http/3.12.3/clj-http-3.12.3.pom", + "mvn-path": "clj-http/clj-http/3.13.0/clj-http-3.13.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-Jx0VRYS9iV/S4w9jEVAE8ce4xySq6tLLKpl8XFWEBfA=" + "hash": "sha256-yFuCMULwudLM/QEWomf5Y4fRZgLryoHp5rX0jP2kef8=" }, { "mvn-path": "clj-http/clj-http/3.9.1/clj-http-3.9.1.pom", @@ -72,11 +72,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-mvUCJmgV0+Yzk/30ZKhSkSP2OgGqU8lK7kcAjNbtOq0=" }, - { - "mvn-path": "clj-tuple/clj-tuple/0.2.2/clj-tuple-0.2.2.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-WNv3Pai8403EmN5sNH+HLvmPMBo6KdyVeX1k+A5ra0c=" - }, { "mvn-path": "clj-tuple/clj-tuple/0.2.2/clj-tuple-0.2.2.pom", "mvn-repo": "https://repo.clojars.org/", @@ -93,14 +88,14 @@ "hash": "sha256-ZIsMIEuk8EDcQ8KfguRHHstmdWCK+wuzGp7BxCYTenQ=" }, { - "mvn-path": "com/clojure-goes-fast/clj-async-profiler/1.0.5/clj-async-profiler-1.0.5.jar", + "mvn-path": "com/clojure-goes-fast/clj-async-profiler/1.2.2/clj-async-profiler-1.2.2.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-GCU/PtxlxREIczFMtETc+xxGGK+y7c+hZjeTGY/M/dk=" + "hash": "sha256-qMHpvYHP4c43Kge18EoI7cQeHCMzEcTE7Ua6R7nWWkM=" }, { - "mvn-path": "com/clojure-goes-fast/clj-async-profiler/1.0.5/clj-async-profiler-1.0.5.pom", + "mvn-path": "com/clojure-goes-fast/clj-async-profiler/1.2.2/clj-async-profiler-1.2.2.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-ZVZ+y4rPUVaTwStPlBe0Gr6NKhwjOioqlWZaM8xMn04=" + "hash": "sha256-fnjS0rxK05hQ2e4p1bvLA1EVrLrz5YfA4+FpfydAGa8=" }, { "mvn-path": "com/damnhandy/handy-uri-templates/2.1.8/handy-uri-templates-2.1.8.jar", @@ -123,9 +118,9 @@ "hash": "sha256-Mlo8lNkkrw9s6sykDp6IehiIZMFT+fw0zpxPAT2ogo4=" }, { - "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.13.3/jackson-core-2.13.3.pom", + "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.17.0/jackson-core-2.17.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-PTyORDm/LbQ3YKYSB9/JV62ZpZt1LMhHaZC0neAM3vw=" + "hash": "sha256-lR2zV/nEmlyId8CJYMSoroa9jJqohwYmZL0E96/+6pU=" }, { "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.9.6/jackson-core-2.9.6.pom", @@ -143,9 +138,9 @@ "hash": "sha256-Do9DC1WFpvbgxEIJCu9aWFfwz0HY6NptCYcI9s/SEZs=" }, { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.13.3/jackson-dataformat-cbor-2.13.3.pom", + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.17.0/jackson-dataformat-cbor-2.17.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-KEaSxtltGPDKwjXCipsWe9y5IKEOt2suFX3l4WTq3Aw=" + "hash": "sha256-Niz1VWDxh5GL5n6L0ej33T5VKZU/uPKNv1x/m8MInQE=" }, { "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.9.6/jackson-dataformat-cbor-2.9.6.pom", @@ -163,9 +158,9 @@ "hash": "sha256-UQrdLrPqpS5nWh86riCWsSziu7AuJrjyifFTgjNeBU8=" }, { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.13.3/jackson-dataformat-smile-2.13.3.pom", + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.17.0/jackson-dataformat-smile-2.17.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-YkR6KghMq4L5Yb4fPulnJu+Wj+Q9qbb7rhPKJXEMzfw=" + "hash": "sha256-5TCGlsx1wvikpUtqajPIVfLI1MpDA09HDfGBoxJv9Ag=" }, { "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.9.6/jackson-dataformat-smile-2.9.6.pom", @@ -178,9 +173,9 @@ "hash": "sha256-9wOYydK1e1piCIbVmEC0x8KgzQ8+aw4N5U/CtLxDZkw=" }, { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformats-binary/2.13.3/jackson-dataformats-binary-2.13.3.pom", + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformats-binary/2.17.0/jackson-dataformats-binary-2.17.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-ukW6RRSBY0/H0kJoGuof5ANzhFPc5LYBJ+tkMSo1KTY=" + "hash": "sha256-En8herLMwy5yGiCNxdGNERJq+Q+tvdeL/Hjq4fcT/mY=" }, { "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformats-binary/2.9.6/jackson-dataformats-binary-2.9.6.pom", @@ -193,9 +188,9 @@ "hash": "sha256-fj71bOHtu3I/f8NxL2yGifS8BvVkElx1ptZY0CO97FY=" }, { - "mvn-path": "com/fasterxml/jackson/jackson-base/2.13.3/jackson-base-2.13.3.pom", + "mvn-path": "com/fasterxml/jackson/jackson-base/2.17.0/jackson-base-2.17.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-ctZykYdsY+GJa8fY/3mQM9OkuQKQIBEEiK+clzFe2Tk=" + "hash": "sha256-w9lEq1Kiy2/0VJ3O6wTUIajeYyo8yl3UVPq8f1KsMeI=" }, { "mvn-path": "com/fasterxml/jackson/jackson-base/2.9.6/jackson-base-2.9.6.pom", @@ -208,9 +203,9 @@ "hash": "sha256-olDkpgIFTfr57pZVr7fWA1OSJslzJ78jfs2LPhcuuQ8=" }, { - "mvn-path": "com/fasterxml/jackson/jackson-bom/2.13.3/jackson-bom-2.13.3.pom", + "mvn-path": "com/fasterxml/jackson/jackson-bom/2.17.0/jackson-bom-2.17.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-32dbg7bKunYC+0fXXUu1E8KvTAphVdfjl8DsDzQRLHU=" + "hash": "sha256-SWSsYtWw5Ne/Vuz4sscC+pkUGCpfwtLnZvTPdoZP0qU=" }, { "mvn-path": "com/fasterxml/jackson/jackson-bom/2.9.6/jackson-bom-2.9.6.pom", @@ -223,9 +218,9 @@ "hash": "sha256-pQ24CCnE+JfG0OfpVHLLtDsOvs4TWmjjnCe4pv4z5IE=" }, { - "mvn-path": "com/fasterxml/jackson/jackson-parent/2.13/jackson-parent-2.13.pom", + "mvn-path": "com/fasterxml/jackson/jackson-parent/2.17/jackson-parent-2.17.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-K7qJl4Fyrx7/y00UPQmSGj8wgspNzxIrHe2Yv1WyrVc=" + "hash": "sha256-rubeSpcoOwQOQ/Ta1XXnt0eWzZhNiSdvfsdWc4DIop0=" }, { "mvn-path": "com/fasterxml/jackson/jackson-parent/2.9.1.1/jackson-parent-2.9.1.1.pom", @@ -243,9 +238,9 @@ "hash": "sha256-yD+PRd/cqNC2s2YcYLP4R4D2cbEuBvka1dHBodH5Zug=" }, { - "mvn-path": "com/fasterxml/oss-parent/43/oss-parent-43.pom", + "mvn-path": "com/fasterxml/oss-parent/58/oss-parent-58.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-5VhcwcNwebLjgXqJl5RXNvFYgxhE1Z0OTTpFsnYR+SY=" + "hash": "sha256-VnDmrBxN3MnUE8+HmXpdou+qTSq+Q5Njr57xAqCgnkA=" }, { "mvn-path": "com/fifesoft/rsyntaxtextarea/2.5.6/rsyntaxtextarea-2.5.6.jar", @@ -258,14 +253,14 @@ "hash": "sha256-ngtNeDGxrM4KT2ejNpUAFVGrRIPfECewJU8oAp9ig/k=" }, { - "mvn-path": "com/github/erosb/everit-json-schema/1.14.1/everit-json-schema-1.14.1.jar", + "mvn-path": "com/github/erosb/everit-json-schema/1.14.4/everit-json-schema-1.14.4.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-EgG9+SC3fZoyOAliWOdj6zJmIktwEt/oxN68PcI4gUU=" + "hash": "sha256-riUor1WfL/w57c9Ge/NmPOME72X5YHdg7+jy+wD2OxE=" }, { - "mvn-path": "com/github/erosb/everit-json-schema/1.14.1/everit-json-schema-1.14.1.pom", + "mvn-path": "com/github/erosb/everit-json-schema/1.14.4/everit-json-schema-1.14.4.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-KaOHMqFp9jYtygLvp6dkDWB9WzQKPAfNTc7qpQSxyW8=" + "hash": "sha256-4ZUP83hUjXjnEZpX5DCMJkRVCidBQ1MPvfww2kBaOQc=" }, { "mvn-path": "com/google/javascript/closure-compiler-parent/v20151015/closure-compiler-parent-v20151015.pom", @@ -323,14 +318,19 @@ "hash": "sha256-wecUDR3qj981KLwePFRErAtUEpcxH0X5gGwhPsPumhA=" }, { - "mvn-path": "commons-codec/commons-codec/1.15/commons-codec-1.15.jar", + "mvn-path": "commons-codec/commons-codec/1.15/commons-codec-1.15.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-s+n21jp5AQm/DQVmEfvtHPaQVYJt7+uYlKcTadJG7WM=" + "hash": "sha256-yG7hmKNaNxVIeGD0Gcv2Qufk2ehxR3eUfb5qTjogq1g=" }, { - "mvn-path": "commons-codec/commons-codec/1.15/commons-codec-1.15.pom", + "mvn-path": "commons-codec/commons-codec/1.16.1/commons-codec-1.16.1.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-yG7hmKNaNxVIeGD0Gcv2Qufk2ehxR3eUfb5qTjogq1g=" + "hash": "sha256-7Ie/tV8iy9GyHiGQ7toosrMS7SpDHuSfvcwBgS0EpeQ=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.16.1/commons-codec-1.16.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-uCbd2S+dfMZDcaAvoIMMFU1nyYNw6lSi0ZbnLrWQrSg=" }, { "mvn-path": "commons-codec/commons-codec/1.6/commons-codec-1.6.pom", @@ -373,19 +373,24 @@ "hash": "sha256-iFF8Wh+NYqr0uLPiMSYVucJ5XbhuaVvzcuNiZ0zz9gA=" }, { - "mvn-path": "commons-fileupload/commons-fileupload/1.5/commons-fileupload-1.5.jar", + "mvn-path": "commons-io/commons-io/2.13.0/commons-io-2.13.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-Ufez3LTlDHZimU2i9HIxUZ/5lwelx/t7BfTE06FyjBQ=" + "hash": "sha256-2z/tZMLhd06/1rGnSQN3MrFJuREd1+a5hfCN2lVHBDk=" }, { - "mvn-path": "commons-fileupload/commons-fileupload/1.5/commons-fileupload-1.5.pom", + "mvn-path": "commons-io/commons-io/2.15.1/commons-io-2.15.1.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-zHIPHgWDV52f5Tk8iE7kbQ10+Z0fm6AXsXKzHqGJ4rE=" + "hash": "sha256-Fxoa+CtnWetXQLO4gJrKgBE96vEVMDby9ERZAd/T+R0=" }, { - "mvn-path": "commons-io/commons-io/2.11.0/commons-io-2.11.0.pom", + "mvn-path": "commons-io/commons-io/2.16.1/commons-io-2.16.1.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-LgFv1+MkS18sIKytg02TqkeQSG7h5FZGQTYaPoMe71k=" + "hash": "sha256-9B97qs1xaJZEes6XWGIfYsHGsKkdiazuSI2ib8R3yE8=" + }, + { + "mvn-path": "commons-io/commons-io/2.16.1/commons-io-2.16.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-V3fSkiUceJXASkxXAVaD7Ds1OhJIbJs+cXjpsLPDj/8=" }, { "mvn-path": "commons-io/commons-io/2.2/commons-io-2.2.pom", @@ -397,16 +402,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-DCOGOJOiKR9aev29jRWSOzlIr9h+Vj+jQc3Pbq4zimA=" }, - { - "mvn-path": "commons-io/commons-io/2.8.0/commons-io-2.8.0.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-AvKR5dEkPcFDSW48u7QKHO1Hqljy1jPT44eAzQaNUHQ=" - }, - { - "mvn-path": "commons-io/commons-io/2.8.0/commons-io-2.8.0.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-18hkGjfW5282+56B/BQg4moJ1j+jLwD3R2TeBnyoNH0=" - }, { "mvn-path": "commons-logging/commons-logging/1.2/commons-logging-1.2.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -468,14 +463,14 @@ "hash": "sha256-OGBZ1P4rXyKX6peRwllnGDM6eQAIgsOfyZqXSEPA5VI=" }, { - "mvn-path": "http-kit/http-kit/2.6.0/http-kit-2.6.0.jar", + "mvn-path": "http-kit/http-kit/2.8.0/http-kit-2.8.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-wUyefSAkSIDL7oATYbOEo/HeIfX3556wSAOskuW0qjQ=" + "hash": "sha256-xJbmqG/sRrN0PcOZ7chy4USW2IioTQTByE3qoe0gg60=" }, { - "mvn-path": "http-kit/http-kit/2.6.0/http-kit-2.6.0.pom", + "mvn-path": "http-kit/http-kit/2.8.0/http-kit-2.8.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-mzngH1mQUDDBHh5BwOgEZ+jhv2Rc7n2gl2hVz/W2mSM=" + "hash": "sha256-RLTLjpPU9rJiwE7Qdx1w3WbnbUXX/HVYIGcaYmVcVDk=" }, { "mvn-path": "j18n/j18n/1.0.2/j18n-1.0.2.jar", @@ -533,14 +528,14 @@ "hash": "sha256-Hpa5ikINI0zKEBDuoCGWToy8mAK1ohdqp5nvBDioFD8=" }, { - "mvn-path": "luposlip/json-schema/0.4.1/json-schema-0.4.1.jar", + "mvn-path": "luposlip/json-schema/0.4.5/json-schema-0.4.5.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-pgkJvS1IZEGc0g3FEJpu3eI+Uldyh2cLP4cFXlk9ftg=" + "hash": "sha256-E2HMz6u8KT5ELgYXvrHmZ3l2Cg7S0Co56uPcCzMA/LM=" }, { - "mvn-path": "luposlip/json-schema/0.4.1/json-schema-0.4.1.pom", + "mvn-path": "luposlip/json-schema/0.4.5/json-schema-0.4.5.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-0jmKkRGLe9T7zNhtSiQZSsP6qWFfshG9LHw6MzWL1JI=" + "hash": "sha256-rvnCCfE47wipIfFoVJcDkucd/DMsa6BdHgqMwRzBzrk=" }, { "mvn-path": "net/cgrand/parsley/0.9.2/parsley-0.9.2.jar", @@ -588,14 +583,14 @@ "hash": "sha256-GKe1bWh2slPlItP9+wTBq4ieNiDr9Znyic2iDSOUdD0=" }, { - "mvn-path": "nrepl/nrepl/1.0.0/nrepl-1.0.0.jar", + "mvn-path": "nrepl/nrepl/1.1.2/nrepl-1.1.2.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-owuXNP8uY582Xw11U7SHcwbypUPPAh4pnRW/HqvWpbs=" + "hash": "sha256-yMxgXaGmw+0iMSwldCkV2pZZMLoXUhupeKlpuIeHnkE=" }, { - "mvn-path": "nrepl/nrepl/1.0.0/nrepl-1.0.0.pom", + "mvn-path": "nrepl/nrepl/1.1.2/nrepl-1.1.2.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-dHN5LZGfjodYUES5nySqLqFVNCBgRI2opHhGvkXz2nI=" + "hash": "sha256-2M+lY/XJqCw28xwl3Vp94VKJAr2QmAzmFD6ga9v9uO4=" }, { "mvn-path": "ns-tracker/ns-tracker/0.4.0/ns-tracker-0.4.0.jar", @@ -647,6 +642,11 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-PkkDcXSCC70N9jQgqXclWIY5iVTCoGKR+mH3J6w1s3c=" }, + { + "mvn-path": "org/apache/apache/31/apache-31.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-VV0MnqppwEKv+SSSe5OB6PgXQTbTVe6tRFIkRS5ikcw=" + }, { "mvn-path": "org/apache/apache/7/apache-7.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -657,6 +657,21 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-SUbmClR8jtpp87wjxbbw2tz4Rp6kmx0dp940rs/PGN0=" }, + { + "mvn-path": "org/apache/commons/commons-fileupload2-core/2.0.0-M1/commons-fileupload2-core-2.0.0-M1.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-aPumFRznTMztKyudp8asY5gDFDFmVzBSudlcyURbgh8=" + }, + { + "mvn-path": "org/apache/commons/commons-fileupload2-core/2.0.0-M1/commons-fileupload2-core-2.0.0-M1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-P238Oe+/9c1XC1Luqb27XH8VCbCnOzUnB7REXwdWnko=" + }, + { + "mvn-path": "org/apache/commons/commons-fileupload2/2.0.0-M1/commons-fileupload2-2.0.0-M1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-KVM5MYJNx4Iu09XWp0fCeEcYoxZVo/Ifx9gkWcFvtq8=" + }, { "mvn-path": "org/apache/commons/commons-math/2.2/commons-math-2.2.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -743,9 +758,24 @@ "hash": "sha256-ddvo806Y5MP/QtquSi+etMvNO18QR9VEYKzpBtu0UC4=" }, { - "mvn-path": "org/apache/commons/commons-parent/56/commons-parent-56.pom", + "mvn-path": "org/apache/commons/commons-parent/58/commons-parent-58.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-LUsS4YiZBjq9fHUni1+pejcp2Ah4zuy2pA2UbpwNVZA=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/65/commons-parent-65.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-VgxwUd3HaOE3LkCHlwdk5MATkDxdxutSwph3Nw2uJpQ=" + "hash": "sha256-bPNJX8LmrJE6K38uA/tZCPs/Ip+wbTNY3EVnjVrz424=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/66/commons-parent-66.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-SP1tyEblax9AhmDRY+dTAPnjhLtjvkgqgIKiHXKo25w=" + }, + { + "mvn-path": "org/apache/commons/commons-parent/69/commons-parent-69.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-1Q2pw5vcqCPWGNG0oDtz8ZZJf8uGFv0NpyfIYjWSqbs=" }, { "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.3/httpasyncclient-4.1.3.pom", @@ -753,24 +783,24 @@ "hash": "sha256-PaAuVYrN/VVGzpb3k4287Bvjjg54PuJMYqz9Slc1Ysk=" }, { - "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.4/httpasyncclient-4.1.4.jar", + "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.5/httpasyncclient-4.1.5.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-UOmBqOVnoW69rRBGBbFWVAqGNFn6EnuLpkfzEN/IPvg=" + "hash": "sha256-DBh3SJqdG6T6UPbPyrEdESNhiFjLMdVq+qta/dUGTZk=" }, { - "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.4/httpasyncclient-4.1.4.pom", + "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.5/httpasyncclient-4.1.5.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-NPJO+Ya4nVG4CgkDh6o9DIJNQPCHrlzPrUf/PCYsLkc=" + "hash": "sha256-beSudsiSFXUj62TD2bFNvHSnWGNrQO5MxkVqwRmYKXU=" }, { - "mvn-path": "org/apache/httpcomponents/httpclient-cache/4.5.13/httpclient-cache-4.5.13.jar", + "mvn-path": "org/apache/httpcomponents/httpclient-cache/4.5.14/httpclient-cache-4.5.14.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-Zs797nR1mFJWr2gL86581dfULo/euTmmJ3ki4b3u1Do=" + "hash": "sha256-UyTSy8LTEcn5G4K8vHRuwqKfH1t0Q5WlD/OvuHPbHO4=" }, { - "mvn-path": "org/apache/httpcomponents/httpclient-cache/4.5.13/httpclient-cache-4.5.13.pom", + "mvn-path": "org/apache/httpcomponents/httpclient-cache/4.5.14/httpclient-cache-4.5.14.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-o1h75UcWw7gKMonBHfhlqWK8cr5HiRReQgxpSL9FpKQ=" + "hash": "sha256-54gC/9b1S6gPxKO9TXF/eBH8vduMBphcmFHZ5fCiY6Y=" }, { "mvn-path": "org/apache/httpcomponents/httpclient-cache/4.5.5/httpclient-cache-4.5.5.pom", @@ -778,14 +808,19 @@ "hash": "sha256-5fDcW+r/uQjJuhuA9X3aTJDpHWiV2DIXsYGuq+Zd11M=" }, { - "mvn-path": "org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar", + "mvn-path": "org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-b+kCalZsalABYIzz/DIZZkH2weXhmG0QN8zb1fMe90M=" + "hash": "sha256-eOua2nSSn81j0HrcT0kjaEGkXMKdX4F79FgB9RP9fmw=" }, { - "mvn-path": "org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.pom", + "mvn-path": "org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-eOua2nSSn81j0HrcT0kjaEGkXMKdX4F79FgB9RP9fmw=" + "hash": "sha256-yLx+HFGm1M5y9A0uu6vxxLaL/nbnMhBLBDgbSTR46dY=" + }, + { + "mvn-path": "org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-8YNVr0z4CopO8E69dCpH6Qp+rwgMclsgldvE/F2977c=" }, { "mvn-path": "org/apache/httpcomponents/httpclient/4.5.3/httpclient-4.5.3.pom", @@ -797,26 +832,26 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-2zsBmOEfOqX6UTEMkVuBjBNKjLy4L8gd35W6IxOGJiY=" }, - { - "mvn-path": "org/apache/httpcomponents/httpclient/4.5.6/httpclient-4.5.6.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-fvwSQec+f7smi/0zJC0R69PKBwYdfYXyli3DKg8LiFU=" - }, { "mvn-path": "org/apache/httpcomponents/httpcomponents-asyncclient/4.1.3/httpcomponents-asyncclient-4.1.3.pom", "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-heaQHESTgHNAodUcJWVc/6/KMQbTtb1Gf64gq8E56xY=" }, { - "mvn-path": "org/apache/httpcomponents/httpcomponents-asyncclient/4.1.4/httpcomponents-asyncclient-4.1.4.pom", + "mvn-path": "org/apache/httpcomponents/httpcomponents-asyncclient/4.1.5/httpcomponents-asyncclient-4.1.5.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-6WXrNprx2LWmi31LeGHVOlJhaugD4TFi6N+EImFwAYA=" + "hash": "sha256-6QGB4oDAJx/zE3QTQ+bwVAX66IJwL0WkzibewBbNnJ8=" }, { "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.13/httpcomponents-client-4.5.13.pom", "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-nLpZTAjbcnHQwg6YRdYiuznmlYORC0Xn1d+C9gWNTdk=" }, + { + "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.14/httpcomponents-client-4.5.14.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-W60d5PEBRHZZ+J0ImGjMutZKaMxQPS1lQQtR9pBKoGE=" + }, { "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.3/httpcomponents-client-4.5.3.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -828,24 +863,19 @@ "hash": "sha256-FEXQEhWPlBcxpgYsfqt0AJPqJ0W0a1TeI2s/d4fpm/M=" }, { - "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.6/httpcomponents-client-4.5.6.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-sEK0HyOR7bANNff05Qmu0hI2SMHSRs5Y0Pe5Bcn+H3M=" - }, - { - "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.10/httpcomponents-core-4.4.10.pom", + "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.13/httpcomponents-core-4.4.13.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-YelCfUvjJsMHp/FrqCjRyzsUcTybBPyLqZKljzdsMTY=" + "hash": "sha256-xVTnAI5FF8fvVOAFzIt09Mh6VKDqLG9Xvl0Fad9Rk2s=" }, { - "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.13/httpcomponents-core-4.4.13.pom", + "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.15/httpcomponents-core-4.4.15.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-xVTnAI5FF8fvVOAFzIt09Mh6VKDqLG9Xvl0Fad9Rk2s=" + "hash": "sha256-YNQ3J6YXSATIrhf5PpzGMuR/PEEQpMVLn6/IzZqMpQk=" }, { - "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.14/httpcomponents-core-4.4.14.pom", + "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.16/httpcomponents-core-4.4.16.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-IJ7ZMctXmYJS3+AnyqnAOtpiBhNkIylnkTEWX4scutE=" + "hash": "sha256-8tdaLC1COtGFOb8hZW1W+IpAkZRKZi/K8VnVrig9t/c=" }, { "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.6/httpcomponents-core-4.4.6.pom", @@ -873,14 +903,14 @@ "hash": "sha256-JlbH5Avb5rb5WHmPfWkYtQtUTfDiO1LONzG5zMILX4w=" }, { - "mvn-path": "org/apache/httpcomponents/httpcore-nio/4.4.10/httpcore-nio-4.4.10.jar", + "mvn-path": "org/apache/httpcomponents/httpcore-nio/4.4.15/httpcore-nio-4.4.15.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-3r7n6VcsAqFs4Mqk9WWp7OsSkNM816HjKXCHvUZ9r/Q=" + "hash": "sha256-RO4+231eltPm0AJjyDivI90s5nVUEpcU6jCuRHupW5I=" }, { - "mvn-path": "org/apache/httpcomponents/httpcore-nio/4.4.10/httpcore-nio-4.4.10.pom", + "mvn-path": "org/apache/httpcomponents/httpcore-nio/4.4.15/httpcore-nio-4.4.15.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-pMmVtzjRBLdcyLEWTbYAUzFWwEfsy0yO5dknshoX7HM=" + "hash": "sha256-qCfxVd4Zzdjzd7hLOEuXFL5ubZUuY4XQ9NZDfCR/YqM=" }, { "mvn-path": "org/apache/httpcomponents/httpcore-nio/4.4.6/httpcore-nio-4.4.6.pom", @@ -888,24 +918,24 @@ "hash": "sha256-Slb+Yua6ijj46jhdhEM5IXm5XQ9WKMjpGJ2cwkj5enw=" }, { - "mvn-path": "org/apache/httpcomponents/httpcore/4.4.10/httpcore-4.4.10.pom", + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-xcEgZt8rO4iomiyGArgeqaYWJ+l25RKe6hiZ67rqOSs=" + "hash": "sha256-j4Etn6e3Kj1Kp/glJ4kypd80S0Km2DmJBYeUMaG/mpc=" }, { - "mvn-path": "org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.pom", + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.15/httpcore-4.4.15.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-j4Etn6e3Kj1Kp/glJ4kypd80S0Km2DmJBYeUMaG/mpc=" + "hash": "sha256-Kaz+qoqIu2IPw0Nxows9QDKNxaecx0kCz0RsCUPBvms=" }, { - "mvn-path": "org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar", + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-+VYgnkUMsdDFF3bfvSPlPp3Y25oSmO1itwvwlEumOyg=" + "hash": "sha256-bJs90UKgncRo4jrTmq1vdaDyuFElEERp8CblKkdORk8=" }, { - "mvn-path": "org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.pom", + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-VXFjmKl48QID+eJciu/AWA2vfwkHxu0K6tgexftrf9g=" + "hash": "sha256-PLrYSbNdrP5s7DGtraLGI8AmwyYRQbDSbux+OZxs1/o=" }, { "mvn-path": "org/apache/httpcomponents/httpcore/4.4.6/httpcore-4.4.6.pom", @@ -918,14 +948,14 @@ "hash": "sha256-bpS9d3vu3v+bXncM9lS1MDJXgQNLJ0bGMrEx7HStUTw=" }, { - "mvn-path": "org/apache/httpcomponents/httpmime/4.5.13/httpmime-4.5.13.jar", + "mvn-path": "org/apache/httpcomponents/httpmime/4.5.14/httpmime-4.5.14.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-BudU2ZJFuY3MKGDctD0g5zfWUNor8gd6EF9orMvVxcw=" + "hash": "sha256-1AEkPVxurpKKNxIbboGRWMjDLqBYR5PnKFu0iasqPRc=" }, { - "mvn-path": "org/apache/httpcomponents/httpmime/4.5.13/httpmime-4.5.13.pom", + "mvn-path": "org/apache/httpcomponents/httpmime/4.5.14/httpmime-4.5.14.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-k0GN8hCu7VBQJUjbzysXwPHZFEMDDnL+++7RZSscKN0=" + "hash": "sha256-4P7ErX/5JZwC3objM3YcmgPfmgW2Rjl+OjJdHDT4/rE=" }, { "mvn-path": "org/apache/httpcomponents/httpmime/4.5.5/httpmime-4.5.5.pom", @@ -948,30 +978,20 @@ "hash": "sha256-PMP2YNEnMFxecW50TPbfkp9u/TUVQk/GkSRf3mfcDgQ=" }, { - "mvn-path": "org/clojure/algo.generic/0.1.3/algo.generic-0.1.3.jar", + "mvn-path": "org/clojure/algo.generic/1.0.1/algo.generic-1.0.1.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-39kqKQba+lfi6hS6TMIjgsRHrID8TxKXxtAFMWxk/6o=" + "hash": "sha256-Tfi0a7S/+63wSdTsyktQ2GzzQkoEd5RGZspeLuhUjAw=" }, { - "mvn-path": "org/clojure/algo.generic/0.1.3/algo.generic-0.1.3.pom", + "mvn-path": "org/clojure/algo.generic/1.0.1/algo.generic-1.0.1.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-hOSMGjYRNiqj4f7glDMiknw8cBn8pUyq2v76iVuIHto=" + "hash": "sha256-p3jFxriL7TBTK9k6KbVWxUCEyZ3KQRALwXd2TARI6Pg=" }, { "mvn-path": "org/clojure/clojure/1.10.0/clojure-1.10.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-IA0uyoZlJAy8uu5IwAcyS3tE02YIDpWN8dIujd4kg4w=" }, - { - "mvn-path": "org/clojure/clojure/1.10.1/clojure-1.10.1.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-1Pb5kf2e0qWefqR3kBCzsGmiuQXzRjE2xCIBEGtK0ho=" - }, - { - "mvn-path": "org/clojure/clojure/1.10.1/clojure-1.10.1.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-EPevJPoORzOE7RqAPgCpB0KzwA+Q3jW2uxXosW3YOow=" - }, { "mvn-path": "org/clojure/clojure/1.10.3/clojure-1.10.3.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1002,6 +1022,16 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-IMRaGr7b2L4grvk2BQrjGgjBZ0CzL4dAuIOM3pb/y4o=" }, + { + "mvn-path": "org/clojure/clojure/1.11.3/clojure-1.11.3.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-nDBUCTKOK5boXdK160t1gQxnt2unCuTQ9t3pvPtVsbc=" + }, + { + "mvn-path": "org/clojure/clojure/1.11.3/clojure-1.11.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-DA2+Ge4NKpxXMQzr3dNWRD8NFlFMQmBHsGLjpXwNuK0=" + }, { "mvn-path": "org/clojure/clojure/1.2.0/clojure-1.2.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1042,11 +1072,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-X3KW7VH/cOurbSg/gDJCmQvXvadBn5QTLs9AajXJS38=" }, - { - "mvn-path": "org/clojure/clojure/1.8.0/clojure-1.8.0.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-/70SvB18eBvurz0rxfNBYNv0UCyUSjdjE0u6+XEAFuQ=" - }, { "mvn-path": "org/clojure/clojure/1.9.0/clojure-1.9.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1063,14 +1088,14 @@ "hash": "sha256-3NxQZHI5f265GqOAiE+3EtqBrtaqUyvAzh5o5Zp8wwI=" }, { - "mvn-path": "org/clojure/core.async/1.6.673/core.async-1.6.673.jar", + "mvn-path": "org/clojure/core.async/1.6.681/core.async-1.6.681.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-FoHdGIjHVAH0RLURyDU/vaPOsadgiBCiPd0l0QRfkHo=" + "hash": "sha256-LpFgEYw4HUGKscTTMLYyP/dreUfm4tmxzRvpUP0mnZ8=" }, { - "mvn-path": "org/clojure/core.async/1.6.673/core.async-1.6.673.pom", + "mvn-path": "org/clojure/core.async/1.6.681/core.async-1.6.681.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-S8rQJfFQpWa3+vdJPQSEy1momBySO3jFC88ORiHr3jg=" + "hash": "sha256-yAQ/MVp1Iq3ekrGGtD8w/UObGkQJ1uUMzGyiwG4UZLE=" }, { "mvn-path": "org/clojure/core.cache/1.0.225/core.cache-1.0.225.jar", @@ -1097,11 +1122,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-02cX+eu4IrMZezUSn9EwYc1xq3/Dbw9AOGx35qCPn1k=" }, - { - "mvn-path": "org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-Ox7E1vDo5Bv3aEJwkIO+s7Vq3zyC+aTxdMPadHdLOBw=" - }, { "mvn-path": "org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1128,24 +1148,24 @@ "hash": "sha256-F3i70Ti9GFkLgFS+nZGdG+toCfhbduXGKFtn1Ad9MA4=" }, { - "mvn-path": "org/clojure/data.csv/1.0.1/data.csv-1.0.1.jar", + "mvn-path": "org/clojure/data.csv/1.1.0/data.csv-1.1.0.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-gTpWu971A7Y1CswhrG9/YKLF2wi+wQpF60uRfmbtE04=" + "hash": "sha256-QShu1w9+zsoxj/Q+OS6zgIQ8McOEOmuubM1p6kk2CP0=" }, { - "mvn-path": "org/clojure/data.csv/1.0.1/data.csv-1.0.1.pom", + "mvn-path": "org/clojure/data.csv/1.1.0/data.csv-1.1.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-1cESYLiumGM6l3FThM8ENeAknrIhTi7VPaTMGMkToAE=" + "hash": "sha256-KHfxC8BhxEhppqmDjYAOguiaQu2aNZRvlhC6y11hH+k=" }, { - "mvn-path": "org/clojure/data.int-map/1.2.1/data.int-map-1.2.1.jar", + "mvn-path": "org/clojure/data.int-map/1.3.0/data.int-map-1.3.0.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-EEdYYvpJt4MszXZeV34rlcIYLgKR/N88IwAkLyi6sRQ=" + "hash": "sha256-Wr9sHv/5/pxjTtUH8rczqHTbz/Tuo0wiXrW7dJxv5lI=" }, { - "mvn-path": "org/clojure/data.int-map/1.2.1/data.int-map-1.2.1.pom", + "mvn-path": "org/clojure/data.int-map/1.3.0/data.int-map-1.3.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-bO0gykWPpQ4MVVm9tG5lmIiAHT8jHJESk2j44dtGZT8=" + "hash": "sha256-nyQxP9kcmoExNlSjZsfEneghnkGl0KT09oWdjQoIJfo=" }, { "mvn-path": "org/clojure/data.json/0.2.6/data.json-0.2.6.pom", @@ -1153,14 +1173,14 @@ "hash": "sha256-/Fe/UH6xwY8Hg0QgCh5Z6BDeZOz5Oa65s1tC/NMyt60=" }, { - "mvn-path": "org/clojure/data.json/2.4.0/data.json-2.4.0.jar", + "mvn-path": "org/clojure/data.json/2.5.0/data.json-2.5.0.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-7D8vmU4e7dQgMTxFK6VRjF9cl75RUt/tVlC8ZhFIat8=" + "hash": "sha256-Z3nDbuH5DXDzX+uHL7MXVqoOG4vWxl4QXngLq7SyVwQ=" }, { - "mvn-path": "org/clojure/data.json/2.4.0/data.json-2.4.0.pom", + "mvn-path": "org/clojure/data.json/2.5.0/data.json-2.5.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-pC6nDxe1F2Zq2EkqG/qRfeXe+se0fFFvbQ1NicJ4DPQ=" + "hash": "sha256-BPCy1pKdF8IvmGslALqAVYVYDeE7gGkke4JUYKhwQf4=" }, { "mvn-path": "org/clojure/data.priority-map/0.0.5/data.priority-map-0.0.5.jar", @@ -1228,24 +1248,24 @@ "hash": "sha256-3HT8YeCMPpABhqeoyvAAQj9EG9Q9XQ6aiRccOFj79qg=" }, { - "mvn-path": "org/clojure/math.combinatorics/0.2.0/math.combinatorics-0.2.0.jar", + "mvn-path": "org/clojure/math.combinatorics/0.3.0/math.combinatorics-0.3.0.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-sIzH3pFeSK/V2mGUuYgXxHqlCvZ3RyHMv7lKn0vWbWM=" + "hash": "sha256-LgxbPNLYMLWAQbc8Smhjll3tKtAFsV1pN7I4PFgwVdo=" }, { - "mvn-path": "org/clojure/math.combinatorics/0.2.0/math.combinatorics-0.2.0.pom", + "mvn-path": "org/clojure/math.combinatorics/0.3.0/math.combinatorics-0.3.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-9jURjWWI35hOA+nLRKjWuuL9x7nGrnub7QAqkoOex+U=" + "hash": "sha256-tGdhbEFzGWCLp1giumK/+zzH/HgL+5miSqsBAAwMXzk=" }, { - "mvn-path": "org/clojure/math.numeric-tower/0.0.5/math.numeric-tower-0.0.5.jar", + "mvn-path": "org/clojure/math.numeric-tower/0.1.0/math.numeric-tower-0.1.0.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-5NIRNXlWkT5DvcbD/csjExqWLjHX6G16GnTTFRm9+/8=" + "hash": "sha256-7N3shZdJql6G/lacPfrVXry9TROJFz0EHcrlVC6klm0=" }, { - "mvn-path": "org/clojure/math.numeric-tower/0.0.5/math.numeric-tower-0.0.5.pom", + "mvn-path": "org/clojure/math.numeric-tower/0.1.0/math.numeric-tower-0.1.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-w/lnehWxSPzvMAsGC29fn2fToTWUMhq+svIFpau+qZE=" + "hash": "sha256-7NxY/u8Oi9PL4/VyPIvlyPB/Hqow53eyidrY19YEcmk=" }, { "mvn-path": "org/clojure/pom.contrib/0.1.2/pom.contrib-0.1.2.pom", @@ -1268,14 +1288,14 @@ "hash": "sha256-EOzku1+YKQENwWVh9C67g7ry9HYFtR+RBbkvPKoIlxU=" }, { - "mvn-path": "org/clojure/spec.alpha/0.1.143/spec.alpha-0.1.143.pom", + "mvn-path": "org/clojure/pom.contrib/1.2.0/pom.contrib-1.2.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-CC36KSqOCpQKuRq+IKuWsjKTSbBAHlScNI5P+qUBOis=" + "hash": "sha256-CRbXpBVYuVAKQnyIb6KYJ6zlJZIGvjrTPmTilvwaYRE=" }, { - "mvn-path": "org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.jar", + "mvn-path": "org/clojure/spec.alpha/0.1.143/spec.alpha-0.1.143.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-/E6W7P803dKrf9BQ50rhN5NC7gnapgKNpSAkxd6DbMQ=" + "hash": "sha256-CC36KSqOCpQKuRq+IKuWsjKTSbBAHlScNI5P+qUBOis=" }, { "mvn-path": "org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.pom", @@ -1303,24 +1323,24 @@ "hash": "sha256-bY3hTDrIdXYMX/kJVi/5hzB3AxxquTnxyxOeFp/pB1g=" }, { - "mvn-path": "org/clojure/tools.analyzer.jvm/1.2.2/tools.analyzer.jvm-1.2.2.jar", + "mvn-path": "org/clojure/tools.analyzer.jvm/1.2.3/tools.analyzer.jvm-1.2.3.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-kQz/AjiTHtiIYstmWmd+ldk+hIDyIzIAiG0zHX7QDl4=" + "hash": "sha256-hjMu9int5Q4a2RDfuDXkzzQnBzz6spB+UrmrL7L2FBY=" }, { - "mvn-path": "org/clojure/tools.analyzer.jvm/1.2.2/tools.analyzer.jvm-1.2.2.pom", + "mvn-path": "org/clojure/tools.analyzer.jvm/1.2.3/tools.analyzer.jvm-1.2.3.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-EOGi60Q6PFfsGd7e8ylC63SbrmnyFZiI/lYLpnuwj0c=" + "hash": "sha256-2Pj5gFOW1A7yV95DQN/P6jGYKEhaqO+OXZFzthCwIws=" }, { - "mvn-path": "org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0.jar", + "mvn-path": "org/clojure/tools.analyzer/1.1.1/tools.analyzer-1.1.1.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-E2i2vDvd98OY1XhNEFSPRMTtLXwB6hBawO/enPXg3yE=" + "hash": "sha256-66Fn/KEXn7FWqOOL3C6dSd/0keUVCWW1SDm5Uvzu0GA=" }, { - "mvn-path": "org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0.pom", + "mvn-path": "org/clojure/tools.analyzer/1.1.1/tools.analyzer-1.1.1.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-NyBxL7knYaNclNDuQV1r8VhB70afBzZGd2h1553JtwY=" + "hash": "sha256-wnhQZ2xYc3An6H9k2yUte5Nr/+mGHukCL8/I6jtNbig=" }, { "mvn-path": "org/clojure/tools.cli/0.3.1/tools.cli-0.3.1.pom", @@ -1328,14 +1348,14 @@ "hash": "sha256-5Ra0m3LZ+cymDu5ojQR8aCD1YGNUEdVWZ5Juv2b6buM=" }, { - "mvn-path": "org/clojure/tools.cli/1.0.219/tools.cli-1.0.219.jar", + "mvn-path": "org/clojure/tools.cli/1.1.230/tools.cli-1.1.230.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-lGADrC8iiODAxYhhYk3z75gRHJ6Id+tAHKUozDd/l6k=" + "hash": "sha256-kWYwtTmkP/RotN0BbGKFfitMtdpmhvEpdYfN1DyhAs0=" }, { - "mvn-path": "org/clojure/tools.cli/1.0.219/tools.cli-1.0.219.pom", + "mvn-path": "org/clojure/tools.cli/1.1.230/tools.cli-1.1.230.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-v9jf44Bp4mJIzqRyQ9+Zvv/0mjGGzDyk1fNTefp9u3M=" + "hash": "sha256-v7Yh5LAaW4vOEWpgcIQNzdWUnomceEaNgRtuiqqf0cc=" }, { "mvn-path": "org/clojure/tools.namespace/0.2.11/tools.namespace-0.2.11.jar", @@ -1383,24 +1403,29 @@ "hash": "sha256-rvXugot8sUocWPRbn4oQ/zQMV2mSXqDvXDXR5J2SC+o=" }, { - "mvn-path": "org/json/json/20230227/json-20230227.jar", + "mvn-path": "org/json/json/20240303/json-20240303.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-PPbNaJLjLitMHDng9S9SSKL1s3ZG/fu3mma0a2GEFO0=" + }, + { + "mvn-path": "org/json/json/20240303/json-20240303.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-ntJnkdwthin9+KIH8a663LUNZBvmN2ZDEO9RwPc+Jps=" + "hash": "sha256-mUuvXYm/TKpc6WjkxOqxNcj5eD7PTSt7Dp0cik3fHk8=" }, { - "mvn-path": "org/json/json/20230227/json-20230227.pom", + "mvn-path": "org/junit/junit-bom/5.10.1/junit-bom-5.10.1.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-w5JpSWHKzw6oY/WCyHTfHYbrWRu/tSaqBY7/eULakHA=" + "hash": "sha256-IcSwKG9LIAaVd/9LIJeKhcEArIpGtvHIZy+6qzN7w/I=" }, { - "mvn-path": "org/junit/junit-bom/5.7.2/junit-bom-5.7.2.pom", + "mvn-path": "org/junit/junit-bom/5.10.2/junit-bom-5.10.2.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-zRSqqGmZH4ICHFhdVw0x/zQry6WLtEIztwGTdxuWSHs=" + "hash": "sha256-Fp3ZBKSw9lIM/+ZYzGIpK/6fPBSpifqSEgckzeQ6mWg=" }, { - "mvn-path": "org/junit/junit-bom/5.9.1/junit-bom-5.9.1.pom", + "mvn-path": "org/junit/junit-bom/5.9.3/junit-bom-5.9.3.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-sWPBz8j8H9WLRXoA1YbATEbphtdZBOnKVMA6l9ZbSWw=" + "hash": "sha256-TQMpzZ5y8kIOXKFXJMv+b/puX9KIg2FRYnEZD9w0Ltc=" }, { "mvn-path": "org/mozilla/rhino/1.7R5/rhino-1.7R5.jar", @@ -1452,6 +1477,26 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-oKFkiu2kvKpd0AvsWq77eLrZCreLstrwloU5M3haXDI=" }, + { + "mvn-path": "org/ring-clojure/ring-core-protocols/1.12.1/ring-core-protocols-1.12.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Va01tEXUgs/4JdPTJ6zwJt7nYBjwneKPN3d7aSruqSM=" + }, + { + "mvn-path": "org/ring-clojure/ring-core-protocols/1.12.1/ring-core-protocols-1.12.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-TfXVGi9iEyNgvowkN7I+IBfo+IaDoZdx802bFKRULS0=" + }, + { + "mvn-path": "org/ring-clojure/ring-websocket-protocols/1.12.1/ring-websocket-protocols-1.12.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-hN/rRt7NAqg+LqTjk9HYs3ip9q2d1zUFFf9Li6u9rbg=" + }, + { + "mvn-path": "org/ring-clojure/ring-websocket-protocols/1.12.1/ring-websocket-protocols-1.12.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-cBHffIUefiTl49YqnrIeIOAAVfFZr9GyoZiY6kb+54E=" + }, { "mvn-path": "org/sonatype/oss/oss-parent/5/oss-parent-5.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1533,14 +1578,19 @@ "hash": "sha256-Mf7YCTza2mFHjDCUFFYiZIndn6yw5mC2KPK8Fgp8KiU=" }, { - "mvn-path": "potemkin/potemkin/0.4.5/potemkin-0.4.5.jar", + "mvn-path": "potemkin/potemkin/0.4.5/potemkin-0.4.5.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-KzZsg02Hy26mMbpoaXvDdNDRr4H23rQsKECUTaMgvZk=" + "hash": "sha256-3tL5YlDzDlqPmI60YeMvKzDzbBy0Qz+6qHu82kJRTDo=" }, { - "mvn-path": "potemkin/potemkin/0.4.5/potemkin-0.4.5.pom", + "mvn-path": "potemkin/potemkin/0.4.7/potemkin-0.4.7.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-3tL5YlDzDlqPmI60YeMvKzDzbBy0Qz+6qHu82kJRTDo=" + "hash": "sha256-uY+41+eb/DrODyFUyFOXOlgxoxIWiEX5cEdaoZPLEZU=" + }, + { + "mvn-path": "potemkin/potemkin/0.4.7/potemkin-0.4.7.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-XPcgcufcyoNbe+mOIxQDzI9ND8mz0PDUMFKP0XDzR2c=" }, { "mvn-path": "reply/reply/0.5.1/reply-0.5.1.jar", @@ -1593,14 +1643,14 @@ "hash": "sha256-de3pMoKzj49m+yTFILdNGDfQsbtdpUIW+AOglmzp2s4=" }, { - "mvn-path": "ring/ring-core/1.10.0/ring-core-1.10.0.jar", + "mvn-path": "ring/ring-core/1.12.1/ring-core-1.12.1.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-890nOHNp+7xifHrtBMLP3Vllo0ni6/tU6Lm/lisgxYo=" + "hash": "sha256-NEXwourPUVl6CDqdCIJjLF3uS9ta8UGtuBDBZxPs1KU=" }, { - "mvn-path": "ring/ring-core/1.10.0/ring-core-1.10.0.pom", + "mvn-path": "ring/ring-core/1.12.1/ring-core-1.12.1.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-u2TefpyESbB9MB079SA+VCs0GIQcDjTeR3STK1gJosk=" + "hash": "sha256-gueKgX6GZ7nT/541733ywmnTPAB27QsW7T31Yeky9xM=" }, { "mvn-path": "ring/ring-core/1.7.1/ring-core-1.7.1.pom", @@ -1613,14 +1663,14 @@ "hash": "sha256-03k0Epji4KSB/e+zKMZluPCsiLT5GlevXiO3+JIXNDU=" }, { - "mvn-path": "ring/ring-devel/1.10.0/ring-devel-1.10.0.jar", + "mvn-path": "ring/ring-devel/1.12.1/ring-devel-1.12.1.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-PR0RDPdBzvmbv/xLilmJZ7WvQmGMcTAm1qrZXmBUaVo=" + "hash": "sha256-yEIjlDOgD4v4s3tgqyfON2EkBSKzLBzO3A7H8m5AXHg=" }, { - "mvn-path": "ring/ring-devel/1.10.0/ring-devel-1.10.0.pom", + "mvn-path": "ring/ring-devel/1.12.1/ring-devel-1.12.1.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-CVk/j1Kyuozf5B7Y7emC9PP8D+bxPRKoqrZy+MmjaGc=" + "hash": "sha256-t5kfQC7hHjEPW39F2jgUALS6DAIk9mAigjJxNIs1jo0=" }, { "mvn-path": "ring/ring-json/0.5.1/ring-json-0.5.1.jar", From d2532e5c9919b7e3a006527497260106c2b62d9a Mon Sep 17 00:00:00 2001 From: Johannes Hirth <58222089+hirthjo@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:22:16 +0200 Subject: [PATCH 20/22] repaired test when pcbo is present: clojure is not mutable (#148) * repaired test when pcbo is present: clojure is not mutable * Classic 'Vorzeichenfehler' --- src/test/clojure/conexp/fca/fast_test.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/clojure/conexp/fca/fast_test.clj b/src/test/clojure/conexp/fca/fast_test.clj index 03a92918e..29c82c764 100644 --- a/src/test/clojure/conexp/fca/fast_test.clj +++ b/src/test/clojure/conexp/fca/fast_test.clj @@ -19,7 +19,8 @@ ;;; Concept Calculations (defn- add-if-not-exists [invalids program-name keyword] - (when-not (program-exists? program-name) + (if (program-exists? program-name) + invalids (conj invalids keyword))) (def- concepts-methods (let [invalid-methods (-> #{} From 50a12e61b820c7e33d840ad5852751d68740b1d4 Mon Sep 17 00:00:00 2001 From: Johannes Hirth <58222089+hirthjo@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:23:11 +0200 Subject: [PATCH 21/22] New fca style (#149) * new latex fca style output format * removed whitespaces * Added doc string --- .../conexp/gui/draw/control/file_exporter.clj | 6 ++ src/main/clojure/conexp/io/latex.clj | 59 +++++++++++++++++++ src/main/clojure/conexp/io/layouts.clj | 3 +- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/gui/draw/control/file_exporter.clj b/src/main/clojure/conexp/gui/draw/control/file_exporter.clj index 0e8ca2c0e..94496ca6b 100644 --- a/src/main/clojure/conexp/gui/draw/control/file_exporter.clj +++ b/src/main/clojure/conexp/gui/draw/control/file_exporter.clj @@ -29,12 +29,16 @@ jpg-filter (FileNameExtensionFilter. "JPEG Files" (into-array ["jpg" "jpeg"])), gif-filter (FileNameExtensionFilter. "GIF Files" (into-array ["gif"])), tikz-filter (FileNameExtensionFilter. "TIKZ Files" (into-array ["tikz"])) + json-filter (FileNameExtensionFilter. "JSON Files" (into-array ["json"])) + fca-filter (FileNameExtensionFilter. "FCA Files" (into-array ["fca"])) layout-filter (FileNameExtensionFilter. "Layout Files" (into-array ["layout"])) png-filter (FileNameExtensionFilter. "PNG Files" (into-array ["png"]))] (doto fc (.addChoosableFileFilter jpg-filter) (.addChoosableFileFilter gif-filter) (.addChoosableFileFilter tikz-filter) + (.addChoosableFileFilter json-filter) + (.addChoosableFileFilter fca-filter) (.addChoosableFileFilter layout-filter) (.addChoosableFileFilter png-filter)) (listen save-button :action @@ -45,6 +49,8 @@ (with-swing-error-msg frame "Error while saving" (case (get-file-extension file) "tikz" (write-layout :tikz (get-layout-from-scene scn) (.getAbsolutePath file)) + "json" (write-layout :json (get-layout-from-scene scn) (.getAbsolutePath file)) + "fca" (write-layout :fca-style (get-layout-from-scene scn) (.getAbsolutePath file)) "layout" (write-layout :simple (get-layout-from-scene scn) (.getAbsolutePath file)) (save-image scn file (get-file-extension file)))))))))) nil) diff --git a/src/main/clojure/conexp/io/latex.clj b/src/main/clojure/conexp/io/latex.clj index ba0a215e8..b654056bd 100644 --- a/src/main/clojure/conexp/io/latex.clj +++ b/src/main/clojure/conexp/io/latex.clj @@ -93,6 +93,7 @@ ;;; Layouts (declare layout->tikz) +(declare layout->fca-style) (extend-type conexp.layouts.base.Layout LaTeX @@ -102,8 +103,66 @@ ([this choice] (case choice :tikz (layout->tikz this) + :fca-style (layout->fca-style this) true (illegal-argument "Unsupported latex format " choice " for layouts."))))) +(defn- layout->fca-style + "Latex output format for the new fca package at https://github.com/keinstein/latex-fca" + [layout] + (let [vertex-pos (positions layout), + sorted-vertices (sort #(let [[x_1 y_1] (vertex-pos %1), + [x_2 y_2] (vertex-pos %2)] + (or (< y_1 y_2) + (and (= y_1 y_2) + (< x_1 x_2)))) + (nodes layout)), + vertex-idx (into {} + (map-indexed (fn [i v] [v i]) + sorted-vertices)), + value-fn #(if (nil? ((valuations layout) %)) + "" ((valuations layout) %))] + (with-out-str + (println "{\\unitlength 1mm") + (println "\\tikzset{concept/.style={/tikz/semithick, /tikz/shape=circle, inner sep=1pt, outer sep=0pt, draw=black!80,") + (println " fill=white, radius=1.5mm},%") + (println " relation/.style={/tikz/-,/tikz/thick,color=black!80,line width=1.5pt},") + (println " valuation/.style={color=red,label distance=3pt}") + (println "}") + (println "\\begin{tikzpicture}[scale=1]") + (println "\\begin{diagram}") + ;; concepts + (doseq [v sorted-vertices] + (let [idx (vertex-idx v) + [x y] (vertex-pos v)] + (println (str"\\Node[/tikz/concept](" idx ")("x", "y")")))) + ;; relation + (doseq [[v w] (connections layout)] + (let [vidx (vertex-idx v) + widx (vertex-idx w)] + (println (str "\\Edge[/tikz/relation](" vidx ")("widx")")))) + ;; attribute labels + (doseq [v sorted-vertices] + (let [idx (vertex-idx v) + ann (annotation layout) + [u _] (map tex-escape (ann v))] + (if-not (= "" u) + (println (str "\\centerAttbox("idx"){" u "}")) ) )) + ;; object labels + (doseq [v sorted-vertices] + (let [idx (vertex-idx v) + ann (annotation layout) + [_ l] (map tex-escape (ann v))] + (if-not (= "" l) + (println (str "\\centerObjbox("idx"){" l "}")) ) )) + ;; valuations + (doseq [v sorted-vertices] + (let [val (value-fn v) + idx (vertex-idx v)] + (if (not= "" val) + (println (str"\\node[/tikz/valuation] [right of="idx"] {"val"};"))))) + (println "\\end{diagram}") + (println "\\end{tikzpicture}}") ) )) + (defn- layout->tikz [layout] (let [vertex-pos (positions layout), sorted-vertices (sort #(let [[x_1 y_1] (vertex-pos %1), diff --git a/src/main/clojure/conexp/io/layouts.clj b/src/main/clojure/conexp/io/layouts.clj index 521d6a549..9528c6915 100644 --- a/src/main/clojure/conexp/io/layouts.clj +++ b/src/main/clojure/conexp/io/layouts.clj @@ -164,7 +164,8 @@ (define-layout-output-format :fca-style [layout file] - (unsupported-operation "Output in :fca-style is not yet supported.")) + (with-out-writer file + (println (latex layout :fca-style)))) ;; Json helpers From 3463568bccb12c9afe2bdb777fcf242b682d4679 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Thu, 13 Jun 2024 01:55:49 +0200 Subject: [PATCH 22/22] Adapted requirements: now boring code but no warning --- src/main/clojure/conexp/api/namespace.clj | 6 ++- src/main/clojure/conexp/base.clj | 42 ++++++++++--------- .../conexp/fca/causal_implications.clj | 30 ++++++------- .../clojure/conexp/fca/concept_transform.clj | 3 +- src/main/clojure/conexp/fca/contexts.clj | 4 +- src/main/clojure/conexp/fca/cover.clj | 3 +- src/main/clojure/conexp/fca/dependencies.clj | 1 + src/main/clojure/conexp/fca/exploration.clj | 3 +- src/main/clojure/conexp/fca/graph.clj | 3 +- src/main/clojure/conexp/fca/implications.clj | 14 ++++--- .../clojure/conexp/fca/incremental_ganter.clj | 3 +- src/main/clojure/conexp/fca/lattices.clj | 3 +- .../conexp/fca/many_valued_contexts.clj | 3 +- src/main/clojure/conexp/fca/metrics.clj | 3 +- src/main/clojure/conexp/fca/more.clj | 1 + src/main/clojure/conexp/fca/posets.clj | 3 +- src/main/clojure/conexp/fca/protoconcepts.clj | 4 +- .../clojure/conexp/fca/random_contexts.clj | 2 +- src/main/clojure/conexp/fca/smeasure.clj | 1 + .../editors/context_editor/context_editor.clj | 3 +- .../editors/context_editor/table_control.clj | 3 +- src/main/clojure/conexp/io/contexts.clj | 3 +- src/main/clojure/conexp/io/latex.clj | 3 +- src/main/clojure/conexp/io/layouts.clj | 3 +- src/main/clojure/conexp/io/smeasure.clj | 3 +- src/main/clojure/conexp/io/util.clj | 3 +- src/main/clojure/conexp/layouts/base.clj | 4 +- src/main/clojure/conexp/layouts/dim_draw.clj | 3 +- src/main/clojure/conexp/math/sampling.clj | 3 +- src/test/clojure/conexp/base_test.clj | 4 +- src/test/clojure/conexp/fca/contexts_test.clj | 4 +- .../clojure/conexp/fca/implications_test.clj | 3 +- src/test/clojure/conexp/fca/lattices_test.clj | 4 +- src/test/clojure/conexp/fca/more_test.clj | 1 + .../clojure/conexp/fca/protoconcepts_test.clj | 1 + src/test/clojure/conexp/fca/smeasure_test.clj | 1 + src/test/clojure/conexp/io/smeasure_test.clj | 1 + src/test/clojure/conexp/layouts/base_test.clj | 1 + .../clojure/conexp/layouts/common_test.clj | 3 +- .../clojure/conexp/layouts/dim_draw_test.clj | 3 +- 40 files changed, 118 insertions(+), 71 deletions(-) diff --git a/src/main/clojure/conexp/api/namespace.clj b/src/main/clojure/conexp/api/namespace.clj index 8a5a8b13d..bb231a456 100644 --- a/src/main/clojure/conexp/api/namespace.clj +++ b/src/main/clojure/conexp/api/namespace.clj @@ -12,7 +12,8 @@ conexp.layouts.freese conexp.layouts.layered conexp.layouts.dim-draw - conexp.api.shorthands)) + conexp.api.shorthands + clojure.set)) ;;; @@ -24,7 +25,8 @@ conexp.layouts.freese conexp.layouts.layered conexp.layouts.dim-draw - conexp.api.shorthands])) + conexp.api.shorthands + clojure.set])) ;; basic set operations (def functions (concat diff --git a/src/main/clojure/conexp/base.clj b/src/main/clojure/conexp/base.clj index eb7a25b39..f4e700d9f 100644 --- a/src/main/clojure/conexp/base.clj +++ b/src/main/clojure/conexp/base.clj @@ -9,7 +9,9 @@ (ns conexp.base "Basic definitions for conexp-clj." (:require [clojure.math.combinatorics :as comb] - [clojure.java.io :as io])) + [clojure.java.io :as io] + [clojure.set :refer [difference union subset? intersection]] + [clojure.math.numeric-tower :as nt])) ;;; def macros, inspired and partially copied from clojure.contrib.def @@ -77,24 +79,24 @@ metadata (as provided by def) merged into the metadata of the original." (last dforms))))) ;;; Namespace tools -(defn immigrate - "Create a public var in this namespace for each public var in the - namespaces named by ns-names. The created vars have the same name, root - binding, and metadata as the original except that their :ns metadata - value is this namespace. - - This function is literally copied from the clojure.contrib.ns-utils library." - [& ns-names] - (doseq [ns ns-names] - (require ns) - (doseq [[sym, ^clojure.lang.Var var] (ns-publics ns)] - (let [sym (with-meta sym (assoc (meta var) :ns *ns*))] - (if (.hasRoot var) - (intern *ns* sym (.getRawRoot var)) - (intern *ns* sym)))))) - -(immigrate 'clojure.set - 'clojure.math.numeric-tower) +;; (defn immigrate +;; "Create a public var in this namespace for each public var in the +;; namespaces named by ns-names. The created vars have the same name, root +;; binding, and metadata as the original except that their :ns metadata +;; value is this namespace. + +;; This function is literally copied from the clojure.contrib.ns-utils library." +;; [& ns-names] +;; (doseq [ns ns-names] +;; (require ns) +;; (doseq [[sym, ^clojure.lang.Var var] (ns-publics ns)] +;; (let [sym (with-meta sym (assoc (meta var) :ns *ns*))] +;; (if (.hasRoot var) +;; (intern *ns* sym (.getRawRoot var)) +;; (intern *ns* sym)))))) + +;; (immigrate 'clojure.set +;; 'clojure.math.numeric-tower) ;;; Version @@ -586,7 +588,7 @@ metadata (as provided by def) merged into the metadata of the original." and returns double otherwise." [a b] (if (not (and (integer? a) (integer? b))) - (clojure.math.numeric-tower/expt a b) + (nt/expt a b) (let [^clojure.lang.BigInt a (bigint a), b (long b)] (if (< b 0) diff --git a/src/main/clojure/conexp/fca/causal_implications.clj b/src/main/clojure/conexp/fca/causal_implications.clj index 386288a44..e25ca969e 100644 --- a/src/main/clojure/conexp/fca/causal_implications.clj +++ b/src/main/clojure/conexp/fca/causal_implications.clj @@ -5,7 +5,7 @@ [conexp.io.contexts :refer :all] [conexp.fca.contexts :refer :all] [conexp.fca.implications :refer :all] - [clojure.set :as set])) + [clojure.set :refer [difference union subset? intersection]])) ;For a full Explanation of the Concepts refer to *Mining Causal Association Rules* ;https://www.researchgate.net/publication/262240022_Mining_Causal_Association_Rules @@ -26,9 +26,9 @@ (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))))))) + (union (intersection a-attributes b-attributes) + (intersection (difference controlled-variables a-attributes) + (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, @@ -47,13 +47,13 @@ (let [objs (objects ctx)] (filter seq (reduce (fn [present-objs new-obj] - (if (contains? (reduce set/union present-objs) new-obj) + (if (contains? (reduce 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)) + (difference objs (reduce union present-objs)) new-obj)))) #{} objs)))) @@ -103,10 +103,10 @@ "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) #{}])) + (* zconf (Math/sqrt (+ (/ 1 (absolute-support ctx [(union premise conclusion) #{}])) (/ 1 (absolute-support ctx [premise conclusion])) (/ 1 (absolute-support ctx [conclusion premise])) - (/ 1 (absolute-support ctx [#{} (set/union premise conclusion)])))))))) + (/ 1 (absolute-support ctx [#{} (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 @@ -180,9 +180,9 @@ 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)) + E (reduce union (exclusive-variables ctx premise thresh)) + controlled-variables (difference (attributes ctx) + (union conclusion irrelevant-vars E premise)) fair-data (fair-data-set ctx impl controlled-variables) fair-odds (fair-odds-ratio ctx impl fair-data)] @@ -217,8 +217,8 @@ (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 + (difference frequent-vars #{response-var}) ;frequent single variables + (for [x (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 @@ -237,9 +237,9 @@ (causal-association-rule-discovery ctx - (set/union rule-set new-causal-rules) + (union rule-set new-causal-rules) variables - (set/difference (filter #(> (local-support ctx (->Implication #{%} #{response-var})) min-lsupp) new-item-sets) + (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 diff --git a/src/main/clojure/conexp/fca/concept_transform.clj b/src/main/clojure/conexp/fca/concept_transform.clj index e127ac323..3a3deb5cf 100644 --- a/src/main/clojure/conexp/fca/concept_transform.clj +++ b/src/main/clojure/conexp/fca/concept_transform.clj @@ -9,7 +9,8 @@ (ns conexp.fca.concept-transform (:require [conexp.base :refer :all] [conexp.fca.contexts :refer :all] - [conexp.fca.cover :refer :all])) + [conexp.fca.cover :refer :all] + [clojure.set :refer [difference union subset? intersection]])) diff --git a/src/main/clojure/conexp/fca/contexts.clj b/src/main/clojure/conexp/fca/contexts.clj index 36e6b43ed..1bb7a4f95 100644 --- a/src/main/clojure/conexp/fca/contexts.clj +++ b/src/main/clojure/conexp/fca/contexts.clj @@ -9,7 +9,9 @@ (ns conexp.fca.contexts "Provides the implementation of formal contexts and functions on them." (:require [clojure.core.reducers :as r] - [conexp.base :refer :all])) + [clojure.set :refer [difference intersection union subset?]] + [conexp.base :refer :all] + )) ;;; diff --git a/src/main/clojure/conexp/fca/cover.clj b/src/main/clojure/conexp/fca/cover.clj index 5907d801b..47d4c08c7 100644 --- a/src/main/clojure/conexp/fca/cover.clj +++ b/src/main/clojure/conexp/fca/cover.clj @@ -19,7 +19,8 @@ to-binary-matrix bitwise-context-attribute-closure]] [clojure.core.reducers :as r] - [clojure.core.async :refer [ lattice diff --git a/src/main/clojure/conexp/fca/implications.clj b/src/main/clojure/conexp/fca/implications.clj index e560d1a46..8b2e9625b 100644 --- a/src/main/clojure/conexp/fca/implications.clj +++ b/src/main/clojure/conexp/fca/implications.clj @@ -12,7 +12,9 @@ [conexp.base :refer :all] [conexp.math.algebra :refer :all] [conexp.fca.contexts :refer :all] - [clojure.set :as set])) + [clojure.set :refer [difference union subset? intersection]] + [clojure.math.numeric-tower :as nt] + )) ;;; @@ -604,15 +606,15 @@ "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 [(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 [(union premise conclusion) #{}]) + (absolute-support ctx [#{} (union premise conclusion)])) (* (absolute-support ctx [premise conclusion]) (absolute-support ctx [conclusion premise])) ))) @@ -621,7 +623,7 @@ "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 [(union premise conclusion) #{}]) (absolute-support ctx [conclusion #{}])))) (defn- frequent-itemsets @@ -812,7 +814,7 @@ (learn-implications-by-queries (attributes ctx) intent? (fn [implications] - (let [nr-iter (ceil (* (/ ε) (+ (swap! iter-counter inc) + (let [nr-iter (nt/ceil (* (/ ε) (+ (swap! iter-counter inc) (/ (Math/log (/ δ)) (Math/log 2)))))] (or (some (fn [test-set] diff --git a/src/main/clojure/conexp/fca/incremental_ganter.clj b/src/main/clojure/conexp/fca/incremental_ganter.clj index d98c63234..64ead8cdd 100644 --- a/src/main/clojure/conexp/fca/incremental_ganter.clj +++ b/src/main/clojure/conexp/fca/incremental_ganter.clj @@ -13,7 +13,8 @@ https://link.springer.com/article/10.1007/s10472-007-9057-2 " (:require [conexp.base :refer :all] [conexp.fca.implications :refer :all] - [conexp.fca.contexts :refer :all])) + [conexp.fca.contexts :refer :all] + [clojure.set :refer [difference union subset? intersection]])) ;;; diff --git a/src/main/clojure/conexp/fca/lattices.clj b/src/main/clojure/conexp/fca/lattices.clj index 5312e146c..d263ab82c 100644 --- a/src/main/clojure/conexp/fca/lattices.clj +++ b/src/main/clojure/conexp/fca/lattices.clj @@ -12,6 +12,7 @@ conexp.math.algebra conexp.fca.contexts conexp.fca.posets) + (:require [clojure.set :refer [difference union subset? intersection]]) (:gen-class)) ;;; Datastructure @@ -466,7 +467,7 @@ (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))) + (let [X-new (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))))) diff --git a/src/main/clojure/conexp/fca/many_valued_contexts.clj b/src/main/clojure/conexp/fca/many_valued_contexts.clj index 56744a482..f67931414 100644 --- a/src/main/clojure/conexp/fca/many_valued_contexts.clj +++ b/src/main/clojure/conexp/fca/many_valued_contexts.clj @@ -8,7 +8,8 @@ (ns conexp.fca.many-valued-contexts "Many-Valued-Contexts and some functions for scaling." - (:require [conexp.base :refer :all] + (:require [clojure.set :refer [difference union subset? intersection]] + [conexp.base :refer :all] [conexp.fca.contexts :refer :all])) ;;; diff --git a/src/main/clojure/conexp/fca/metrics.clj b/src/main/clojure/conexp/fca/metrics.clj index 6068d1650..166c0266e 100644 --- a/src/main/clojure/conexp/fca/metrics.clj +++ b/src/main/clojure/conexp/fca/metrics.clj @@ -28,7 +28,8 @@ bitwise-attribute-derivation concepts]] [implications :refer :all] [lattices :refer [inf sup lattice-base-set make-lattice concept-lattice lattice-order]]] - [conexp.math.util :refer [eval-polynomial binomial-coefficient]]) + [conexp.math.util :refer [eval-polynomial binomial-coefficient]] + [clojure.set :refer [difference union subset? intersection]]) (:import [conexp.fca.lattices Lattice] [java.util ArrayList BitSet])) diff --git a/src/main/clojure/conexp/fca/more.clj b/src/main/clojure/conexp/fca/more.clj index cf1c57d0a..da2dd14f1 100644 --- a/src/main/clojure/conexp/fca/more.clj +++ b/src/main/clojure/conexp/fca/more.clj @@ -10,6 +10,7 @@ "More on FCA." (:require [conexp.base :refer :all] [clojure.core.reducers :as r] + [clojure.set :refer [difference union subset? intersection]] [conexp.fca [contexts :refer :all] [exploration :refer :all] diff --git a/src/main/clojure/conexp/fca/posets.clj b/src/main/clojure/conexp/fca/posets.clj index c93be4897..d26e37bf0 100644 --- a/src/main/clojure/conexp/fca/posets.clj +++ b/src/main/clojure/conexp/fca/posets.clj @@ -7,7 +7,8 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.fca.posets - (:require [conexp.base :refer :all] + (:require [clojure.set :refer [union difference subset?]] + [conexp.base :refer :all] [conexp.math.algebra :refer :all] [conexp.fca.contexts :refer :all])) diff --git a/src/main/clojure/conexp/fca/protoconcepts.clj b/src/main/clojure/conexp/fca/protoconcepts.clj index e6d429712..95f328c25 100644 --- a/src/main/clojure/conexp/fca/protoconcepts.clj +++ b/src/main/clojure/conexp/fca/protoconcepts.clj @@ -13,7 +13,9 @@ [conexp.math.algebra :refer :all] [conexp.fca.contexts :refer :all] [conexp.fca.lattices :refer :all] - [conexp.fca.posets :refer :all])) + [conexp.fca.posets :refer :all] + [clojure.set :refer [difference union subset? intersection]] + )) (deftype Protoconcepts [base-set order-function] Object diff --git a/src/main/clojure/conexp/fca/random_contexts.clj b/src/main/clojure/conexp/fca/random_contexts.clj index a4ef21a2e..397b6e28f 100644 --- a/src/main/clojure/conexp/fca/random_contexts.clj +++ b/src/main/clojure/conexp/fca/random_contexts.clj @@ -4,7 +4,7 @@ [conexp.base :refer [set-of exists forall => defalias]] [conexp.fca.contexts :as contexts] [clojure.set :refer [subset? difference union intersection select]] - [clojure.math.numeric-tower :refer :all] + [clojure.math.numeric-tower :refer [expt]] [clojure.math.combinatorics :refer [cartesian-product]] ) (:import [org.apache.commons.math3.distribution diff --git a/src/main/clojure/conexp/fca/smeasure.clj b/src/main/clojure/conexp/fca/smeasure.clj index 4c0b6033c..3cba71fa1 100644 --- a/src/main/clojure/conexp/fca/smeasure.clj +++ b/src/main/clojure/conexp/fca/smeasure.clj @@ -16,6 +16,7 @@ [clojure.math.combinatorics :as comb] [loom.graph :as lg] [loom.alg :as la] [clojure.core.reducers :as r] + [clojure.set :refer [difference union subset? intersection]] [conexp.fca.implications :refer :all])) (defprotocol Smeasure diff --git a/src/main/clojure/conexp/gui/editors/context_editor/context_editor.clj b/src/main/clojure/conexp/gui/editors/context_editor/context_editor.clj index db94867a8..2e8c1aacb 100644 --- a/src/main/clojure/conexp/gui/editors/context_editor/context_editor.clj +++ b/src/main/clojure/conexp/gui/editors/context_editor/context_editor.clj @@ -16,7 +16,8 @@ [conexp.gui.editors.context-editor.table-control :refer :all] [conexp.gui.editors.context-editor.widgets :refer :all] [conexp.gui.util :refer :all] - [seesaw.core :refer [button toolbar top-bottom-split]]) + [seesaw.core :refer [button toolbar top-bottom-split]] + [clojure.set :refer [difference union subset? intersection]]) (:import [java.awt.event ActionEvent KeyEvent] [javax.swing Box JTable KeyStroke])) diff --git a/src/main/clojure/conexp/gui/editors/context_editor/table_control.clj b/src/main/clojure/conexp/gui/editors/context_editor/table_control.clj index 0682b4600..867f20ec6 100644 --- a/src/main/clojure/conexp/gui/editors/context_editor/table_control.clj +++ b/src/main/clojure/conexp/gui/editors/context_editor/table_control.clj @@ -12,7 +12,8 @@ (:require [clojure.string :refer [split split-lines]] [conexp.base :refer :all] [conexp.gui.editors.context-editor.widgets :refer :all] - [conexp.gui.util :refer :all]) + [conexp.gui.util :refer :all] + [clojure.set :refer [difference union subset? intersection join map-invert]]) (:import [java.awt.event ActionEvent ActionListener InputEvent KeyEvent MouseEvent] java.awt.Point [javax.swing AbstractAction DefaultCellEditor JComponent JScrollPane JTable JTextField KeyStroke] diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 4f44212ec..c27cfff7f 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -18,7 +18,8 @@ [clojure.data.json :as json] [json-schema.core :as json-schema] [clojure.data.csv :as csv] - [clojure.java.io :as io]) + [clojure.java.io :as io] + [clojure.set :refer [difference union]]) (:import [java.io PushbackReader])) diff --git a/src/main/clojure/conexp/io/latex.clj b/src/main/clojure/conexp/io/latex.clj index b654056bd..28cec7d85 100644 --- a/src/main/clojure/conexp/io/latex.clj +++ b/src/main/clojure/conexp/io/latex.clj @@ -12,7 +12,8 @@ [conexp.fca.contexts :only (objects attributes incidence)] conexp.fca.lattices conexp.fca.many-valued-contexts - [conexp.layouts.base :only (positions connections nodes inf-irreducibles sup-irreducibles annotation valuations)])) + [conexp.layouts.base :only (positions connections nodes inf-irreducibles sup-irreducibles annotation valuations)]) + (:require [clojure.set :refer [difference union subset? intersection]])) ;;; diff --git a/src/main/clojure/conexp/io/layouts.clj b/src/main/clojure/conexp/io/layouts.clj index 9528c6915..4e2cf18c0 100644 --- a/src/main/clojure/conexp/io/layouts.clj +++ b/src/main/clojure/conexp/io/layouts.clj @@ -15,7 +15,8 @@ conexp.layouts.util conexp.layouts.base) (:require clojure.string - [clojure.data.json :as json]) + [clojure.data.json :as json] + [clojure.set :refer [difference union subset? intersection map-invert]]) (:import [java.io PushbackReader])) ;;; Input format dispatch diff --git a/src/main/clojure/conexp/io/smeasure.clj b/src/main/clojure/conexp/io/smeasure.clj index f6f213fc5..b79e34e7f 100644 --- a/src/main/clojure/conexp/io/smeasure.clj +++ b/src/main/clojure/conexp/io/smeasure.clj @@ -15,7 +15,8 @@ [conexp.layouts.base :refer [positions nodes inf-irreducibles sup-irreducibles connections annotation]] conexp.layouts.dim-draw - conexp.io.latex)) + conexp.io.latex) + (:require [clojure.set :refer [difference union subset? intersection]])) ;;; Smeasure diff --git a/src/main/clojure/conexp/io/util.clj b/src/main/clojure/conexp/io/util.clj index 4d5035c77..5f237adf1 100644 --- a/src/main/clojure/conexp/io/util.clj +++ b/src/main/clojure/conexp/io/util.clj @@ -8,7 +8,8 @@ (ns conexp.io.util (:use conexp.base) - (:require [clojure.java.io :as io])) + (:require [clojure.java.io :as io] + [clojure.set :refer [intersection]])) ;;; diff --git a/src/main/clojure/conexp/layouts/base.clj b/src/main/clojure/conexp/layouts/base.clj index 3756149c9..50d49a1f2 100644 --- a/src/main/clojure/conexp/layouts/base.clj +++ b/src/main/clojure/conexp/layouts/base.clj @@ -12,7 +12,9 @@ conexp.math.algebra conexp.fca.lattices conexp.fca.posets - clojure.pprint)) + clojure.pprint) + (:require + [clojure.set :refer [difference subset? superset? intersection]])) ;;; diff --git a/src/main/clojure/conexp/layouts/dim_draw.clj b/src/main/clojure/conexp/layouts/dim_draw.clj index cc83bd360..3950c6f3e 100644 --- a/src/main/clojure/conexp/layouts/dim_draw.clj +++ b/src/main/clojure/conexp/layouts/dim_draw.clj @@ -6,7 +6,8 @@ [conexp.util.graph :refer :all] [conexp.layouts.base :as lay] [conexp.base :exclude [transitive-closure] :refer :all] - [rolling-stones.core :as sat :refer :all]) + [rolling-stones.core :as sat :refer :all] + [clojure.set :refer [difference union subset? intersection]]) (:import [org.dimdraw Bipartite])) (defn in-odd-cycle? diff --git a/src/main/clojure/conexp/math/sampling.clj b/src/main/clojure/conexp/math/sampling.clj index 20be34458..8e02d67eb 100644 --- a/src/main/clojure/conexp/math/sampling.clj +++ b/src/main/clojure/conexp/math/sampling.clj @@ -14,7 +14,8 @@ Elías F. Combarro, Julen Hurtado de Saracho, Irene Díaz https://www.sciencedirect.com/science/article/pii/S0020025519305043 " (:use conexp.base - conexp.math.algebra)) + conexp.math.algebra) + (:require [clojure.set :refer [difference union subset? intersection]])) ;; Sampler diff --git a/src/test/clojure/conexp/base_test.clj b/src/test/clojure/conexp/base_test.clj index 004ccc67d..3e8a7e725 100644 --- a/src/test/clojure/conexp/base_test.clj +++ b/src/test/clojure/conexp/base_test.clj @@ -8,7 +8,9 @@ (ns conexp.base-test (:use clojure.test - conexp.base)) + conexp.base) + (:require [clojure.set :refer [difference union subset? superset? intersection]] + [clojure.math.numeric-tower :refer [gcd]])) ;;; diff --git a/src/test/clojure/conexp/fca/contexts_test.clj b/src/test/clojure/conexp/fca/contexts_test.clj index 70b3d2d5e..3fa55e7f0 100644 --- a/src/test/clojure/conexp/fca/contexts_test.clj +++ b/src/test/clojure/conexp/fca/contexts_test.clj @@ -9,7 +9,9 @@ (ns conexp.fca.contexts-test (:use conexp.base conexp.fca.contexts) - (:use clojure.test)) + (:use clojure.test) + (:require [clojure.set :refer [difference union subset? intersection]] + [clojure.math.numeric-tower :refer [gcd]])) ;;; diff --git a/src/test/clojure/conexp/fca/implications_test.clj b/src/test/clojure/conexp/fca/implications_test.clj index 8065994a2..3460e972d 100644 --- a/src/test/clojure/conexp/fca/implications_test.clj +++ b/src/test/clojure/conexp/fca/implications_test.clj @@ -13,7 +13,8 @@ conexp.io.contexts conexp.math.algebra conexp.fca.implications) - (:require [conexp.fca.contexts-test :as contexts])) + (:require [conexp.fca.contexts-test :as contexts] + [clojure.set :refer [difference union subset? intersection]])) ;;; diff --git a/src/test/clojure/conexp/fca/lattices_test.clj b/src/test/clojure/conexp/fca/lattices_test.clj index a317d2370..b77169514 100644 --- a/src/test/clojure/conexp/fca/lattices_test.clj +++ b/src/test/clojure/conexp/fca/lattices_test.clj @@ -13,7 +13,9 @@ conexp.fca.posets conexp.math.algebra conexp.fca.lattices) - (:use clojure.test)) + (:use clojure.test) + (:require [clojure.set :refer [difference union subset? intersection]])) + ;;; Testing basic datastructure diff --git a/src/test/clojure/conexp/fca/more_test.clj b/src/test/clojure/conexp/fca/more_test.clj index ba55b03ca..20d8380ac 100644 --- a/src/test/clojure/conexp/fca/more_test.clj +++ b/src/test/clojure/conexp/fca/more_test.clj @@ -8,6 +8,7 @@ (ns conexp.fca.more-test (:require [clojure.test :refer [deftest is]] + [clojure.set :refer [difference union subset? intersection]] [conexp.base :refer :all] [conexp.fca.contexts :refer :all] [conexp.fca.contexts-test :refer :all] diff --git a/src/test/clojure/conexp/fca/protoconcepts_test.clj b/src/test/clojure/conexp/fca/protoconcepts_test.clj index 4b5c172ef..5b7ced32c 100644 --- a/src/test/clojure/conexp/fca/protoconcepts_test.clj +++ b/src/test/clojure/conexp/fca/protoconcepts_test.clj @@ -10,6 +10,7 @@ (:use conexp.base conexp.fca.contexts conexp.fca.protoconcepts) + (:require [clojure.set :refer [difference union subset? intersection]]) (:use clojure.test)) (def test-context-01 (make-context #{"a" "b" "c"} diff --git a/src/test/clojure/conexp/fca/smeasure_test.clj b/src/test/clojure/conexp/fca/smeasure_test.clj index c68fe508a..a7f2a6489 100644 --- a/src/test/clojure/conexp/fca/smeasure_test.clj +++ b/src/test/clojure/conexp/fca/smeasure_test.clj @@ -11,6 +11,7 @@ conexp.fca.implications conexp.fca.smeasure conexp.base) + (:require [clojure.set :refer [difference union subset? intersection]]) (:use clojure.test)) (def- ctx1 diff --git a/src/test/clojure/conexp/io/smeasure_test.clj b/src/test/clojure/conexp/io/smeasure_test.clj index 94bff1078..e0b506a69 100644 --- a/src/test/clojure/conexp/io/smeasure_test.clj +++ b/src/test/clojure/conexp/io/smeasure_test.clj @@ -14,6 +14,7 @@ conexp.layouts.base conexp.io.latex conexp.io.smeasure) + (:require [clojure.set :refer [difference union subset? intersection]]) (:use clojure.test)) ;;; diff --git a/src/test/clojure/conexp/layouts/base_test.clj b/src/test/clojure/conexp/layouts/base_test.clj index 89c143331..30bb3fdc2 100644 --- a/src/test/clojure/conexp/layouts/base_test.clj +++ b/src/test/clojure/conexp/layouts/base_test.clj @@ -14,6 +14,7 @@ conexp.fca.lattices conexp.layouts.base conexp.layouts.layered) + (:require [clojure.set :refer [difference union subset? intersection]]) (:use clojure.test)) ;;; diff --git a/src/test/clojure/conexp/layouts/common_test.clj b/src/test/clojure/conexp/layouts/common_test.clj index e0309b00d..14bd2e35c 100644 --- a/src/test/clojure/conexp/layouts/common_test.clj +++ b/src/test/clojure/conexp/layouts/common_test.clj @@ -13,7 +13,8 @@ conexp.fca.posets conexp.layouts.base conexp.layouts.common) - (:use clojure.test)) + (:use clojure.test) + (:require [clojure.set :refer [difference union subset? intersection]])) ;;; diff --git a/src/test/clojure/conexp/layouts/dim_draw_test.clj b/src/test/clojure/conexp/layouts/dim_draw_test.clj index fab1aa38c..1c0ee7b14 100644 --- a/src/test/clojure/conexp/layouts/dim_draw_test.clj +++ b/src/test/clojure/conexp/layouts/dim_draw_test.clj @@ -5,7 +5,8 @@ [conexp.base :exclude [transitive-closure] :refer :all] [loom.graph :as lg] [loom.alg :as la] - [rolling-stones.core :as sat :refer :all]) + [rolling-stones.core :as sat :refer :all] + [clojure.set :refer [difference union subset? intersection]]) (:use conexp.layouts.dim-draw)) ;;;