From 46c416b4c94297602edb4a002a7736c9a2a1106c Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Fri, 18 Dec 2020 10:53:28 +0100 Subject: [PATCH 001/112] Added concept separation --- src/main/clojure/conexp/fca/metrics.clj | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/clojure/conexp/fca/metrics.clj b/src/main/clojure/conexp/fca/metrics.clj index 2afbc3298..af44cff69 100644 --- a/src/main/clojure/conexp/fca/metrics.clj +++ b/src/main/clojure/conexp/fca/metrics.clj @@ -54,6 +54,30 @@ (/ (counter #{} extent) (expt 2 (count extent))))) +(defn concept-separation + "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." + [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" From 8a214dd74ef2552088a1ac1a072bcfbc30de6bdf Mon Sep 17 00:00:00 2001 From: Maximilian Felde Date: Fri, 19 Feb 2021 18:54:59 +0100 Subject: [PATCH 002/112] fixed :anonymous-burmeister write to file function --- src/main/clojure/conexp/io/contexts.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 6cbdd2dfc..dfc4055e7 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -109,8 +109,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)] From be4b7e01152e294976fadf52ca27782fb1ba067c Mon Sep 17 00:00:00 2001 From: Maximilian Felde Date: Tue, 2 Mar 2021 16:45:25 +0100 Subject: [PATCH 003/112] 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. --- src/test/clojure/conexp/io/contexts_test.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/clojure/conexp/io/contexts_test.clj b/src/test/clojure/conexp/io/contexts_test.clj index a9931fc0c..eb9f85e70 100644 --- a/src/test/clojure/conexp/io/contexts_test.clj +++ b/src/test/clojure/conexp/io/contexts_test.clj @@ -26,7 +26,7 @@ (deftest test-context-out-in (with-testing-data [ctx contexts-oi, - fmt (remove #{:binary-csv} + fmt (remove #{:binary-csv :anonymous-burmeister} (list-context-formats))] (try (= ctx (out-in ctx 'context fmt)) (catch UnsupportedOperationException _ true)))) @@ -40,7 +40,7 @@ (deftest test-context-out-in-out-in (with-testing-data [ctx contexts-oioi, - fmt (list-context-formats)] + fmt (remove #{:anonymous-burmeister} (list-context-formats))] (try (out-in-out-in-test ctx 'context fmt) (catch UnsupportedOperationException _ true)))) @@ -63,7 +63,7 @@ (deftest test-for-random-contexts (with-testing-data [ctx (random-contexts 20 50), - fmt (list-context-formats)] + fmt (remove #{:anonymous-burmeister} (list-context-formats))] (try (out-in-out-in-test ctx 'context fmt) (catch UnsupportedOperationException _ true)))) From 4aa9b39a6591c96181be66de333fa83a7c466242 Mon Sep 17 00:00:00 2001 From: Maximilian Felde Date: Wed, 3 Mar 2021 14:25:01 +0100 Subject: [PATCH 004/112] doc: corrected example for :anonymous-burmeister format --- doc/Common-FCA-File-Formats-for-Formal-Contexts.org | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org index fb4f57a12..ab89b9305 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 From a8b50061ed90a34860c096fa11ff6014d8b37c30 Mon Sep 17 00:00:00 2001 From: Maximilian Felde Date: Mon, 8 Mar 2021 14:04:37 +0100 Subject: [PATCH 005/112] 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. --- src/test/clojure/conexp/io/contexts_test.clj | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/test/clojure/conexp/io/contexts_test.clj b/src/test/clojure/conexp/io/contexts_test.clj index eb9f85e70..390c10bce 100644 --- a/src/test/clojure/conexp/io/contexts_test.clj +++ b/src/test/clojure/conexp/io/contexts_test.clj @@ -31,6 +31,19 @@ (try (= ctx (out-in ctx 'context fmt)) (catch UnsupportedOperationException _ true)))) +(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-anonymous-burmeister-out-in + (with-testing-data [ctx contexts-oi + fmt #{:anonymous-burmeister}] + (possible-isomorphic? ctx (out-in ctx 'context fmt)))) ;; (def- contexts-oioi @@ -44,6 +57,13 @@ (try (out-in-out-in-test ctx 'context fmt) (catch UnsupportedOperationException _ true)))) +(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 @@ -67,6 +87,13 @@ (try (out-in-out-in-test ctx 'context fmt) (catch UnsupportedOperationException _ true)))) +(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) (deftest test-graphml From f5a735bf33dd47c5d9044d8d8110fd6011133c7f Mon Sep 17 00:00:00 2001 From: hirthjo <58222089+hirthjo@users.noreply.github.com> Date: Thu, 18 Mar 2021 11:26:43 +0100 Subject: [PATCH 006/112] Update IO.org --- doc/IO.org | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/IO.org b/doc/IO.org index e1c4e5f01..fe0fa3121 100644 --- a/doc/IO.org +++ b/doc/IO.org @@ -30,6 +30,7 @@ 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 See [[Common-FCA-File-Formats-for-Formal-Contexts.org][Common FCA File Formats for Formal Contexts]] for somewhat more details on those formats. From bb13d62d92b6116e678dd660d00b179d67d1a3cc Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Mon, 26 Apr 2021 16:13:09 +0200 Subject: [PATCH 007/112] Added Named Binary CSV Input and Doc --- ...n-FCA-File-Formats-for-Formal-Contexts.org | 31 +++++++++++++++++++ src/main/clojure/conexp/io/contexts.clj | 29 +++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org index ab89b9305..b6451580e 100644 --- a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org +++ b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org @@ -166,3 +166,34 @@ 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 :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 diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index dfc4055e7..228a455e8 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -380,6 +380,10 @@ (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 @@ -395,11 +399,36 @@ (recur (rest atts)))) (println))))) +(add-context-input-format :named-binary-csv + (fn [rdr] + (= "named binary CSV" (read-line)))) + +(define-context-input-format :named-binary-csv + [file] + (with-in-reader file + "named binary CSV" + (let [[_ & atts] (split (read-line) #",") + atts-idx (reduce #(assoc %1 %2 (.indexOf atts %2)) {} atts) + [o & second-line] (split (read-line) #",")] + (loop [objs #{o}, + incidence (set-of [o a] | a atts, :when (= (nth second-line (get atts-idx a)) "1"))] + (if-let [line (read-line)] + (let [[o & line] (split line #",")] + (recur (conj objs o) + (into incidence + (for [a atts :when (= (nth line (get atts-idx a)) "1")] + [o a])))) + (make-context objs atts incidence)))))) + (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")) + (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 From 899913cd13c486d18db3157a4d03c7b696d07cc6 Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Thu, 27 May 2021 18:36:44 +0200 Subject: [PATCH 008/112] Fixed NB write and detection, Added write options and FCA Output --- src/main/clojure/conexp/io/contexts.clj | 41 +++++++++++++++--------- src/main/clojure/conexp/io/util.clj | 7 ++-- src/test/clojure/conexp/io/util_test.clj | 2 +- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 228a455e8..25b52a634 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -401,7 +401,7 @@ (add-context-input-format :named-binary-csv (fn [rdr] - (= "named binary CSV" (read-line)))) + (= "NB" (subs (read-line) 0 2)))) (define-context-input-format :named-binary-csv [file] @@ -432,21 +432,11 @@ (let [objs (sort (objects ctx)), atts (sort (attributes ctx))] (with-out-writer file - (print "objects") - (doseq [m atts] - (print ", " m)) - (println) + (println (clojure.string/join "," (into ["NB"] atts))) (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))))) + (println (clojure.string/join "," + (into [g] + (map #(if (incident? ctx g %) 1 0) atts)))))))) ;; output as tex array @@ -591,6 +581,27 @@ (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}"))))) + + ;;; TODO ;; slf 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/test/clojure/conexp/io/util_test.clj b/src/test/clojure/conexp/io/util_test.clj index 6eb4a3c8e..fc76d97b5 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, From 79e44550cf0e977e7c6d93a9f969405a90fb711b Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Tue, 9 Nov 2021 16:53:14 +0100 Subject: [PATCH 009/112] LaTeX Output for lattices supports valuation functions --- doc/Concept-Lattices.org | 15 +++++++- doc/images/draw-lattice-02.png | Bin 0 -> 53429 bytes src/main/clojure/conexp/gui/draw.clj | 6 ++-- src/main/clojure/conexp/io/latex.clj | 49 ++++++++++++++++++--------- testing-data/latex/tikz-example.tex | 33 +++++++++--------- 5 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 doc/images/draw-lattice-02.png diff --git a/doc/Concept-Lattices.org b/doc/Concept-Lattices.org index b8b34bb02..8ab9aed9a 100644 --- a/doc/Concept-Lattices.org +++ b/doc/Concept-Lattices.org @@ -151,10 +151,23 @@ The layout used by ~draw-lattice~ is defined in ~standard-layout~. Other layouts can be provided to ~draw-lattice~ as an additional argument #+begin_src clojure :results silent -(draw-lattice (concept-lattice (adiag-context 2)) +(draw-concept-lattice (concept-lattice (adiag-context 2)) :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-concept-lattice (concept-lattice (adiag-context 2)) + :layout-fn inf-additive-layout + :value-fn (comp count first)) +#+end_src + +#+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. 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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 z lattice layout-fn (to-valued-layout value-fn)) args))) (defn draw-concept-lattice "Draws the concept lattice of a given context, passing all remaining 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/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} From f47b323818456a3114c52b9ec44023eae3c7bd23 Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Fri, 26 Nov 2021 11:51:11 +0100 Subject: [PATCH 010/112] Added gui I/O for layouts --- src/main/clojure/conexp/gui/draw.clj | 3 +- .../conexp/gui/draw/control/file_exporter.clj | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/main/clojure/conexp/gui/draw.clj b/src/main/clojure/conexp/gui/draw.clj index 5ac661df4..eb72cbf3d 100644 --- a/src/main/clojure/conexp/gui/draw.clj +++ b/src/main/clojure/conexp/gui/draw.clj @@ -61,7 +61,8 @@ "Freese" freese, "Force" improve-layout-by-force), snapshot-saver, - export-as-file) + export-as-file + import-from-file) ;; drawing area (doto canvas-panel 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) ;;; From 307181b32444219c90bfd329ea8b89e8d66fef56 Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Mon, 29 Nov 2021 15:02:26 +0100 Subject: [PATCH 011/112] More Valuations --- src/main/clojure/conexp/fca/lattices.clj | 15 ++++++++++ .../conexp/gui/draw/control/parameters.clj | 28 +++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/conexp/fca/lattices.clj b/src/main/clojure/conexp/fca/lattices.clj index 94547c4a7..7cb02ad08 100644 --- a/src/main/clojure/conexp/fca/lattices.clj +++ b/src/main/clojure/conexp/fca/lattices.clj @@ -291,6 +291,21 @@ (fn [x y] ((order lat) [x y])))) +(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/gui/draw/control/parameters.clj b/src/main/clojure/conexp/gui/draw/control/parameters.clj index 2ab4ec291..54b1ffc6f 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] @@ -17,7 +18,8 @@ (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) + modularity-valuation-mode, distributivity-valuation-mode, + support-valuation-mode, stability-valuation-mode, probability-valuation-mode) ;;; @@ -70,7 +72,10 @@ "count-ints" 'count-int-valuation-mode, "count-exts" 'count-ext-valuation-mode, "distributivity" 'distributivity-valuation-mode, - "modularity" 'modularity-valuation-mode} + "modularity" 'modularity-valuation-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 @@ -303,6 +308,23 @@ [lat c] (float (elements-distributivity lat 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] "") From f50921e27c775fb99f2da27d9273df9be54084a3 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Tue, 30 Nov 2021 20:21:14 +0100 Subject: [PATCH 012/112] Fixed concept probability for concepts having |extent|=|G| --- src/main/clojure/conexp/fca/metrics.clj | 47 ++++++++++++++----------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/main/clojure/conexp/fca/metrics.clj b/src/main/clojure/conexp/fca/metrics.clj index 7cba84742..755657854 100644 --- a/src/main/clojure/conexp/fca/metrics.clj +++ b/src/main/clojure/conexp/fca/metrics.clj @@ -89,30 +89,35 @@ (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))))))) - - + (println P_M_B) + (println p_B) + (println one_minus_p_B_n) + (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 From 61fbc1d67db2888bbce3f15475e12b77832712e7 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Tue, 30 Nov 2021 20:23:35 +0100 Subject: [PATCH 013/112] Fixed concept probability ... debugging prints ;) --- src/main/clojure/conexp/fca/metrics.clj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/clojure/conexp/fca/metrics.clj b/src/main/clojure/conexp/fca/metrics.clj index 755657854..0d1befbbf 100644 --- a/src/main/clojure/conexp/fca/metrics.clj +++ b/src/main/clojure/conexp/fca/metrics.clj @@ -93,9 +93,6 @@ 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)] - (println P_M_B) - (println p_B) - (println one_minus_p_B_n) (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 From 9c0981731917803fcf4152f1229668cbe52d2966 Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Tue, 7 Dec 2021 12:08:54 +0100 Subject: [PATCH 014/112] Added documentation --- src/main/clojure/conexp/fca/metrics.clj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/conexp/fca/metrics.clj b/src/main/clojure/conexp/fca/metrics.clj index af44cff69..6dba78015 100644 --- a/src/main/clojure/conexp/fca/metrics.clj +++ b/src/main/clojure/conexp/fca/metrics.clj @@ -54,10 +54,16 @@ (/ (counter #{} extent) (expt 2 (count extent))))) -(defn concept-separation +(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." + 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.") From 389819cf2f93ffd55ea435678d03c6fdbda4b847 Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Tue, 7 Dec 2021 12:14:42 +0100 Subject: [PATCH 015/112] Added separation-index to the gui --- .../clojure/conexp/gui/draw/control/parameters.clj | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/conexp/gui/draw/control/parameters.clj b/src/main/clojure/conexp/gui/draw/control/parameters.clj index 2ab4ec291..c1b98b6db 100644 --- a/src/main/clojure/conexp/gui/draw/control/parameters.clj +++ b/src/main/clojure/conexp/gui/draw/control/parameters.clj @@ -17,7 +17,8 @@ (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) + modularity-valuation-mode, distributivity-valuation-mode, + separation-index-mode) ;;; @@ -70,7 +71,8 @@ "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} ^JComboBox combo-box (make-combo-box buttons (keys valuation-modes)), current-valuation-mode (atom (valuation-modes "none"))] (listen combo-box :action @@ -303,6 +305,11 @@ [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- no-valuation-mode [_ concept] "") From 024def3d82024bc2740fe54072f6bba8c1791609 Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Tue, 7 Dec 2021 14:17:51 +0100 Subject: [PATCH 016/112] Fixed syntax error --- src/main/clojure/conexp/gui/draw/control/parameters.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/gui/draw/control/parameters.clj b/src/main/clojure/conexp/gui/draw/control/parameters.clj index 9b969bd4a..b2955e9e2 100644 --- a/src/main/clojure/conexp/gui/draw/control/parameters.clj +++ b/src/main/clojure/conexp/gui/draw/control/parameters.clj @@ -75,7 +75,7 @@ "count-exts" 'count-ext-valuation-mode, "distributivity" 'distributivity-valuation-mode, "modularity" 'modularity-valuation-mode, - "separation-index" 'separation-index-mode} + "separation-index" 'separation-index-mode "support" 'support-valuation-mode, "stability" 'stability-valuation-mode, "probability" 'probability-valuation-mode} From b48a82a9f9840ff3e643d726595324e3a2626095 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Wed, 15 Dec 2021 18:14:16 +0100 Subject: [PATCH 017/112] Fixed code junk found in metrics. --- src/main/clojure/conexp/fca/metrics.clj | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/clojure/conexp/fca/metrics.clj b/src/main/clojure/conexp/fca/metrics.clj index a60b92655..6068d1650 100644 --- a/src/main/clojure/conexp/fca/metrics.clj +++ b/src/main/clojure/conexp/fca/metrics.clj @@ -692,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 From 940f2826bb4eb51ba6fa4197aed9f71e893659bf Mon Sep 17 00:00:00 2001 From: Jana Date: Thu, 16 Dec 2021 10:03:16 +0100 Subject: [PATCH 018/112] add read & write of json format for implications --- project.clj | 2 +- src/main/clojure/conexp/io/implications.clj | 53 +++++++++++++++++++++ src/main/clojure/conexp/main.clj | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/main/clojure/conexp/io/implications.clj diff --git a/project.clj b/project.clj index 64515acb3..b169ccb98 100644 --- a/project.clj +++ b/project.clj @@ -17,7 +17,7 @@ :dependencies [[org.clojure/clojure "1.10.1"] [org.clojure/core.async "1.3.610"] [org.clojure/data.int-map "1.0.0"] - [org.clojure/data.json "1.0.0"] + [org.clojure/data.json "2.4.0"] [org.clojure/data.xml "0.0.8"] [org.clojure/math.combinatorics "0.1.6"] [org.clojure/math.numeric-tower "0.0.4"] diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj new file mode 100644 index 000000000..0e8dbac0a --- /dev/null +++ b/src/main/clojure/conexp/io/implications.clj @@ -0,0 +1,53 @@ +(ns conexp.io.implications + (:require [clojure.data.json :as json] + [conexp.fca.implications :refer :all] + [conexp.io.util :refer :all])) + +;;; Input format dispatch + +(define-format-dispatch "implication") +(set-default-implication-format! :json) + +;;; Formats + +;; Json helpers + +(defn implication->json + [impl] + (json/write-str {:premise (premise impl) + :conclusion (conclusion impl)})) + +;;; TODO: adjustments needed +(defn implications->json + [impl] + (map implication->json impl)) + +(defn json->implication + [json] + (make-implication (into #{} (:premise json)) + (into #{} (:conclusion json)))) + +(defn json->implications + [json] + (let [impl (:implications json)] + (map json->implication impl))) + +;; Json Format + +(add-implication-input-format :json + (fn [rdr] + (= "json" (read-line)))) + +;;; TODO: adjustments needed +(define-implication-output-format :json + [impl file] + (with-out-writer file + (println "json") + (prn (implications->json impl)))) + +(define-implication-input-format :json + [file] + (with-in-reader file + (let [_ (get-line) + impl (json/read *in* :key-fn keyword)] + (json->implications impl)))) diff --git a/src/main/clojure/conexp/main.clj b/src/main/clojure/conexp/main.clj index 4783bbca3..860e5d7ea 100644 --- a/src/main/clojure/conexp/main.clj +++ b/src/main/clojure/conexp/main.clj @@ -27,6 +27,7 @@ conexp.fca.more conexp.io.latex conexp.io.contexts + conexp.io.implications conexp.io.lattices conexp.io.layouts conexp.io.many-valued-contexts From 38c73204b48a399433ff87f710c01d8ae7d465ae Mon Sep 17 00:00:00 2001 From: Jana Date: Thu, 16 Dec 2021 15:33:30 +0100 Subject: [PATCH 019/112] correct read and write format for implications --- src/main/clojure/conexp/io/implications.clj | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index 0e8dbac0a..3bc136ab1 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -17,15 +17,16 @@ (json/write-str {:premise (premise impl) :conclusion (conclusion impl)})) -;;; TODO: adjustments needed (defn implications->json [impl] - (map implication->json impl)) + (json/write-str {:implications + (mapv implication->json impl)})) (defn json->implication - [json] - (make-implication (into #{} (:premise json)) - (into #{} (:conclusion json)))) + [json-impl] + (let [map-impl (json/read-str json-impl :key-fn keyword)] + (make-implication (into #{} (:premise map-impl)) + (into #{} (:conclusion map-impl))))) (defn json->implications [json] @@ -38,12 +39,11 @@ (fn [rdr] (= "json" (read-line)))) -;;; TODO: adjustments needed (define-implication-output-format :json [impl file] (with-out-writer file (println "json") - (prn (implications->json impl)))) + (print (implications->json impl)))) (define-implication-input-format :json [file] From 80af3720af64c99303877fed1885f1e86f9e41ec Mon Sep 17 00:00:00 2001 From: Jana Date: Fri, 17 Dec 2021 08:40:53 +0100 Subject: [PATCH 020/112] add oi and oioi tests for json implication format --- .../clojure/conexp/io/implications_test.clj | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/test/clojure/conexp/io/implications_test.clj 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..5533a5ebc --- /dev/null +++ b/src/test/clojure/conexp/io/implications_test.clj @@ -0,0 +1,32 @@ +(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)))) + +(deftest test-implications-out-in-out-in + (with-testing-data [impl implications-oi, + fmt (list-implication-formats)] + (try (out-in-out-in-test impl 'implication fmt) + (catch UnsupportedOperationException _ true)))) From 1fb73de21b7c31112a42e201df5a9ae17f8bf8b2 Mon Sep 17 00:00:00 2001 From: Jana Date: Tue, 21 Dec 2021 12:09:22 +0100 Subject: [PATCH 021/112] add json context input format --- src/main/clojure/conexp/io/contexts.clj | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 25b52a634..aa8c773b7 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -13,7 +13,8 @@ [conexp.fca.many-valued-contexts :refer :all] [conexp.io.latex :refer :all] [clojure.string :refer (split)] - [clojure.data.xml :as xml]) + [clojure.data.xml :as xml] + [clojure.data.json :as json]) (:import [java.io PushbackReader])) @@ -601,6 +602,25 @@ "}{" o "}"))) (println "\\end{cxt}"))))) +;; Json Format + +(defn object->incidence + [object] + (set-of [o a] [o [(:object object)], a (:attributes object)])) + +(add-context-input-format :json + (fn [rdr] + (= "json" (read-line)))) + +(define-context-input-format :json + [file] + (with-in-reader file + (let [_ (conexp.io.util/get-line) + json-ctx (:formal_context (json/read *in* :key-fn keyword)) + objects (map :object json-ctx) + attributes (distinct (flatten (map :attributes json-ctx))) + incidence (apply union (mapv object->incidence json-ctx))] + (make-context objects attributes incidence)))) ;;; TODO From e642a8f628a73ad233b7ddb154ffcf7c35b14d3c Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 22 Dec 2021 09:10:26 +0100 Subject: [PATCH 022/112] add json context output format and improve implication output format --- src/main/clojure/conexp/io/contexts.clj | 26 +++++++++++++++++---- src/main/clojure/conexp/io/implications.clj | 21 ++++++++--------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index aa8c773b7..4b44eecde 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -602,20 +602,38 @@ "}{" o "}"))) (println "\\end{cxt}"))))) -;; Json Format +;; Json helpers -(defn object->incidence +(defn- object->incidence [object] (set-of [o a] [o [(:object object)], a (:attributes object)])) +(defn object->json + [ctx object] + {:object object + :attributes (mapv second (filter #(= object (first %)) (incidence ctx)))}) + +(defn ctx->json + [ctx] + (json/write-str {:formal_context + (mapv (partial object->json ctx) (objects ctx))})) + +;; Json Format + (add-context-input-format :json (fn [rdr] - (= "json" (read-line)))) + (= "json ctx" (read-line)))) + +(define-context-output-format :json + [ctx file] + (with-out-writer file + (println "json ctx") + (print (ctx->json ctx)))) (define-context-input-format :json [file] (with-in-reader file - (let [_ (conexp.io.util/get-line) + (let [_ (get-line) json-ctx (:formal_context (json/read *in* :key-fn keyword)) objects (map :object json-ctx) attributes (distinct (flatten (map :attributes json-ctx))) diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index 3bc136ab1..5fb305e70 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -12,23 +12,22 @@ ;; Json helpers -(defn implication->json +(defn- implication->json [impl] - (json/write-str {:premise (premise impl) - :conclusion (conclusion impl)})) + {:premise (premise impl) + :conclusion (conclusion impl)}) -(defn implications->json +(defn- implications->json [impl] (json/write-str {:implications (mapv implication->json impl)})) -(defn json->implication +(defn- json->implication [json-impl] - (let [map-impl (json/read-str json-impl :key-fn keyword)] - (make-implication (into #{} (:premise map-impl)) - (into #{} (:conclusion map-impl))))) + (make-implication (into #{} (:premise json-impl)) + (into #{} (:conclusion json-impl)))) -(defn json->implications +(defn- json->implications [json] (let [impl (:implications json)] (map json->implication impl))) @@ -37,12 +36,12 @@ (add-implication-input-format :json (fn [rdr] - (= "json" (read-line)))) + (= "json impl" (read-line)))) (define-implication-output-format :json [impl file] (with-out-writer file - (println "json") + (println "json impl") (print (implications->json impl)))) (define-implication-input-format :json From 2f15cbd8e78b4bed513137895a8708849236aa40 Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 22 Dec 2021 10:51:27 +0100 Subject: [PATCH 023/112] add json concept input & output format --- src/main/clojure/conexp/io/lattices.clj | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 06979452c..e739cd65f 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -7,6 +7,7 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.io.lattices + (:require [clojure.data.json :as json]) (:use conexp.base conexp.math.algebra conexp.fca.lattices @@ -48,6 +49,43 @@ (illegal-argument "File " file " does not contain a lattice.")) (apply make-lattice lattice)))) +;; Json helpers + +(defn- json->concept + [json-concept] + [(into #{} (:extent json-concept)) + (into #{} (:intent json-concept))]) + +(defn- concept->json + [concept] + {:extent (first concept) + :intent (second concept)}) + +(defn- concepts->json + [concepts] + (json/write-str {:formal_concepts + (mapv concept->json concepts)})) + +;; Json Format +;; TODO: adapt for lattice, at the moment it only works for concepts + +(add-lattice-input-format :json + (fn [rdr] + (= "json lat" (read-line)))) + +(define-lattice-output-format :json + [concepts file] + (with-out-writer file + (println "json lat") + (print (concepts->json concepts)))) + +(define-lattice-input-format :json + [file] + (with-in-reader file + (let [_ (get-line) + json-lat (:formal_concepts (json/read *in* :key-fn keyword))] + (map json->concept json-lat)))) + ;;; ConExp lattice format 'TODO From 3015305d7ba876c3a10326babfaaeecd1250eac1 Mon Sep 17 00:00:00 2001 From: Jana Date: Tue, 11 Jan 2022 09:56:44 +0100 Subject: [PATCH 024/112] Add json input & output for whole process (context + concepts + implications) --- src/main/clojure/conexp/io/contexts.clj | 22 ++++++----- src/main/clojure/conexp/io/implications.clj | 10 ++--- src/main/clojure/conexp/io/lattices.clj | 9 ++--- src/main/clojure/conexp/io/process.clj | 41 +++++++++++++++++++++ src/main/clojure/conexp/main.clj | 1 + 5 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 src/main/clojure/conexp/io/process.clj diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 4b44eecde..325684150 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -615,9 +615,15 @@ (defn ctx->json [ctx] - (json/write-str {:formal_context - (mapv (partial object->json ctx) (objects ctx))})) - + {:formal_context + (mapv (partial object->json ctx) (objects ctx))}) + +(defn json->ctx + [json-ctx] + (let [objects (map :object json-ctx) + attributes (distinct (flatten (map :attributes json-ctx))) + incidence (apply union (mapv object->incidence json-ctx))] + (make-context objects attributes incidence))) ;; Json Format (add-context-input-format :json @@ -628,17 +634,15 @@ [ctx file] (with-out-writer file (println "json ctx") - (print (ctx->json ctx)))) + (print (json/write-str (ctx->json ctx))))) (define-context-input-format :json [file] (with-in-reader file (let [_ (get-line) - json-ctx (:formal_context (json/read *in* :key-fn keyword)) - objects (map :object json-ctx) - attributes (distinct (flatten (map :attributes json-ctx))) - incidence (apply union (mapv object->incidence json-ctx))] - (make-context objects attributes incidence)))) + json-ctx (:formal_context (json/read *in* :key-fn keyword))] + (json->ctx json-ctx) + ))) ;;; TODO diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index 5fb305e70..195774210 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -17,17 +17,17 @@ {:premise (premise impl) :conclusion (conclusion impl)}) -(defn- implications->json +(defn implications->json [impl] - (json/write-str {:implications - (mapv implication->json impl)})) + {:implications + (mapv implication->json impl)}) (defn- json->implication [json-impl] (make-implication (into #{} (:premise json-impl)) (into #{} (:conclusion json-impl)))) -(defn- json->implications +(defn json->implications [json] (let [impl (:implications json)] (map json->implication impl))) @@ -42,7 +42,7 @@ [impl file] (with-out-writer file (println "json impl") - (print (implications->json impl)))) + (print (json/write-str (implications->json impl))))) (define-implication-input-format :json [file] diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index e739cd65f..dc3d37cef 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -51,7 +51,7 @@ ;; Json helpers -(defn- json->concept +(defn json->concept [json-concept] [(into #{} (:extent json-concept)) (into #{} (:intent json-concept))]) @@ -61,10 +61,9 @@ {:extent (first concept) :intent (second concept)}) -(defn- concepts->json +(defn concepts->json [concepts] - (json/write-str {:formal_concepts - (mapv concept->json concepts)})) + {:formal_concepts (mapv concept->json concepts)}) ;; Json Format ;; TODO: adapt for lattice, at the moment it only works for concepts @@ -77,7 +76,7 @@ [concepts file] (with-out-writer file (println "json lat") - (print (concepts->json concepts)))) + (print (json/write-str (concepts->json concepts))))) (define-lattice-input-format :json [file] diff --git a/src/main/clojure/conexp/io/process.clj b/src/main/clojure/conexp/io/process.clj new file mode 100644 index 000000000..41c0e2bca --- /dev/null +++ b/src/main/clojure/conexp/io/process.clj @@ -0,0 +1,41 @@ +(ns conexp.io.process + (:require [conexp.io.contexts :refer :all] + [conexp.io.implications :refer :all] + [conexp.io.lattices :refer :all] + [conexp.io.util :refer :all] + [clojure.data.json :as json])) + +;;; Input format dispatch + +(define-format-dispatch "process") +(set-default-process-format! :json) + +;;; Formats + +;; Json Format + +(add-process-input-format :json + (fn [rdr] + (= "json" (read-line)))) + +(define-process-output-format :json + [[ctx concepts implications] file] + (with-out-writer file + (println "json") + (print + (json/write-str {:context (ctx->json ctx) + :concepts (concepts->json concepts) + :implication_sets [(implications->json implications)]})))) + +(define-process-input-format :json + [file] + (with-in-reader file + (let [_ (get-line) + json-process (json/read *in* :key-fn keyword) + json-ctx (:context json-process) + json-concepts (:concepts json-process) + json-implications (:implication_sets json-process)] + [(json->ctx (:formal_context json-ctx)) + (map json->concept (:formal_concepts json-concepts)) + (map json->implications json-implications)])) + ) diff --git a/src/main/clojure/conexp/main.clj b/src/main/clojure/conexp/main.clj index 860e5d7ea..80753a526 100644 --- a/src/main/clojure/conexp/main.clj +++ b/src/main/clojure/conexp/main.clj @@ -31,6 +31,7 @@ conexp.io.lattices conexp.io.layouts conexp.io.many-valued-contexts + conexp.io.process conexp.layouts]) (apply use conexp-clj-namespaces) From 993bc6134c838bd692f0b394be654e52e8414829 Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 17 Jan 2022 15:44:46 +0100 Subject: [PATCH 025/112] rename process.clj to fca.clj and change output from triple to map --- src/main/clojure/conexp/io/fca.clj | 48 +++++++++++++++++++++ src/main/clojure/conexp/io/implications.clj | 8 ++++ src/main/clojure/conexp/io/process.clj | 41 ------------------ src/main/clojure/conexp/main.clj | 2 +- 4 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 src/main/clojure/conexp/io/fca.clj delete mode 100644 src/main/clojure/conexp/io/process.clj diff --git a/src/main/clojure/conexp/io/fca.clj b/src/main/clojure/conexp/io/fca.clj new file mode 100644 index 000000000..fe0ddcf58 --- /dev/null +++ b/src/main/clojure/conexp/io/fca.clj @@ -0,0 +1,48 @@ +;; 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.fca + (:require [conexp.io.contexts :refer :all] + [conexp.io.implications :refer :all] + [conexp.io.lattices :refer :all] + [conexp.io.util :refer :all] + [clojure.data.json :as json])) + +;;; Input format dispatch + +(define-format-dispatch "fca") +(set-default-fca-format! :json) + +;;; Formats + +;; Json Format + +(add-fca-input-format :json + (fn [rdr] + (= "json" (read-line)))) + +(define-fca-output-format :json + [[ctx concepts implications] file] + (with-out-writer file + (println "json") + (print + (json/write-str {:context (ctx->json ctx) + :concepts (concepts->json concepts) + :implication_sets [(implications->json implications)]})))) + +(define-fca-input-format :json + [file] + (with-in-reader file + (let [_ (get-line) + json-fca (json/read *in* :key-fn keyword) + json-ctx (:context json-fca) + json-concepts (:concepts json-fca) + json-implications (:implication_sets json-fca)] + {:context (json->ctx (:formal_context json-ctx)) + :concepts (map json->concept (:formal_concepts json-concepts)) + :implication-sets (map json->implications json-implications)}))) diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index 195774210..df8e3306f 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -1,3 +1,11 @@ +;; 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 [clojure.data.json :as json] [conexp.fca.implications :refer :all] diff --git a/src/main/clojure/conexp/io/process.clj b/src/main/clojure/conexp/io/process.clj deleted file mode 100644 index 41c0e2bca..000000000 --- a/src/main/clojure/conexp/io/process.clj +++ /dev/null @@ -1,41 +0,0 @@ -(ns conexp.io.process - (:require [conexp.io.contexts :refer :all] - [conexp.io.implications :refer :all] - [conexp.io.lattices :refer :all] - [conexp.io.util :refer :all] - [clojure.data.json :as json])) - -;;; Input format dispatch - -(define-format-dispatch "process") -(set-default-process-format! :json) - -;;; Formats - -;; Json Format - -(add-process-input-format :json - (fn [rdr] - (= "json" (read-line)))) - -(define-process-output-format :json - [[ctx concepts implications] file] - (with-out-writer file - (println "json") - (print - (json/write-str {:context (ctx->json ctx) - :concepts (concepts->json concepts) - :implication_sets [(implications->json implications)]})))) - -(define-process-input-format :json - [file] - (with-in-reader file - (let [_ (get-line) - json-process (json/read *in* :key-fn keyword) - json-ctx (:context json-process) - json-concepts (:concepts json-process) - json-implications (:implication_sets json-process)] - [(json->ctx (:formal_context json-ctx)) - (map json->concept (:formal_concepts json-concepts)) - (map json->implications json-implications)])) - ) diff --git a/src/main/clojure/conexp/main.clj b/src/main/clojure/conexp/main.clj index 80753a526..8d99b737f 100644 --- a/src/main/clojure/conexp/main.clj +++ b/src/main/clojure/conexp/main.clj @@ -31,7 +31,7 @@ conexp.io.lattices conexp.io.layouts conexp.io.many-valued-contexts - conexp.io.process + conexp.io.fca conexp.layouts]) (apply use conexp-clj-namespaces) From eb96c07b79dfadcd8239bfb3a3d188e625a75e97 Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 17 Jan 2022 16:14:09 +0100 Subject: [PATCH 026/112] add validation for fca-schema --- project.clj | 3 +- src/main/clojure/conexp/io/fca.clj | 12 +- .../resources/schemas/fca_schema_v1.0.json | 209 ++++++++++++++++++ 3 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/schemas/fca_schema_v1.0.json diff --git a/project.clj b/project.clj index b169ccb98..34b9ebcee 100644 --- a/project.clj +++ b/project.clj @@ -37,7 +37,8 @@ [ring/ring-core "1.8.2"] [ring/ring-json "0.5.0"] [http-kit "2.5.0"] - [org.apache.commons/commons-math3 "3.6.1"]] + [org.apache.commons/commons-math3 "3.6.1"] + [luposlip/json-schema "0.3.3"]] :profiles {:uberjar {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"]] diff --git a/src/main/clojure/conexp/io/fca.clj b/src/main/clojure/conexp/io/fca.clj index fe0ddcf58..9c1355bab 100644 --- a/src/main/clojure/conexp/io/fca.clj +++ b/src/main/clojure/conexp/io/fca.clj @@ -11,7 +11,8 @@ [conexp.io.implications :refer :all] [conexp.io.lattices :refer :all] [conexp.io.util :refer :all] - [clojure.data.json :as json])) + [clojure.data.json :as json] + [json-schema.core :as json-schema])) ;;; Input format dispatch @@ -24,7 +25,11 @@ (add-fca-input-format :json (fn [rdr] - (= "json" (read-line)))) + (= :success + (let [schema (json-schema/prepare-schema (-> "src/main/resources/schemas/fca_schema_v1.0.json" slurp (cheshire.core/parse-string true))) + json (json/read rdr)] + (json-schema/validate schema json) + :success)))) (define-fca-output-format :json [[ctx concepts implications] file] @@ -38,8 +43,7 @@ (define-fca-input-format :json [file] (with-in-reader file - (let [_ (get-line) - json-fca (json/read *in* :key-fn keyword) + (let [json-fca (json/read *in* :key-fn keyword) json-ctx (:context json-fca) json-concepts (:concepts json-fca) json-implications (:implication_sets json-fca)] 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..b1d87679c --- /dev/null +++ b/src/main/resources/schemas/fca_schema_v1.0.json @@ -0,0 +1,209 @@ +{ + "$id": "https://example.com/schemas/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": { + "type": "string", + "description": "JSON schema for a single object." + }, + "attribute": { + "type": "string", + "description": "JSON schema for a single attribute." + }, + "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": "#/$defs/attribute" + } + }, + "conclusion": { + "description": "The conclusion of the implication", + "type": "array", + "items": { + "$ref": "#/$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 + }, + "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).", + "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" + ] + }, + "implications": { + "type": "array", + "items": { + "$ref": "#/$defs/implication" + } + }, + "additional information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false, + "$comment": "If 'is_base' is false, then a property named 'base_type' may not exist.", + "if": { + "properties": { + "is_base": { + "const": false + } + } + }, + "then": { + "properties": { + "base_type": { + "not": {} + } + } + }, + "$comment": "If 'base_type' exists, then 'is_base' has to exist as well.", + "dependentRequired": { + "base_type": [ + "is_base" + ] + } + } + }, + "properties": { + "source": { + "type": "string", + "description": "In 'source', the source of the context / data can be added." + }, + "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.", + "required": [ + "formal_context" + ], + "properties": { + "formal_context": { + "type": "array", + "items": { + "item": { + "type": "object", + "properties": { + "$ref": "#/$defs/object", + "attributes": { + "type": "array", + "description": "List of attributes", + "items": { + "$ref": "#/$defs/attribute" + } + } + } + } + } + }, + "additional_information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false + }, + "concepts": { + "type": "object", + "description": "JSON schema for a set of formal concepts. Each concept consists of the extent (an array of objects) and the intent (an array of attributes).", + "required": [ + "formal_concepts" + ], + "properties": { + "formal_concepts": { + "type": "array", + "items": { + "formal_concept": { + "type": "object", + "properties": { + "extent": { + "type": "array", + "description": "Array of objects", + "items": { + "$ref": "#/$defs/object" + } + }, + "intent": { + "type": "array", + "description": "Array of attributes", + "items": { + "$ref": "#/$defs/attribute" + } + }, + "valuation": { + "type": "number" + } + } + } + } + }, + "additional_information": { + "type": "string" + } + }, + "additionalProperties": false + }, + "implication_sets": { + "type": "array", + "description": "Several sets of implications can be added.", + "items": { + "$ref": "#/$defs/set_of_implications" + } + }, + "additional_information": { + "description": "Information that is additional to the defined properties", + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "context" + ] +} From 8d4091f8d586b58fbead0f2a4e5b1598520d10d3 Mon Sep 17 00:00:00 2001 From: Jana Date: Tue, 18 Jan 2022 14:49:34 +0100 Subject: [PATCH 027/112] fix io/context tests that were failing for :json format --- src/main/clojure/conexp/io/contexts.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 325684150..d31be942f 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -611,7 +611,7 @@ (defn object->json [ctx object] {:object object - :attributes (mapv second (filter #(= object (first %)) (incidence ctx)))}) + :attributes (filter #(incident? ctx object %) (attributes ctx))}) (defn ctx->json [ctx] @@ -624,6 +624,7 @@ attributes (distinct (flatten (map :attributes json-ctx))) incidence (apply union (mapv object->incidence json-ctx))] (make-context objects attributes incidence))) + ;; Json Format (add-context-input-format :json From 4c095194c52efa6276874e848dabaa841d8ab97a Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 19 Jan 2022 15:59:54 +0100 Subject: [PATCH 028/112] json format: lattices can be saved with order now --- src/main/clojure/conexp/io/lattices.clj | 38 ++++++++-- .../resources/schemas/fca_schema_v1.0.json | 74 +++++++++++++------ 2 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index dc3d37cef..63296a995 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -61,29 +61,51 @@ {:extent (first concept) :intent (second concept)}) -(defn concepts->json - [concepts] - {:formal_concepts (mapv concept->json concepts)}) +(defn- link->json + [link] + {:start_concept (concept->json (first link)) + :end_concept (concept->json (second link))}) + +(defn- lattice-structure->json + [lattice] + (let [links (set-of [x y] + [x (base-set lattice) + y (base-set lattice) + :when ((order lattice) [x y])])] + (map link->json links))) + +(defn- lattice->json + [lat] + {:formal_concepts (mapv concept->json (base-set lat)) + :lattice_structure (lattice-structure->json lat)}) + +(defn- json->lattice-order + [json-lattice-order] + [(into [] (json->concept (:start_concept json-lattice-order))) + (into [] (json->concept (:end_concept json-lattice-order)))]) ;; Json Format -;; TODO: adapt for lattice, at the moment it only works for concepts (add-lattice-input-format :json (fn [rdr] (= "json lat" (read-line)))) (define-lattice-output-format :json - [concepts file] + [lattice file] (with-out-writer file (println "json lat") - (print (json/write-str (concepts->json concepts))))) + (print (json/write-str (lattice->json lattice))))) (define-lattice-input-format :json [file] (with-in-reader file (let [_ (get-line) - json-lat (:formal_concepts (json/read *in* :key-fn keyword))] - (map json->concept json-lat)))) + json-lattice (json/read *in* :key-fn keyword) + json-concepts (:formal_concepts json-lattice) + json-lattice-structure (:lattice_structure json-lattice) + lattice-base-set (map json->concept json-concepts) + lattice-order (map json->lattice-order json-lattice-structure)] + (make-lattice lattice-base-set lattice-order)))) ;;; ConExp lattice format diff --git a/src/main/resources/schemas/fca_schema_v1.0.json b/src/main/resources/schemas/fca_schema_v1.0.json index b1d87679c..d7baae130 100644 --- a/src/main/resources/schemas/fca_schema_v1.0.json +++ b/src/main/resources/schemas/fca_schema_v1.0.json @@ -111,6 +111,47 @@ "is_base" ] } + }, + "formal_concept": { + "type": "object", + "required": [ + "extent", + "intent" + ], + "properties": { + "extent": { + "type": "array", + "description": "Array of objects", + "items": { + "$ref": "#/$defs/object" + } + }, + "intent": { + "type": "array", + "description": "Array of attributes", + "items": { + "$ref": "#/$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" + } + } } }, "properties": { @@ -150,9 +191,9 @@ }, "additionalProperties": false }, - "concepts": { + "lattice": { "type": "object", - "description": "JSON schema for a set of formal concepts. Each concept consists of the extent (an array of objects) and the intent (an array of attributes).", + "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.", "required": [ "formal_concepts" ], @@ -160,28 +201,13 @@ "formal_concepts": { "type": "array", "items": { - "formal_concept": { - "type": "object", - "properties": { - "extent": { - "type": "array", - "description": "Array of objects", - "items": { - "$ref": "#/$defs/object" - } - }, - "intent": { - "type": "array", - "description": "Array of attributes", - "items": { - "$ref": "#/$defs/attribute" - } - }, - "valuation": { - "type": "number" - } - } - } + "$ref": "#/$defs/formal_concept" + } + }, + "lattice_structure": { + "type": "array", + "items": { + "$ref": "#/$defs/link" } }, "additional_information": { From 7be229ada540d760256b179d0fba2bb59a663935 Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 19 Jan 2022 17:14:39 +0100 Subject: [PATCH 029/112] improve tests for reading / writing of implications --- .../clojure/conexp/io/implications_test.clj | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/test/clojure/conexp/io/implications_test.clj b/src/test/clojure/conexp/io/implications_test.clj index 5533a5ebc..1af53797e 100644 --- a/src/test/clojure/conexp/io/implications_test.clj +++ b/src/test/clojure/conexp/io/implications_test.clj @@ -1,3 +1,11 @@ +;; 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 @@ -11,9 +19,9 @@ (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"]})) + #{"1" "2" "3"} + #{["a" "1"] ["a" "3"] + ["b" "2"] ["c" "3"]})) (def- implications-oi "Implications to use for out-in testing" @@ -24,9 +32,17 @@ 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-oi, + (with-testing-data [impl implications-oioi, fmt (list-implication-formats)] (try (out-in-out-in-test impl 'implication fmt) (catch UnsupportedOperationException _ true)))) From 05ffc469c054bf2650dfa792e3edf76e208ba70b Mon Sep 17 00:00:00 2001 From: Jana Date: Thu, 27 Jan 2022 17:14:00 +0100 Subject: [PATCH 030/112] change fca_schema, so that objects and attributes can be of type string and number --- .../resources/schemas/fca_schema_v1.0.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/resources/schemas/fca_schema_v1.0.json b/src/main/resources/schemas/fca_schema_v1.0.json index d7baae130..ae12a9b55 100644 --- a/src/main/resources/schemas/fca_schema_v1.0.json +++ b/src/main/resources/schemas/fca_schema_v1.0.json @@ -6,11 +6,25 @@ "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": { - "type": "string", + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ], "description": "JSON schema for a single object." }, "attribute": { - "type": "string", + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ], "description": "JSON schema for a single attribute." }, "implication": { From b9ad105a2029e809cd2f59824e7fdf0495b92c41 Mon Sep 17 00:00:00 2001 From: Jana Date: Thu, 27 Jan 2022 17:16:51 +0100 Subject: [PATCH 031/112] adapt read-fca and write-fca after change of lattice format --- src/main/clojure/conexp/io/fca.clj | 9 ++++----- src/main/clojure/conexp/io/lattices.clj | 20 ++++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/clojure/conexp/io/fca.clj b/src/main/clojure/conexp/io/fca.clj index 9c1355bab..f7d18cd49 100644 --- a/src/main/clojure/conexp/io/fca.clj +++ b/src/main/clojure/conexp/io/fca.clj @@ -32,12 +32,11 @@ :success)))) (define-fca-output-format :json - [[ctx concepts implications] file] + [[ctx lattice implications] file] (with-out-writer file - (println "json") (print (json/write-str {:context (ctx->json ctx) - :concepts (concepts->json concepts) + :lattice (lattice->json lattice) :implication_sets [(implications->json implications)]})))) (define-fca-input-format :json @@ -45,8 +44,8 @@ (with-in-reader file (let [json-fca (json/read *in* :key-fn keyword) json-ctx (:context json-fca) - json-concepts (:concepts json-fca) + json-lattice (:lattice json-fca) json-implications (:implication_sets json-fca)] {:context (json->ctx (:formal_context json-ctx)) - :concepts (map json->concept (:formal_concepts json-concepts)) + :lattice (json->lattice json-lattice) :implication-sets (map json->implications json-implications)}))) diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 63296a995..9d70e4399 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -51,7 +51,7 @@ ;; Json helpers -(defn json->concept +(defn- json->concept [json-concept] [(into #{} (:extent json-concept)) (into #{} (:intent json-concept))]) @@ -74,7 +74,7 @@ :when ((order lattice) [x y])])] (map link->json links))) -(defn- lattice->json +(defn lattice->json [lat] {:formal_concepts (mapv concept->json (base-set lat)) :lattice_structure (lattice-structure->json lat)}) @@ -84,6 +84,14 @@ [(into [] (json->concept (:start_concept json-lattice-order))) (into [] (json->concept (:end_concept json-lattice-order)))]) +(defn json->lattice + [json-lattice] + (let [json-concepts (:formal_concepts json-lattice) + json-lattice-structure (:lattice_structure json-lattice) + lattice-base-set (map json->concept json-concepts) + lattice-order (map json->lattice-order json-lattice-structure)] + (make-lattice lattice-base-set lattice-order))) + ;; Json Format (add-lattice-input-format :json @@ -100,12 +108,8 @@ [file] (with-in-reader file (let [_ (get-line) - json-lattice (json/read *in* :key-fn keyword) - json-concepts (:formal_concepts json-lattice) - json-lattice-structure (:lattice_structure json-lattice) - lattice-base-set (map json->concept json-concepts) - lattice-order (map json->lattice-order json-lattice-structure)] - (make-lattice lattice-base-set lattice-order)))) + json-lattice (json/read *in* :key-fn keyword)] + (json->lattice json-lattice)))) ;;; ConExp lattice format From f4b2ef3a2c314130a34e0491fbac22607fbe52a7 Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 7 Feb 2022 14:52:21 +0100 Subject: [PATCH 032/112] add tests for reading / writing of fcas --- .../clojure/conexp/io/{fca.clj => fcas.clj} | 49 ++++-- src/main/clojure/conexp/main.clj | 2 +- src/test/clojure/conexp/io/fcas_test.clj | 151 ++++++++++++++++++ 3 files changed, 188 insertions(+), 14 deletions(-) rename src/main/clojure/conexp/io/{fca.clj => fcas.clj} (52%) create mode 100644 src/test/clojure/conexp/io/fcas_test.clj diff --git a/src/main/clojure/conexp/io/fca.clj b/src/main/clojure/conexp/io/fcas.clj similarity index 52% rename from src/main/clojure/conexp/io/fca.clj rename to src/main/clojure/conexp/io/fcas.clj index f7d18cd49..b8bd84383 100644 --- a/src/main/clojure/conexp/io/fca.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -6,7 +6,7 @@ ;; the terms of this license. ;; You must not remove this notice, or any other, from this software. -(ns conexp.io.fca +(ns conexp.io.fcas (:require [conexp.io.contexts :refer :all] [conexp.io.implications :refer :all] [conexp.io.lattices :refer :all] @@ -31,21 +31,44 @@ (json-schema/validate schema json) :success)))) +(defn create-fca-output-map + [fca] + (let [ctx (:context fca) + lattice (:lattice fca) + implication-sets (:implication-sets fca) + fca-map {:context (ctx->json ctx)} + fca-map (if-not (nil? lattice) + (into fca-map {:lattice (lattice->json lattice)}) + fca-map) + fca-map (if-not (nil? implication-sets) + (into fca-map {:implication_sets (mapv implications->json implication-sets)}) + fca-map)] + fca-map)) + +(defn create-fca-input-map + [json-fca] + (let [json-ctx (:context json-fca) + json-lattice (:lattice json-fca) + json-implication-sets (:implication_sets json-fca) + fca-map {:context (json->ctx (:formal_context json-ctx))} + fca-map (if-not (nil? json-lattice) + (into fca-map {:lattice (json->lattice json-lattice)}) + fca-map) + fca-map (if-not (nil? json-implication-sets) + (into fca-map {:implication-sets (map json->implications json-implication-sets)}) + fca-map)] + fca-map)) + (define-fca-output-format :json - [[ctx lattice implications] file] - (with-out-writer file - (print - (json/write-str {:context (ctx->json ctx) - :lattice (lattice->json lattice) - :implication_sets [(implications->json implications)]})))) + [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) - json-ctx (:context json-fca) - json-lattice (:lattice json-fca) - json-implications (:implication_sets json-fca)] - {:context (json->ctx (:formal_context json-ctx)) - :lattice (json->lattice json-lattice) - :implication-sets (map json->implications json-implications)}))) + fca-map (create-fca-input-map json-fca)] + fca-map))) diff --git a/src/main/clojure/conexp/main.clj b/src/main/clojure/conexp/main.clj index 8d99b737f..809e96cc7 100644 --- a/src/main/clojure/conexp/main.clj +++ b/src/main/clojure/conexp/main.clj @@ -31,7 +31,7 @@ conexp.io.lattices conexp.io.layouts conexp.io.many-valued-contexts - conexp.io.fca + conexp.io.fcas conexp.layouts]) (apply use conexp-clj-namespaces) 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..8394fcf6d --- /dev/null +++ b/src/test/clojure/conexp/io/fcas_test.clj @@ -0,0 +1,151 @@ +;; 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) + (: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)] + (try (= fca (out-in fca 'fca fmt)) + (catch UnsupportedOperationException _ true)))) + +(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)] + (try (= fca (out-in fca 'fca fmt)) + (catch UnsupportedOperationException _ true)))) + +(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)] + (try (= fca (out-in fca 'fca fmt)) + (catch UnsupportedOperationException _ true)))) + +(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)] + (try (= fca (out-in fca 'fca fmt)) + (catch UnsupportedOperationException _ true)))) + +(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)] + (try (= fca (out-in fca 'fca 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- 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)] + (try (= fca (out-in fca 'fca fmt)) + (catch UnsupportedOperationException _ true)))) + +(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)] + (try (= fca (out-in fca 'fca fmt)) + (catch UnsupportedOperationException _ true)))) + +(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)] + (try (= fca (out-in fca 'fca fmt)) + (catch UnsupportedOperationException _ true)))) + +(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)] + (try (out-in-out-in-test fca 'fca fmt) + (catch UnsupportedOperationException _ true)))) From 3ab4341e3157992639e7a9ab088468c46af6ed17 Mon Sep 17 00:00:00 2001 From: Jana Date: Tue, 8 Feb 2022 10:02:07 +0100 Subject: [PATCH 033/112] add validation for context io --- src/main/clojure/conexp/io/contexts.clj | 16 +++++---- .../schemas/context_schema_v1.0.json | 35 +++++++++++++++++++ .../resources/schemas/fca_schema_v1.0.json | 31 +--------------- 3 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 src/main/resources/schemas/context_schema_v1.0.json diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index d31be942f..8e0390926 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -14,7 +14,8 @@ [conexp.io.latex :refer :all] [clojure.string :refer (split)] [clojure.data.xml :as xml] - [clojure.data.json :as json]) + [clojure.data.json :as json] + [json-schema.core :as json-schema]) (:import [java.io PushbackReader])) @@ -629,21 +630,22 @@ (add-context-input-format :json (fn [rdr] - (= "json ctx" (read-line)))) + (= :success + (let [schema (json-schema/prepare-schema (-> "src/main/resources/schemas/context_schema_v1.0.json" slurp (cheshire.core/parse-string true))) + json (json/read rdr)] + (json-schema/validate schema json) + :success)))) (define-context-output-format :json [ctx file] (with-out-writer file - (println "json ctx") (print (json/write-str (ctx->json ctx))))) (define-context-input-format :json [file] (with-in-reader file - (let [_ (get-line) - json-ctx (:formal_context (json/read *in* :key-fn keyword))] - (json->ctx json-ctx) - ))) + (let [json-ctx (:formal_context (json/read *in* :key-fn keyword))] + (json->ctx json-ctx)))) ;;; TODO 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..eb1d7deab --- /dev/null +++ b/src/main/resources/schemas/context_schema_v1.0.json @@ -0,0 +1,35 @@ +{ + "$id": "https://example.com/schemas/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.", + "required": [ + "formal_context" + ], + "properties": { + "formal_context": { + "type": "array", + "items": { + "item": { + "type": "object", + "properties": { + "$ref": "#/$defs/object", + "attributes": { + "type": "array", + "description": "List of attributes", + "items": { + "$ref": "#/$defs/attribute" + } + } + } + } + } + }, + "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/fca_schema_v1.0.json b/src/main/resources/schemas/fca_schema_v1.0.json index ae12a9b55..544a97685 100644 --- a/src/main/resources/schemas/fca_schema_v1.0.json +++ b/src/main/resources/schemas/fca_schema_v1.0.json @@ -174,36 +174,7 @@ "description": "In 'source', the source of the context / data can be added." }, "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.", - "required": [ - "formal_context" - ], - "properties": { - "formal_context": { - "type": "array", - "items": { - "item": { - "type": "object", - "properties": { - "$ref": "#/$defs/object", - "attributes": { - "type": "array", - "description": "List of attributes", - "items": { - "$ref": "#/$defs/attribute" - } - } - } - } - } - }, - "additional_information": { - "description": "Information that is additional to the defined properties", - "type": "string" - } - }, - "additionalProperties": false + "$ref": "context_schema_v1.0.json" }, "lattice": { "type": "object", From ed2dad8463a4903fc007403ad49fc98a3172156a Mon Sep 17 00:00:00 2001 From: Jana Date: Tue, 8 Feb 2022 12:08:45 +0100 Subject: [PATCH 034/112] add validation for lattice io --- src/main/clojure/conexp/io/contexts.clj | 7 +- src/main/clojure/conexp/io/fcas.clj | 7 +- src/main/clojure/conexp/io/lattices.clj | 17 +++-- .../schemas/context_schema_v1.0.json | 6 +- .../resources/schemas/fca_schema_v1.0.json | 71 ++----------------- .../schemas/lattice_schema_v1.0.json | 71 +++++++++++++++++++ 6 files changed, 102 insertions(+), 77 deletions(-) create mode 100644 src/main/resources/schemas/lattice_schema_v1.0.json diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 8e0390926..2d36ee2f9 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -631,7 +631,12 @@ (add-context-input-format :json (fn [rdr] (= :success - (let [schema (json-schema/prepare-schema (-> "src/main/resources/schemas/context_schema_v1.0.json" slurp (cheshire.core/parse-string true))) + (let [schema (json-schema/prepare-schema + (-> "src/main/resources/schemas/context_schema_v1.0.json" slurp + (cheshire.core/parse-string true)) + ;; referencing inside of schemas with relative references + {:classpath-aware? true + :default-resolution-scope "classpath://schemas/"}) json (json/read rdr)] (json-schema/validate schema json) :success)))) diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj index b8bd84383..ab3e08f0b 100644 --- a/src/main/clojure/conexp/io/fcas.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -26,7 +26,12 @@ (add-fca-input-format :json (fn [rdr] (= :success - (let [schema (json-schema/prepare-schema (-> "src/main/resources/schemas/fca_schema_v1.0.json" slurp (cheshire.core/parse-string true))) + (let [schema (json-schema/prepare-schema + (-> "src/main/resources/schemas/fca_schema_v1.0.json" slurp + (cheshire.core/parse-string true)) + ;; referencing inside of schemas with relative references + {:classpath-aware? true + :default-resolution-scope "classpath://schemas/"}) json (json/read rdr)] (json-schema/validate schema json) :success)))) diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 9d70e4399..436d9ba5b 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -7,7 +7,8 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.io.lattices - (:require [clojure.data.json :as json]) + (:require [clojure.data.json :as json] + [json-schema.core :as json-schema]) (:use conexp.base conexp.math.algebra conexp.fca.lattices @@ -96,19 +97,25 @@ (add-lattice-input-format :json (fn [rdr] - (= "json lat" (read-line)))) + (= :success + (let [schema (json-schema/prepare-schema + (-> "src/main/resources/schemas/lattice_schema_v1.0.json" slurp + (cheshire.core/parse-string true)) + ;; referencing inside of schemas with relative references + {:classpath-aware? true + :default-resolution-scope "classpath://schemas/"}) + json (json/read rdr)] + :success)))) (define-lattice-output-format :json [lattice file] (with-out-writer file - (println "json lat") (print (json/write-str (lattice->json lattice))))) (define-lattice-input-format :json [file] (with-in-reader file - (let [_ (get-line) - json-lattice (json/read *in* :key-fn keyword)] + (let [json-lattice (json/read *in* :key-fn keyword)] (json->lattice json-lattice)))) ;;; ConExp lattice format diff --git a/src/main/resources/schemas/context_schema_v1.0.json b/src/main/resources/schemas/context_schema_v1.0.json index eb1d7deab..038c60a48 100644 --- a/src/main/resources/schemas/context_schema_v1.0.json +++ b/src/main/resources/schemas/context_schema_v1.0.json @@ -1,5 +1,5 @@ { - "$id": "https://example.com/schemas/context_schema_v1.0", + "$id": "context_schema_v1.0", "$schema": "https://json-schema.org/draft-07/schema", "title": "JSON schema for a formal context", "type": "object", @@ -14,12 +14,12 @@ "item": { "type": "object", "properties": { - "$ref": "#/$defs/object", + "$ref": "fca_schema_v1.0.json#/$defs/object", "attributes": { "type": "array", "description": "List of attributes", "items": { - "$ref": "#/$defs/attribute" + "$ref": "fca_schema_v1.0.json#/$defs/attribute" } } } diff --git a/src/main/resources/schemas/fca_schema_v1.0.json b/src/main/resources/schemas/fca_schema_v1.0.json index 544a97685..a50ddbfb5 100644 --- a/src/main/resources/schemas/fca_schema_v1.0.json +++ b/src/main/resources/schemas/fca_schema_v1.0.json @@ -1,5 +1,5 @@ { - "$id": "https://example.com/schemas/fca_schema_v1.0", + "$id": "fca_schema_v1.0", "$schema": "https://json-schema.org/draft-07/schema", "title": "JSON schema for an FCA process", "type": "object", @@ -104,8 +104,8 @@ } }, "additionalProperties": false, - "$comment": "If 'is_base' is false, then a property named 'base_type' may not exist.", "if": { + "$comment": "If 'is_base' is false, then a property named 'base_type' may not exist.", "properties": { "is_base": { "const": false @@ -119,53 +119,12 @@ } } }, - "$comment": "If 'base_type' exists, then 'is_base' has to exist as well.", "dependentRequired": { + "$comment": "If 'base_type' exists, then 'is_base' has to exist as well.", "base_type": [ "is_base" ] } - }, - "formal_concept": { - "type": "object", - "required": [ - "extent", - "intent" - ], - "properties": { - "extent": { - "type": "array", - "description": "Array of objects", - "items": { - "$ref": "#/$defs/object" - } - }, - "intent": { - "type": "array", - "description": "Array of attributes", - "items": { - "$ref": "#/$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" - } - } } }, "properties": { @@ -177,29 +136,7 @@ "$ref": "context_schema_v1.0.json" }, "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.", - "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 + "$ref": "lattice_schema_v1.0.json" }, "implication_sets": { "type": "array", 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 From ad1c346d33e912453e7fb0e4843a42d9feaba7cf Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 9 Feb 2022 15:46:31 +0100 Subject: [PATCH 035/112] add validation for implication io --- src/main/clojure/conexp/io/implications.clj | 22 ++-- .../resources/schemas/fca_schema_v1.0.json | 101 +---------------- .../schemas/implications_schema_v1.0.json | 104 ++++++++++++++++++ 3 files changed, 120 insertions(+), 107 deletions(-) create mode 100644 src/main/resources/schemas/implications_schema_v1.0.json diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index df8e3306f..d11283e8a 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -7,9 +7,10 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.io.implications - (:require [clojure.data.json :as json] - [conexp.fca.implications :refer :all] - [conexp.io.util :refer :all])) + (:require [conexp.fca.implications :refer :all] + [conexp.io.util :refer :all] + [clojure.data.json :as json] + [json-schema.core :as json-schema])) ;;; Input format dispatch @@ -44,17 +45,24 @@ (add-implication-input-format :json (fn [rdr] - (= "json impl" (read-line)))) + (= :success + (let [schema (json-schema/prepare-schema + (-> "src/main/resources/schemas/implications_schema_v1.0.json" slurp + (cheshire.core/parse-string true)) + ;; referencing inside of schemas with relative references + {:classpath-aware? true + :default-resolution-scope "classpath://schemas/"}) + json (json/read rdr)] + (json-schema/validate schema json) + :success)))) (define-implication-output-format :json [impl file] (with-out-writer file - (println "json impl") (print (json/write-str (implications->json impl))))) (define-implication-input-format :json [file] (with-in-reader file - (let [_ (get-line) - impl (json/read *in* :key-fn keyword)] + (let [impl (json/read *in* :key-fn keyword)] (json->implications impl)))) diff --git a/src/main/resources/schemas/fca_schema_v1.0.json b/src/main/resources/schemas/fca_schema_v1.0.json index a50ddbfb5..1759c3edb 100644 --- a/src/main/resources/schemas/fca_schema_v1.0.json +++ b/src/main/resources/schemas/fca_schema_v1.0.json @@ -26,105 +26,6 @@ } ], "description": "JSON schema for a single attribute." - }, - "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": "#/$defs/attribute" - } - }, - "conclusion": { - "description": "The conclusion of the implication", - "type": "array", - "items": { - "$ref": "#/$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 - }, - "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).", - "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" - ] - }, - "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" - ] - } } }, "properties": { @@ -142,7 +43,7 @@ "type": "array", "description": "Several sets of implications can be added.", "items": { - "$ref": "#/$defs/set_of_implications" + "$ref": "implications_schema_v1.0.json" } }, "additional_information": { 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..86dd1f994 --- /dev/null +++ b/src/main/resources/schemas/implications_schema_v1.0.json @@ -0,0 +1,104 @@ +{ + "$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" + ] + }, + "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" + ] + } +} \ No newline at end of file From 72acc4ab22decebea408c485d7ba243e4e16827c Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 16 Feb 2022 14:15:23 +0100 Subject: [PATCH 036/112] refactoring of fca io --- src/main/clojure/conexp/io/contexts.clj | 8 ++--- src/main/clojure/conexp/io/fcas.clj | 34 ++++++--------------- src/main/clojure/conexp/io/implications.clj | 8 ++--- src/main/clojure/conexp/io/json.clj | 20 ++++++++++++ src/main/clojure/conexp/io/lattices.clj | 10 ++---- 5 files changed, 37 insertions(+), 43 deletions(-) create mode 100644 src/main/clojure/conexp/io/json.clj diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 2d36ee2f9..db6318e09 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -12,6 +12,7 @@ [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.json :as json] @@ -631,12 +632,7 @@ (add-context-input-format :json (fn [rdr] (= :success - (let [schema (json-schema/prepare-schema - (-> "src/main/resources/schemas/context_schema_v1.0.json" slurp - (cheshire.core/parse-string true)) - ;; referencing inside of schemas with relative references - {:classpath-aware? true - :default-resolution-scope "classpath://schemas/"}) + (let [schema (read-schema "src/main/resources/schemas/context_schema_v1.0.json") json (json/read rdr)] (json-schema/validate schema json) :success)))) diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj index ab3e08f0b..2881c8368 100644 --- a/src/main/clojure/conexp/io/fcas.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -9,6 +9,7 @@ (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] @@ -26,12 +27,7 @@ (add-fca-input-format :json (fn [rdr] (= :success - (let [schema (json-schema/prepare-schema - (-> "src/main/resources/schemas/fca_schema_v1.0.json" slurp - (cheshire.core/parse-string true)) - ;; referencing inside of schemas with relative references - {:classpath-aware? true - :default-resolution-scope "classpath://schemas/"}) + (let [schema (read-schema "src/main/resources/schemas/fca_schema_v1.0.json") json (json/read rdr)] (json-schema/validate schema json) :success)))) @@ -40,29 +36,19 @@ [fca] (let [ctx (:context fca) lattice (:lattice fca) - implication-sets (:implication-sets fca) - fca-map {:context (ctx->json ctx)} - fca-map (if-not (nil? lattice) - (into fca-map {:lattice (lattice->json lattice)}) - fca-map) - fca-map (if-not (nil? implication-sets) - (into fca-map {:implication_sets (mapv implications->json implication-sets)}) - fca-map)] - fca-map)) + 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 [json-fca] (let [json-ctx (:context json-fca) json-lattice (:lattice json-fca) - json-implication-sets (:implication_sets json-fca) - fca-map {:context (json->ctx (:formal_context json-ctx))} - fca-map (if-not (nil? json-lattice) - (into fca-map {:lattice (json->lattice json-lattice)}) - fca-map) - fca-map (if-not (nil? json-implication-sets) - (into fca-map {:implication-sets (map json->implications json-implication-sets)}) - fca-map)] - fca-map)) + json-implication-sets (:implication_sets json-fca)] + (cond-> {:context (json->ctx (:formal_context json-ctx))} + (some? json-lattice) (assoc :lattice (json->lattice json-lattice)) + (some? json-implication-sets) (assoc :implication-sets (map json->implications json-implication-sets))))) (define-fca-output-format :json [fca file] diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index d11283e8a..46f6a42a2 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -9,6 +9,7 @@ (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])) @@ -46,12 +47,7 @@ (add-implication-input-format :json (fn [rdr] (= :success - (let [schema (json-schema/prepare-schema - (-> "src/main/resources/schemas/implications_schema_v1.0.json" slurp - (cheshire.core/parse-string true)) - ;; referencing inside of schemas with relative references - {:classpath-aware? true - :default-resolution-scope "classpath://schemas/"}) + (let [schema (read-schema "src/main/resources/schemas/implications_schema_v1.0.json") json (json/read rdr)] (json-schema/validate schema json) :success)))) diff --git a/src/main/clojure/conexp/io/json.clj b/src/main/clojure/conexp/io/json.clj new file mode 100644 index 000000000..1b3634f0c --- /dev/null +++ b/src/main/clojure/conexp/io/json.clj @@ -0,0 +1,20 @@ +;; 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 funtionality to read and process json files" + (:require [json-schema.core :as json-schema])) + +(defn read-schema + [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/"})) diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 436d9ba5b..422d64303 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -12,7 +12,8 @@ (: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 @@ -98,12 +99,7 @@ (add-lattice-input-format :json (fn [rdr] (= :success - (let [schema (json-schema/prepare-schema - (-> "src/main/resources/schemas/lattice_schema_v1.0.json" slurp - (cheshire.core/parse-string true)) - ;; referencing inside of schemas with relative references - {:classpath-aware? true - :default-resolution-scope "classpath://schemas/"}) + (let [schema (read-schema "src/main/resources/schemas/lattice_schema_v1.0.json") json (json/read rdr)] :success)))) From b4c57e41508358cf4780ed50f2e11659e1da7f13 Mon Sep 17 00:00:00 2001 From: Jana Date: Fri, 18 Feb 2022 09:39:37 +0100 Subject: [PATCH 037/112] move context schema validation to read-context and add a general json validation for format detection --- src/main/clojure/conexp/io/contexts.clj | 19 +++++++--- src/main/clojure/conexp/io/json.clj | 17 ++++++++- .../schemas/context_schema_v1.0.json | 37 ++++++++++++------- src/test/clojure/conexp/io/contexts_test.clj | 31 +++++++++++++++- testing-data/digits-context.json | 1 + testing-data/digits-lattice.json | 1 + 6 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 testing-data/digits-context.json create mode 100644 testing-data/digits-lattice.json diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index db6318e09..7fee346d8 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -627,15 +627,19 @@ incidence (apply union (mapv object->incidence json-ctx))] (make-context objects attributes incidence))) +(defn matches-schema? + [json] + (let [schema (read-schema "src/main/resources/schemas/context_schema_v1.0.json")] + (try (json-schema/validate schema json) + true + (catch Exception _ false)))) + ;; Json Format (add-context-input-format :json (fn [rdr] - (= :success - (let [schema (read-schema "src/main/resources/schemas/context_schema_v1.0.json") - json (json/read rdr)] - (json-schema/validate schema json) - :success)))) + (try (json-object? rdr) + (catch Exception _)))) (define-context-output-format :json [ctx file] @@ -645,7 +649,10 @@ (define-context-input-format :json [file] (with-in-reader file - (let [json-ctx (:formal_context (json/read *in* :key-fn keyword))] + (let [file-content (json/read *in* :key-fn keyword) + json-ctx (:formal_context file-content)] + (assert (matches-schema? file-content) + "The input file does not match the schema given at src/main/resources/schemas/context_schema_v1.0.json.") (json->ctx json-ctx)))) ;;; TODO diff --git a/src/main/clojure/conexp/io/json.clj b/src/main/clojure/conexp/io/json.clj index 1b3634f0c..7bba65c74 100644 --- a/src/main/clojure/conexp/io/json.clj +++ b/src/main/clojure/conexp/io/json.clj @@ -7,8 +7,9 @@ ;; You must not remove this notice, or any other, from this software. (ns conexp.io.json - "Provides funtionality to read and process json files" - (:require [json-schema.core :as json-schema])) + "Provides functionality to read and process json files" + (:require [json-schema.core :as json-schema] + [clojure.data.json :as json])) (defn read-schema [file] @@ -18,3 +19,15 @@ ;; 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))))) diff --git a/src/main/resources/schemas/context_schema_v1.0.json b/src/main/resources/schemas/context_schema_v1.0.json index 038c60a48..9608c8f41 100644 --- a/src/main/resources/schemas/context_schema_v1.0.json +++ b/src/main/resources/schemas/context_schema_v1.0.json @@ -4,6 +4,29 @@ "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": [ "formal_context" ], @@ -11,19 +34,7 @@ "formal_context": { "type": "array", "items": { - "item": { - "type": "object", - "properties": { - "$ref": "fca_schema_v1.0.json#/$defs/object", - "attributes": { - "type": "array", - "description": "List of attributes", - "items": { - "$ref": "fca_schema_v1.0.json#/$defs/attribute" - } - } - } - } + "$ref": "#/$defs/context_item" } }, "additional_information": { diff --git a/src/test/clojure/conexp/io/contexts_test.clj b/src/test/clojure/conexp/io/contexts_test.clj index 390c10bce..d7667c03e 100644 --- a/src/test/clojure/conexp/io/contexts_test.clj +++ b/src/test/clojure/conexp/io/contexts_test.clj @@ -146,8 +146,35 @@ (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." + (if-not (.exists (java.io.File. "testing-data/digits-context.json")) + (warn "Could not verify validation of context schema. Testing file not found.") + (let [ctx (read-context "testing-data/digits-context.json" :json)] + (is (= 7 (count (attributes ctx)))) + (is (= 10 (count (objects ctx)))) + (is (= 47 (count (incidence-relation ctx))))))) ;;; diff --git a/testing-data/digits-context.json b/testing-data/digits-context.json new file mode 100644 index 000000000..a090cabfc --- /dev/null +++ b/testing-data/digits-context.json @@ -0,0 +1 @@ +{"formal_context":[{"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"]}]} \ 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 From 82b2407d5eef1b042860bfc3d3010fcb4f3ba810 Mon Sep 17 00:00:00 2001 From: Jana Date: Fri, 18 Feb 2022 11:38:10 +0100 Subject: [PATCH 038/112] move schema validation to the read functions for lattice, implications and fca --- src/main/clojure/conexp/io/contexts.clj | 9 +- src/main/clojure/conexp/io/fcas.clj | 21 +- src/main/clojure/conexp/io/implications.clj | 9 +- src/main/clojure/conexp/io/json.clj | 8 + src/main/clojure/conexp/io/lattices.clj | 8 +- src/test/clojure/conexp/io/fcas_test.clj | 21 + .../clojure/conexp/io/implications_test.clj | 20 + src/test/clojure/conexp/io/lattices_test.clj | 17 + testing-data/digits-fca.json | 14317 ++++++++++++++++ testing-data/digits-implication.json | 1 + 10 files changed, 14403 insertions(+), 28 deletions(-) create mode 100644 testing-data/digits-fca.json create mode 100644 testing-data/digits-implication.json diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 7fee346d8..2941a4ac0 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -627,13 +627,6 @@ incidence (apply union (mapv object->incidence json-ctx))] (make-context objects attributes incidence))) -(defn matches-schema? - [json] - (let [schema (read-schema "src/main/resources/schemas/context_schema_v1.0.json")] - (try (json-schema/validate schema json) - true - (catch Exception _ false)))) - ;; Json Format (add-context-input-format :json @@ -651,7 +644,7 @@ (with-in-reader file (let [file-content (json/read *in* :key-fn keyword) json-ctx (:formal_context file-content)] - (assert (matches-schema? file-content) + (assert (matches-schema? file-content "context_schema_v1.0.json") "The input file does not match the schema given at src/main/resources/schemas/context_schema_v1.0.json.") (json->ctx json-ctx)))) diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj index 2881c8368..90341c803 100644 --- a/src/main/clojure/conexp/io/fcas.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -24,14 +24,6 @@ ;; Json Format -(add-fca-input-format :json - (fn [rdr] - (= :success - (let [schema (read-schema "src/main/resources/schemas/fca_schema_v1.0.json") - json (json/read rdr)] - (json-schema/validate schema json) - :success)))) - (defn create-fca-output-map [fca] (let [ctx (:context fca) @@ -50,6 +42,12 @@ (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)] @@ -60,6 +58,7 @@ (define-fca-input-format :json [file] (with-in-reader file - (let [json-fca (json/read *in* :key-fn keyword) - fca-map (create-fca-input-map json-fca)] - fca-map))) + (let [json-fca (json/read *in* :key-fn keyword)] + (assert (matches-schema? json-fca "fca_schema_v1.0.json") + "The input file does not match the schema fiven at src/main/resources/schema/fca_schema_v1.0.json.") + (create-fca-input-map json-fca)))) diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index 46f6a42a2..dc48d9ec3 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -46,11 +46,8 @@ (add-implication-input-format :json (fn [rdr] - (= :success - (let [schema (read-schema "src/main/resources/schemas/implications_schema_v1.0.json") - json (json/read rdr)] - (json-schema/validate schema json) - :success)))) + (try (json-object? rdr) + (catch Exception _)))) (define-implication-output-format :json [impl file] @@ -61,4 +58,6 @@ [file] (with-in-reader file (let [impl (json/read *in* :key-fn keyword)] + (assert (matches-schema? impl "implications_schema_v1.0.json") + "The input file does not match the schema given at src/main/resources/schemas/implications_schema_v1.0.json.") (json->implications impl)))) diff --git a/src/main/clojure/conexp/io/json.clj b/src/main/clojure/conexp/io/json.clj index 7bba65c74..f69c2ff46 100644 --- a/src/main/clojure/conexp/io/json.clj +++ b/src/main/clojure/conexp/io/json.clj @@ -31,3 +31,11 @@ [rdr] (let [content (slurp rdr)] (and (json-format? content) (= \{ (first content))))) + +(defn matches-schema? + "Json schema validation" + [json schema-file] + (let [schema (read-schema (str "src/main/resources/schemas/" schema-file))] + (try (json-schema/validate schema json) + true + (catch Exception _ false)))) diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 422d64303..68fa85d78 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -98,10 +98,8 @@ (add-lattice-input-format :json (fn [rdr] - (= :success - (let [schema (read-schema "src/main/resources/schemas/lattice_schema_v1.0.json") - json (json/read rdr)] - :success)))) + (try (json-object? rdr) + (catch Exception _)))) (define-lattice-output-format :json [lattice file] @@ -112,6 +110,8 @@ [file] (with-in-reader file (let [json-lattice (json/read *in* :key-fn keyword)] + (assert (matches-schema? json-lattice "lattice_schema_v1.0.json") + "The input file does not match the schema given at src/main/resources/schemas/lattice_schema_v1.0.json.") (json->lattice json-lattice)))) ;;; ConExp lattice format diff --git a/src/test/clojure/conexp/io/fcas_test.clj b/src/test/clojure/conexp/io/fcas_test.clj index 8394fcf6d..f382d737f 100644 --- a/src/test/clojure/conexp/io/fcas_test.clj +++ b/src/test/clojure/conexp/io/fcas_test.clj @@ -149,3 +149,24 @@ fmt (list-fca-formats)] (try (out-in-out-in-test fca 'fca 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 fca schema. Testing file not found.") + (is (thrown? + AssertionError + (read-fca "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-fca.json")) + (warn "Could not verify validation of fca schema. Testing file not found.") + (let [fca (read-fca "testing-data/digits-fca.json" :json)] + (is (= 6 (count (first (:implication-sets fca)))))))) + +;;; + +nil diff --git a/src/test/clojure/conexp/io/implications_test.clj b/src/test/clojure/conexp/io/implications_test.clj index 1af53797e..44e3c8380 100644 --- a/src/test/clojure/conexp/io/implications_test.clj +++ b/src/test/clojure/conexp/io/implications_test.clj @@ -46,3 +46,23 @@ 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)))))) + +;;; + +nil diff --git a/src/test/clojure/conexp/io/lattices_test.clj b/src/test/clojure/conexp/io/lattices_test.clj index ed178652f..90d85611b 100644 --- a/src/test/clojure/conexp/io/lattices_test.clj +++ b/src/test/clojure/conexp/io/lattices_test.clj @@ -31,4 +31,21 @@ ;;; +(deftest test-json-matching-schema + "Read a json format that matches the given schema." + (if-not (.exists (java.io.File. "testing-data/digits-lattice.json")) + (warn "Could not verify validation of lattice schema. Testing file not found.") + (let [lattice (read-lattice "testing-data/digits-lattice.json" :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))))) + +;;; + nil diff --git a/testing-data/digits-fca.json b/testing-data/digits-fca.json new file mode 100644 index 000000000..96653f66d --- /dev/null +++ b/testing-data/digits-fca.json @@ -0,0 +1,14317 @@ +{ + "context": { + "formal_context": [ + { + "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 From 0339df1f9cf128ba528aea71eb83e7665d38cf51 Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 23 Feb 2022 15:44:25 +0100 Subject: [PATCH 039/112] Add documentation --- src/main/clojure/conexp/io/contexts.clj | 23 +++++---- src/main/clojure/conexp/io/fcas.clj | 17 +++++-- src/main/clojure/conexp/io/implications.clj | 21 +++++--- src/main/clojure/conexp/io/json.clj | 7 +-- src/main/clojure/conexp/io/lattices.clj | 54 ++++++++++++--------- 5 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 2941a4ac0..9e27b01de 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -606,25 +606,29 @@ ;; Json helpers -(defn- object->incidence - [object] - (set-of [o a] [o [(:object object)], a (:attributes object)])) - -(defn object->json +(defn- object->json + "Returns an objects with its attributes in json format." [ctx object] {:object object :attributes (filter #(incident? ctx object %) (attributes ctx))}) (defn ctx->json + "Returns a formal context in json format." [ctx] {:formal_context (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 [objects (map :object json-ctx) attributes (distinct (flatten (map :attributes json-ctx))) - incidence (apply union (mapv object->incidence json-ctx))] + incidence (apply union (mapv json-ctx->incidence json-ctx))] (make-context objects attributes incidence))) ;; Json Format @@ -643,9 +647,10 @@ [file] (with-in-reader file (let [file-content (json/read *in* :key-fn keyword) - json-ctx (:formal_context file-content)] - (assert (matches-schema? file-content "context_schema_v1.0.json") - "The input file does not match the schema given at src/main/resources/schemas/context_schema_v1.0.json.") + json-ctx (:formal_context file-content) + schema-file "src/main/resources/schemas/context_schema_v1.0.json"] + (assert (matches-schema? file-content schema-file) + (str "The input file does not match the schema given at " schema-file ".")) (json->ctx json-ctx)))) ;;; TODO diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj index 90341c803..badae9470 100644 --- a/src/main/clojure/conexp/io/fcas.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -24,7 +24,10 @@ ;; Json Format -(defn create-fca-output-map +(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) @@ -33,7 +36,10 @@ (some? lattice) (assoc :lattice (lattice->json lattice)) (some? implication-sets) (assoc :implication_sets (mapv implications->json implication-sets))))) -(defn create-fca-input-map +(defn- create-fca-input-map + "Returns a map containing the elements of the fca from the json fca. + + The map contains :lattice and :implication-sets only if they are included in the json fca." [json-fca] (let [json-ctx (:context json-fca) json-lattice (:lattice json-fca) @@ -58,7 +64,8 @@ (define-fca-input-format :json [file] (with-in-reader file - (let [json-fca (json/read *in* :key-fn keyword)] - (assert (matches-schema? json-fca "fca_schema_v1.0.json") - "The input file does not match the schema fiven at src/main/resources/schema/fca_schema_v1.0.json.") + (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 index dc48d9ec3..8a3ee4f2c 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -23,21 +23,25 @@ ;; Json helpers (defn- implication->json - [impl] - {:premise (premise impl) - :conclusion (conclusion impl)}) + "Returns one implication in json format." + [implication] + {:premise (premise implication) + :conclusion (conclusion implication)}) (defn implications->json - [impl] + "Returns a vector of implications in json format." + [implication-list] {:implications - (mapv implication->json impl)}) + (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))) @@ -57,7 +61,8 @@ (define-implication-input-format :json [file] (with-in-reader file - (let [impl (json/read *in* :key-fn keyword)] - (assert (matches-schema? impl "implications_schema_v1.0.json") - "The input file does not match the schema given at src/main/resources/schemas/implications_schema_v1.0.json.") + (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/json.clj b/src/main/clojure/conexp/io/json.clj index f69c2ff46..fae1f57a3 100644 --- a/src/main/clojure/conexp/io/json.clj +++ b/src/main/clojure/conexp/io/json.clj @@ -11,7 +11,8 @@ (:require [json-schema.core :as json-schema] [clojure.data.json :as json])) -(defn read-schema +(defn- read-schema + "Returns the file content as Json object." [file] (json-schema/prepare-schema (-> file slurp @@ -33,9 +34,9 @@ (and (json-format? content) (= \{ (first content))))) (defn matches-schema? - "Json schema validation" + "Tests whether the Json object matches the given schema." [json schema-file] - (let [schema (read-schema (str "src/main/resources/schemas/" 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/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 68fa85d78..cb4f7f5e7 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -53,45 +53,52 @@ ;; Json helpers -(defn- json->concept - [json-concept] - [(into #{} (:extent json-concept)) - (into #{} (:intent json-concept))]) - (defn- concept->json + "Returns the concept in json format." [concept] {:extent (first concept) :intent (second concept)}) -(defn- link->json - [link] - {:start_concept (concept->json (first link)) - :end_concept (concept->json (second link))}) +(defn- concept-pair->json + "Returns the pair of concepts in json format." + [pair] + {:start_concept (concept->json (first pair)) + :end_concept (concept->json (second pair))}) -(defn- lattice-structure->json +(defn- lattice-order->json + "Returns the order of the concept lattice in json format." [lattice] - (let [links (set-of [x y] + (let [pairs (set-of [x y] [x (base-set lattice) y (base-set lattice) :when ((order lattice) [x y])])] - (map link->json links))) + (map concept-pair->json pairs))) (defn lattice->json + "Returns a concept lattice, consisting of the base set and order, in json format." [lat] {:formal_concepts (mapv concept->json (base-set lat)) - :lattice_structure (lattice-structure->json lat)}) + :lattice_structure (lattice-order->json lat)}) + +(defn- json->concept + "Returns a vector containing one map each for extent and intent." + [json-concept] + [(into #{} (:extent json-concept)) + (into #{} (:intent json-concept))]) -(defn- json->lattice-order - [json-lattice-order] - [(into [] (json->concept (:start_concept json-lattice-order))) - (into [] (json->concept (:end_concept json-lattice-order)))]) +(defn- json->concept-pair + "Returns a vector containing the concept pair." + [json-concept-pair] + [(into [] (json->concept (:start_concept json-concept-pair))) + (into [] (json->concept (:end_concept json-concept-pair)))]) (defn json->lattice + "Returns a Lattice object for the given json lattice." [json-lattice] (let [json-concepts (:formal_concepts json-lattice) - json-lattice-structure (:lattice_structure json-lattice) - lattice-base-set (map json->concept json-concepts) - lattice-order (map json->lattice-order json-lattice-structure)] + json-lattice-order (:lattice_structure json-lattice) + lattice-base-set (map json->concept json-concepts) + lattice-order (map json->concept-pair json-lattice-order)] (make-lattice lattice-base-set lattice-order))) ;; Json Format @@ -109,9 +116,10 @@ (define-lattice-input-format :json [file] (with-in-reader file - (let [json-lattice (json/read *in* :key-fn keyword)] - (assert (matches-schema? json-lattice "lattice_schema_v1.0.json") - "The input file does not match the schema given at src/main/resources/schemas/lattice_schema_v1.0.json.") + (let [json-lattice (json/read *in* :key-fn keyword) + schema-file "src/main/resources/schemas/lattice_schema_v1.0.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 From 6ff5b7b09bed5b71bfda7dd569d280310a56c9fa Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 23 Feb 2022 17:54:16 +0100 Subject: [PATCH 040/112] add documentation --- ...n-FCA-File-Formats-for-Formal-Contexts.org | 22 ++++++++++++ doc/IO.org | 35 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org index b6451580e..d67f96f47 100644 --- a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org +++ b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org @@ -197,3 +197,25 @@ Conexp-clj I/O 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/IO.org b/doc/IO.org index fe0fa3121..c853c12a0 100644 --- a/doc/IO.org +++ b/doc/IO.org @@ -31,6 +31,7 @@ formal contexts on success. Supported formats for input are - 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. @@ -77,3 +78,37 @@ 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. + + +** FCA containing context, lattice and implication sets + +An FCA 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. + +An FCA can be read using + +#+begin_src clojure +(read-fca "testing-data/digits-fca.json") +#+end_src + +or + +#+begin_src clojure +(read-fca "testing-data/digits-fca.json" :json) +#+end_src + +The reading function returns a map containing the keys ~:context~, ~:lattice~ +(optional) and ~implication-sets~ (optional). + +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. +It is possible to save more than one implication set in the FCA. From aa304b0df98c2a15599dfb00f2a712be958039fe Mon Sep 17 00:00:00 2001 From: Jana Date: Thu, 24 Feb 2022 11:23:01 +0100 Subject: [PATCH 041/112] add myself as author --- AUTHORS.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 986815f56..84edbb8ae 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -11,6 +11,7 @@ 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) * Gleb Kanterov (interval-scale) @@ -18,5 +19,3 @@ Additional Contributors are * Maximilian Stubbemann (concept-robustness) * Anselm von Wangenheim (DimDraw) * Johannes Wollbold (bug reports, feature requests) - - From ef5c6527369a22d54578404e26147f907838d956 Mon Sep 17 00:00:00 2001 From: Jana Date: Fri, 25 Feb 2022 11:45:43 +0100 Subject: [PATCH 042/112] add tests for identifying context input formats --- src/main/clojure/conexp/io/contexts.clj | 6 ++- src/test/clojure/conexp/io/contexts_test.clj | 39 ++++++++++++++++++++ src/test/clojure/conexp/io/util_test.clj | 16 ++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 25b52a634..7aaf52299 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -401,7 +401,11 @@ (add-context-input-format :named-binary-csv (fn [rdr] - (= "NB" (subs (read-line) 0 2)))) + (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] diff --git a/src/test/clojure/conexp/io/contexts_test.clj b/src/test/clojure/conexp/io/contexts_test.clj index 390c10bce..adfbf78a1 100644 --- a/src/test/clojure/conexp/io/contexts_test.clj +++ b/src/test/clojure/conexp/io/contexts_test.clj @@ -161,4 +161,43 @@ ;;; +(def- contexts-oi-fcalgs + [(make-context #{0 1} + #{0 1} + #{[0 1] [1 0] [1 1]})]) + +(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)))) + +;;; + nil diff --git a/src/test/clojure/conexp/io/util_test.clj b/src/test/clojure/conexp/io/util_test.clj index fc76d97b5..2e1c2bfd6 100644 --- a/src/test/clojure/conexp/io/util_test.clj +++ b/src/test/clojure/conexp/io/util_test.clj @@ -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 From 5d1171d07a67732f6d9d4103b3c3021820a7e055 Mon Sep 17 00:00:00 2001 From: Jana Date: Fri, 25 Feb 2022 11:54:41 +0100 Subject: [PATCH 043/112] add test for writing / reading fcalgs format --- src/test/clojure/conexp/io/contexts_test.clj | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/test/clojure/conexp/io/contexts_test.clj b/src/test/clojure/conexp/io/contexts_test.clj index adfbf78a1..cfd54913d 100644 --- a/src/test/clojure/conexp/io/contexts_test.clj +++ b/src/test/clojure/conexp/io/contexts_test.clj @@ -24,12 +24,22 @@ ["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 :anonymous-burmeister} + fmt (remove #{:binary-csv :anonymous-burmeister :fcalgs} (list-context-formats))] (try (= ctx (out-in ctx 'context fmt)) - (catch UnsupportedOperationException _ true)))) + (catch UnsupportedOperationException _ true))) + + ;; fcalgs throws UnsupportedOperationException at writing 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." @@ -161,11 +171,6 @@ ;;; -(def- contexts-oi-fcalgs - [(make-context #{0 1} - #{0 1} - #{[0 1] [1 0] [1 1]})]) - (deftest test-identify-input-format "Test if the automatic identification of the file format works correctly." (with-testing-data [ctx contexts-oi, From 72030255bd4f0dedb84a1def58877d59e5a14acd Mon Sep 17 00:00:00 2001 From: Jana Date: Tue, 1 Mar 2022 15:19:13 +0100 Subject: [PATCH 044/112] add tests for identifying lattice input formats --- src/test/clojure/conexp/io/lattices_test.clj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/clojure/conexp/io/lattices_test.clj b/src/test/clojure/conexp/io/lattices_test.clj index ed178652f..6001a69b7 100644 --- a/src/test/clojure/conexp/io/lattices_test.clj +++ b/src/test/clojure/conexp/io/lattices_test.clj @@ -31,4 +31,12 @@ ;;; +(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 From f3fdc8045d090dd04fbec341ee545cb27775b365 Mon Sep 17 00:00:00 2001 From: Jana Date: Thu, 3 Mar 2022 10:54:53 +0100 Subject: [PATCH 045/112] add documentation + comments --- doc/IO.org | 30 +++++++++++++++------ src/main/clojure/conexp/io/contexts.clj | 15 ++++++++--- src/main/clojure/conexp/io/fcas.clj | 6 ++--- src/main/clojure/conexp/io/implications.clj | 2 +- src/main/clojure/conexp/io/lattices.clj | 2 +- testing-data/small-fca.json | 1 + 6 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 testing-data/small-fca.json diff --git a/doc/IO.org b/doc/IO.org index c853c12a0..a773c5cf6 100644 --- a/doc/IO.org +++ b/doc/IO.org @@ -80,27 +80,38 @@ will become the name of the object. Otherwise, objects will be given arbitrary names. -** FCA containing context, lattice and implication sets +** Store a complete FCA data study: context, lattice and implication sets -An FCA 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 +An 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. An FCA can be read using #+begin_src clojure -(read-fca "testing-data/digits-fca.json") +(read-fca "testing-data/small-fca.json") #+end_src or #+begin_src clojure -(read-fca "testing-data/digits-fca.json" :json) +(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). +(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 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 @@ -110,5 +121,8 @@ canonical base of ~ctx~. Writing all information into one file works with (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. -It is possible to save more than one implication set in the FCA. +While the ~:context~ is always required, ~:lattice~ and ~:implication-sets~ are optional. +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 a vector of implications. Further information on implications can be found +at [[Implications.org][Implications]]. It is possible to save more than one implication set in the FCA. diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 9e27b01de..edb13a1bc 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -607,13 +607,22 @@ ;; Json helpers (defn- object->json - "Returns an objects with its attributes in json format." + "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 in json format." + "Returns a formal context as a map that can easily be converted into json format. + + Example: + {formal_context: { + object: \"b\", + attributes: [\"1\", \"2\"]}}" [ctx] {:formal_context (mapv (partial object->json ctx) (objects ctx))}) @@ -631,7 +640,7 @@ incidence (apply union (mapv json-ctx->incidence json-ctx))] (make-context objects attributes incidence))) -;; Json Format +;; Json Format (src/main/resources/schemas/context_schema_v1.0.json) (add-context-input-format :json (fn [rdr] diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj index badae9470..00e0097cb 100644 --- a/src/main/clojure/conexp/io/fcas.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -22,7 +22,7 @@ ;;; Formats -;; Json Format +;; 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. @@ -37,9 +37,9 @@ (some? implication-sets) (assoc :implication_sets (mapv implications->json implication-sets))))) (defn- create-fca-input-map - "Returns a map containing the elements of the fca from the json fca. + "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. - The map contains :lattice and :implication-sets only if they are included in 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) diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index 8a3ee4f2c..fb47532b3 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -46,7 +46,7 @@ (let [impl (:implications json)] (map json->implication impl))) -;; Json Format +;; Json Format (src/main/resources/schemas/implications_schema_v1.0.json) (add-implication-input-format :json (fn [rdr] diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index cb4f7f5e7..0c73a9c0d 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -101,7 +101,7 @@ lattice-order (map json->concept-pair json-lattice-order)] (make-lattice lattice-base-set lattice-order))) -;; Json Format +;; Json Format (src/main/resources/schemas/lattice_schema_v1.0.json) (add-lattice-input-format :json (fn [rdr] 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 00c5221a3090d1067c0800614fc8d1bfe9ef036a Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 9 Mar 2022 14:38:12 +0100 Subject: [PATCH 046/112] improve documentation --- doc/IO.org | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/IO.org b/doc/IO.org index a773c5cf6..f8bf51541 100644 --- a/doc/IO.org +++ b/doc/IO.org @@ -85,9 +85,9 @@ names. An 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. +of the format. The schema of the format is given at [[../../src/main/resources/schemas/fca_schema_v1.0.json][fca_schema_v1.0.json]]. -An FCA can be read using +An FCA data study can be read using #+begin_src clojure (read-fca "testing-data/small-fca.json") @@ -113,6 +113,12 @@ The reading function returns a map containing the keys ~:context~, ~:lattice~ } #+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 @@ -122,7 +128,3 @@ canonical base of ~ctx~. Writing all information into one file works with #+end_src While the ~:context~ is always required, ~:lattice~ and ~:implication-sets~ are optional. -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 a vector of implications. Further information on implications can be found -at [[Implications.org][Implications]]. It is possible to save more than one implication set in the FCA. From 454dfd14d1d594758801f85c9593e8e18f9e2a9c Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 9 Mar 2022 14:58:53 +0100 Subject: [PATCH 047/112] minor changes in documentation --- doc/IO.org | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/IO.org b/doc/IO.org index f8bf51541..1eb2ce5e8 100644 --- a/doc/IO.org +++ b/doc/IO.org @@ -82,12 +82,12 @@ names. ** Store a complete FCA data study: context, lattice and implication sets -An FCA data format contains the context and can contain the corresponding lattice +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 schema of the format is given at [[../../src/main/resources/schemas/fca_schema_v1.0.json][fca_schema_v1.0.json]]. +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]]. -An FCA data study can be read using +The FCA json format can be read using #+begin_src clojure (read-fca "testing-data/small-fca.json") From 60e5504c1a44c518c1d49665a1651675feb1ac7d Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 4 Apr 2022 17:21:35 +0200 Subject: [PATCH 048/112] improve tests for fcas and implications --- src/test/clojure/conexp/io/fcas_test.clj | 36 +++++++++---------- .../clojure/conexp/io/implications_test.clj | 12 ++++++- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/test/clojure/conexp/io/fcas_test.clj b/src/test/clojure/conexp/io/fcas_test.clj index f382d737f..e4fc3da1a 100644 --- a/src/test/clojure/conexp/io/fcas_test.clj +++ b/src/test/clojure/conexp/io/fcas_test.clj @@ -32,8 +32,7 @@ "Context is the only input" (with-testing-data [fca [fca-ctx-oi], fmt (list-fca-formats)] - (try (= fca (out-in fca 'fca fmt)) - (catch UnsupportedOperationException _ true)))) + (= fca (out-in fca 'fca fmt)))) (def- lattice-oi "Lattice to use for out-in testing" @@ -47,8 +46,7 @@ "Context and lattice as input" (with-testing-data [fca [fca-lat-oi], fmt (list-fca-formats)] - (try (= fca (out-in fca 'fca fmt)) - (catch UnsupportedOperationException _ true)))) + (= fca (out-in fca 'fca fmt)))) (def- implications-oi "Implications to use for out-in testing" @@ -62,8 +60,7 @@ "Context and implications as input" (with-testing-data [fca [fca-impl-oi], fmt (list-fca-formats)] - (try (= fca (out-in fca 'fca fmt)) - (catch UnsupportedOperationException _ true)))) + (= fca (out-in fca 'fca fmt)))) (def- fca-oi "FCA with context, lattice and implications" @@ -73,8 +70,7 @@ "Context, lattice and implications as input" (with-testing-data [fca [fca-oi], fmt (list-fca-formats)] - (try (= fca (out-in fca 'fca fmt)) - (catch UnsupportedOperationException _ true)))) + (= fca (out-in fca 'fca fmt)))) (def- fca-several-implication-sets-oi "FCA with context, lattice and several implication sets" @@ -84,8 +80,7 @@ "Context, lattice and several implication sets as input" (with-testing-data [fca [fca-several-implication-sets-oi], fmt (list-fca-formats)] - (try (= fca (out-in fca 'fca fmt)) - (catch UnsupportedOperationException _ true)))) + (= fca (out-in fca 'fca fmt)))) (def- contexts-oioi "Contexts to use for out-in-out-in testing" @@ -100,8 +95,7 @@ "Several tests with only-context input" (with-testing-data [fca fca-ctx-oioi, fmt (list-fca-formats)] - (try (= fca (out-in fca 'fca fmt)) - (catch UnsupportedOperationException _ true)))) + (out-in-out-in-test fca 'fca fmt))) (def- lattice-oioi "Lattice to use for out-in-out-in testing" @@ -117,8 +111,7 @@ "Several tests with context and lattice input" (with-testing-data [fca fca-lat-oioi, fmt (list-fca-formats)] - (try (= fca (out-in fca 'fca fmt)) - (catch UnsupportedOperationException _ true)))) + (out-in-out-in-test fca 'fca fmt))) (def- implications-oioi "Implications to use for out-in-out-in testing" @@ -134,8 +127,7 @@ "Several tests with context and implication input" (with-testing-data [fca fca-impl-oioi, fmt (list-fca-formats)] - (try (= fca (out-in fca 'fca fmt)) - (catch UnsupportedOperationException _ true)))) + (out-in-out-in-test fca 'fca fmt))) (def- fca-oioi "FCAs for out-in-out-in testing with context, concepts and implications" @@ -147,8 +139,7 @@ "Several tests with complete FCA" (with-testing-data [fca fca-oioi, fmt (list-fca-formats)] - (try (out-in-out-in-test fca 'fca fmt) - (catch UnsupportedOperationException _ true)))) + (out-in-out-in-test fca 'fca fmt))) ;;; @@ -167,6 +158,15 @@ (let [fca (read-fca "testing-data/digits-fca.json" :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 index 44e3c8380..183b9eb6e 100644 --- a/src/test/clojure/conexp/io/implications_test.clj +++ b/src/test/clojure/conexp/io/implications_test.clj @@ -48,7 +48,8 @@ (catch UnsupportedOperationException _ true)))) ;;; - (deftest test-json-not-matching-schema + +(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.") @@ -63,6 +64,15 @@ (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 From b56281373fc3973ad176c052cc3faca33d7a871e Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Tue, 21 Dec 2021 03:02:17 +0100 Subject: [PATCH 049/112] Add nix flake --- .envrc | 1 + flake.lock | 77 +++++++++++++++++++++++++++++++++ flake.nix | 45 +++++++++++++++++++ nix/conexp-clj/default.nix | 49 +++++++++++++++++++++ nix/conexp-clj/dependencies.nix | 37 ++++++++++++++++ shell.nix | 13 ++++++ 6 files changed, 222 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/conexp-clj/default.nix create mode 100644 nix/conexp-clj/dependencies.nix create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..051d09d29 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..be19e9206 --- /dev/null +++ b/flake.lock @@ -0,0 +1,77 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1627913399, + "narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1638122382, + "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "74f7e4319258e287b0f9cb95426c9853b282730b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignoresrc": { + "flake": false, + "locked": { + "lastModified": 1635165013, + "narHash": "sha256-o/BdVjNwcB6jOmzZjOH703BesSkkS5O7ej3xhyO8hAY=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "5b9e0ff9d3b551234b4f3eb3983744fa354b17f1", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1639989170, + "narHash": "sha256-REf0rqdJs6XIPo/zc/FhJMecggjEXi45QyiV207y30Y=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "86453059bf8312f0f5bf1fe8a2f52da2be664489", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-21.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignoresrc": "gitignoresrc", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..f2deb237d --- /dev/null +++ b/flake.nix @@ -0,0 +1,45 @@ +{ + description = "conexp-clj, a general purpose software tool for Formal Concept Analysis"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.11"; + flake-utils.url = "github:numtide/flake-utils"; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + gitignoresrc = { + url = "github:hercules-ci/gitignore.nix"; + flake = false; + }; + }; + + outputs = { self, nixpkgs, flake-utils, ... }@inputs: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + gitignoreSource = (import inputs.gitignoresrc { inherit (pkgs) lib; }).gitignoreSource; + conexp-clj = pkgs.callPackage ./nix/conexp-clj + { + inherit gitignoreSource; + }; + in + rec { + packages = flake-utils.lib.flattenTree { + conexp-clj = conexp-clj; + }; + defaultPackage = packages.conexp-clj; + apps.conexp-clj = flake-utils.lib.mkApp { drv = packages.conexp-clj; }; + defaultApp = apps.conexp-clj; + + devShell = pkgs.mkShell { + buildInputs = with pkgs; [ + clojure-lsp + leiningen + ]; + shellHook = '' + export "PATH=${conexp-clj}/bin:$PATH" + ''; + }; + }); +} diff --git a/nix/conexp-clj/default.nix b/nix/conexp-clj/default.nix new file mode 100644 index 000000000..83e217cb4 --- /dev/null +++ b/nix/conexp-clj/default.nix @@ -0,0 +1,49 @@ +{ pkgs +, lib +, stdenv +, makeWrapper +, strip-nondeterminism +, gitignoreSource +, leiningen +, jdk +}: + +let dependencies = pkgs.callPackage ./dependencies.nix { inherit gitignoreSource leiningen; }; +in +stdenv.mkDerivation rec { + pname = "conexp-clj"; + version = "2.3.0-SNAPSHOT"; + src = gitignoreSource ../..; + + buildInputs = [ makeWrapper ]; + nativeBuildInputs = [ leiningen ]; + + preBuild = '' + mkdir -p $out + mkdir -p $out/maven + cp -R ${dependencies}/.m2 $out/maven + chmod -R +w $out/maven + ''; + + buildPhase = '' + runHook preBuild + + export HOME=$PWD + export LEIN_HOME=$HOME/.lein + mkdir -p $LEIN_HOME + + _JAVA_OPTIONS="-Duser.home=$out/maven" lein -o uberjar + + runHook postBuild + ''; + + postFixup = '' + ${strip-nondeterminism}/bin/strip-nondeterminism $out/${pname}-${version}-standalone.jar + ''; + + installPhase = '' + cp builds/uberjar/${pname}-${version}-standalone.jar $out + makeWrapper ${jdk}/bin/java $out/bin/${pname} --add-flags "-jar $out/${pname}-${version}-standalone.jar" + makeWrapper ${leiningen}/bin/lein $out/bin/lein --add-flags "-o" --set "_JAVA_OPTIONS" "-Duser.home=$out/maven" + ''; +} diff --git a/nix/conexp-clj/dependencies.nix b/nix/conexp-clj/dependencies.nix new file mode 100644 index 000000000..fb576e6c5 --- /dev/null +++ b/nix/conexp-clj/dependencies.nix @@ -0,0 +1,37 @@ +{ pkgs +, lib +, stdenv +, gitignoreSource +, leiningen +}: + +stdenv.mkDerivation { + name = "conexp-clj-dependencies"; + nativeBuildInputs = [ leiningen ]; + src = gitignoreSource ../..; + + buildPhase = '' + runHook preBuild + + export HOME=$PWD + export LEIN_HOME=$HOME/.lein + mkdir -p $LEIN_HOME + + _JAVA_OPTIONS="-Duser.home=$out" lein with-profile uberjar,dev,debug deps + # hack to get repl dependencies into the repo + echo | _JAVA_OPTIONS="-Duser.home=$out" lein repl + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + find $out/.m2 -type f -regex '.+\(\.lastUpdated\|resolver-status\.properties\|_remote\.repositories\|maven-metadata-local\.xml\)' -delete + + runHook postInstall + ''; + + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "sha256-o+DQDDs3Ch+cGuMoFGB6xGFwJKBM/946X6bFzlBiaVo="; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..9eb132a58 --- /dev/null +++ b/shell.nix @@ -0,0 +1,13 @@ +(import + ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { + src = ./.; + }).shellNix From ede2ff8e4ee3bbcc5368cf5a6898c9a632435c26 Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Tue, 21 Dec 2021 03:02:17 +0100 Subject: [PATCH 050/112] Add nix flake --- .envrc | 1 + flake.lock | 77 +++++++++++++++++++++++++++++++++ flake.nix | 45 +++++++++++++++++++ nix/conexp-clj/default.nix | 49 +++++++++++++++++++++ nix/conexp-clj/dependencies.nix | 37 ++++++++++++++++ shell.nix | 13 ++++++ 6 files changed, 222 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/conexp-clj/default.nix create mode 100644 nix/conexp-clj/dependencies.nix create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..051d09d29 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..be19e9206 --- /dev/null +++ b/flake.lock @@ -0,0 +1,77 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1627913399, + "narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1638122382, + "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "74f7e4319258e287b0f9cb95426c9853b282730b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignoresrc": { + "flake": false, + "locked": { + "lastModified": 1635165013, + "narHash": "sha256-o/BdVjNwcB6jOmzZjOH703BesSkkS5O7ej3xhyO8hAY=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "5b9e0ff9d3b551234b4f3eb3983744fa354b17f1", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1639989170, + "narHash": "sha256-REf0rqdJs6XIPo/zc/FhJMecggjEXi45QyiV207y30Y=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "86453059bf8312f0f5bf1fe8a2f52da2be664489", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-21.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignoresrc": "gitignoresrc", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..f2deb237d --- /dev/null +++ b/flake.nix @@ -0,0 +1,45 @@ +{ + description = "conexp-clj, a general purpose software tool for Formal Concept Analysis"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.11"; + flake-utils.url = "github:numtide/flake-utils"; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + gitignoresrc = { + url = "github:hercules-ci/gitignore.nix"; + flake = false; + }; + }; + + outputs = { self, nixpkgs, flake-utils, ... }@inputs: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + gitignoreSource = (import inputs.gitignoresrc { inherit (pkgs) lib; }).gitignoreSource; + conexp-clj = pkgs.callPackage ./nix/conexp-clj + { + inherit gitignoreSource; + }; + in + rec { + packages = flake-utils.lib.flattenTree { + conexp-clj = conexp-clj; + }; + defaultPackage = packages.conexp-clj; + apps.conexp-clj = flake-utils.lib.mkApp { drv = packages.conexp-clj; }; + defaultApp = apps.conexp-clj; + + devShell = pkgs.mkShell { + buildInputs = with pkgs; [ + clojure-lsp + leiningen + ]; + shellHook = '' + export "PATH=${conexp-clj}/bin:$PATH" + ''; + }; + }); +} diff --git a/nix/conexp-clj/default.nix b/nix/conexp-clj/default.nix new file mode 100644 index 000000000..83e217cb4 --- /dev/null +++ b/nix/conexp-clj/default.nix @@ -0,0 +1,49 @@ +{ pkgs +, lib +, stdenv +, makeWrapper +, strip-nondeterminism +, gitignoreSource +, leiningen +, jdk +}: + +let dependencies = pkgs.callPackage ./dependencies.nix { inherit gitignoreSource leiningen; }; +in +stdenv.mkDerivation rec { + pname = "conexp-clj"; + version = "2.3.0-SNAPSHOT"; + src = gitignoreSource ../..; + + buildInputs = [ makeWrapper ]; + nativeBuildInputs = [ leiningen ]; + + preBuild = '' + mkdir -p $out + mkdir -p $out/maven + cp -R ${dependencies}/.m2 $out/maven + chmod -R +w $out/maven + ''; + + buildPhase = '' + runHook preBuild + + export HOME=$PWD + export LEIN_HOME=$HOME/.lein + mkdir -p $LEIN_HOME + + _JAVA_OPTIONS="-Duser.home=$out/maven" lein -o uberjar + + runHook postBuild + ''; + + postFixup = '' + ${strip-nondeterminism}/bin/strip-nondeterminism $out/${pname}-${version}-standalone.jar + ''; + + installPhase = '' + cp builds/uberjar/${pname}-${version}-standalone.jar $out + makeWrapper ${jdk}/bin/java $out/bin/${pname} --add-flags "-jar $out/${pname}-${version}-standalone.jar" + makeWrapper ${leiningen}/bin/lein $out/bin/lein --add-flags "-o" --set "_JAVA_OPTIONS" "-Duser.home=$out/maven" + ''; +} diff --git a/nix/conexp-clj/dependencies.nix b/nix/conexp-clj/dependencies.nix new file mode 100644 index 000000000..fb576e6c5 --- /dev/null +++ b/nix/conexp-clj/dependencies.nix @@ -0,0 +1,37 @@ +{ pkgs +, lib +, stdenv +, gitignoreSource +, leiningen +}: + +stdenv.mkDerivation { + name = "conexp-clj-dependencies"; + nativeBuildInputs = [ leiningen ]; + src = gitignoreSource ../..; + + buildPhase = '' + runHook preBuild + + export HOME=$PWD + export LEIN_HOME=$HOME/.lein + mkdir -p $LEIN_HOME + + _JAVA_OPTIONS="-Duser.home=$out" lein with-profile uberjar,dev,debug deps + # hack to get repl dependencies into the repo + echo | _JAVA_OPTIONS="-Duser.home=$out" lein repl + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + find $out/.m2 -type f -regex '.+\(\.lastUpdated\|resolver-status\.properties\|_remote\.repositories\|maven-metadata-local\.xml\)' -delete + + runHook postInstall + ''; + + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "sha256-o+DQDDs3Ch+cGuMoFGB6xGFwJKBM/946X6bFzlBiaVo="; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..9eb132a58 --- /dev/null +++ b/shell.nix @@ -0,0 +1,13 @@ +(import + ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { + src = ./.; + }).shellNix From 9c45390f01ed6ae1a640945f7e6a7dc567734974 Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Fri, 11 Mar 2022 09:42:15 +0100 Subject: [PATCH 051/112] Updating the scene layout did not update the layout data --- .../clojure/conexp/gui/draw/scene_layouts.clj | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/main/clojure/conexp/gui/draw/scene_layouts.clj b/src/main/clojure/conexp/gui/draw/scene_layouts.clj index 8e2b42212..d245adb50 100644 --- a/src/main/clojure/conexp/gui/draw/scene_layouts.clj +++ b/src/main/clojure/conexp/gui/draw/scene_layouts.clj @@ -54,38 +54,41 @@ ([^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 (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." From 0a76d33d759b38a0bd0285532cf0d41aee59172e Mon Sep 17 00:00:00 2001 From: Johannes Hirth Date: Wed, 16 Mar 2022 09:25:14 +0100 Subject: [PATCH 052/112] updating the layout cased a removal of the old value-fn --- .../clojure/conexp/gui/draw/scene_layouts.clj | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/clojure/conexp/gui/draw/scene_layouts.clj b/src/main/clojure/conexp/gui/draw/scene_layouts.clj index d245adb50..b679c45b3 100644 --- a/src/main/clojure/conexp/gui/draw/scene_layouts.clj +++ b/src/main/clojure/conexp/gui/draw/scene_layouts.clj @@ -76,11 +76,23 @@ "Updates layout according to new layout. The underlying lattice must not be changed." [^GScene scene, layout] - (do (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)))))) + + (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 From 8ded7cebc97e5764851c9120f0ad77b5dd2cba2c Mon Sep 17 00:00:00 2001 From: Jana Date: Fri, 18 Mar 2022 12:29:32 +0100 Subject: [PATCH 053/112] update documentation for drawing concept lattices --- doc/Concept-Lattices.org | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/Concept-Lattices.org b/doc/Concept-Lattices.org index 8ab9aed9a..370a52800 100644 --- a/doc/Concept-Lattices.org +++ b/doc/Concept-Lattices.org @@ -151,7 +151,7 @@ The layout used by ~draw-lattice~ is defined in ~standard-layout~. Other layouts can be provided to ~draw-lattice~ as an additional argument #+begin_src clojure :results silent -(draw-concept-lattice (concept-lattice (adiag-context 2)) +(draw-lattice (concept-lattice (adiag-context 2)) :layout-fn inf-additive-layout) #+end_src @@ -160,11 +160,14 @@ provided by the ~:value-fn~ argument. The following example annotates the extent size. #+begin_src clojure :results silent -(draw-concept-lattice (concept-lattice (adiag-context 2)) +(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]] From 3cb86b4906864de7254e1f93e1bb263d165373d9 Mon Sep 17 00:00:00 2001 From: maximilian-felde <43608455+maximilian-felde@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:19:32 +0200 Subject: [PATCH 054/112] Triadic (#70) * triadic exploration * Triadic-Exploration: Added basic example to doc * Triadic Exploration: small fix in doc --- README.md | 1 + doc/Triadic-Exploration.org | 151 +++++++ .../concept-lattice-conditional-theories.png | Bin 0 -> 29629 bytes project.clj | 1 + .../conexp/fca/triadic_exploration.clj | 379 ++++++++++++++++++ 5 files changed, 532 insertions(+) create mode 100644 doc/Triadic-Exploration.org create mode 100644 doc/images/concept-lattice-conditional-theories.png create mode 100644 src/main/clojure/conexp/fca/triadic_exploration.clj diff --git a/README.md b/README.md index 6f9442c0c..4d1b014fb 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ 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) 6. [API documentation](doc/API.md) 7. [Development](doc/Development.org) 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/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/project.clj b/project.clj index 34b9ebcee..7108e40ba 100644 --- a/project.clj +++ b/project.clj @@ -23,6 +23,7 @@ [org.clojure/math.numeric-tower "0.0.4"] [org.clojure/tools.cli "1.0.194"] [org.apache.commons/commons-math "2.2"] + [org.clojure/algo.generic "0.1.3"] [seesaw "1.5.0"] [reply "0.4.4" :exclusions [org.clojure/clojure 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..813d421fd --- /dev/null +++ b/src/main/clojure/conexp/fca/triadic_exploration.clj @@ -0,0 +1,379 @@ +(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 + "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)" + [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 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 ] [])}) From 2c7b58902c640f4232db7c1ca1acb2bee269a37e Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Wed, 29 Jun 2022 11:57:28 +0200 Subject: [PATCH 055/112] modify :json context schema so that empty attribute columns are not dropped any more (#92) --- src/main/clojure/conexp/io/contexts.clj | 24 ++++++++++++------- src/main/clojure/conexp/io/fcas.clj | 2 +- .../schemas/context_schema_v1.0.json | 12 ++++++++-- src/test/clojure/conexp/io/contexts_test.clj | 9 ++++++- testing-data/digits-context.json | 2 +- testing-data/digits-fca.json | 3 ++- 6 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index ceb23094c..bab7f64e0 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -329,7 +329,10 @@ (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 "{\"attributes\":[],\"adjacency-list\":[]}"))) (catch Exception _)))) (define-context-input-format :csv @@ -624,11 +627,13 @@ "Returns a formal context as a map that can easily be converted into json format. Example: - {formal_context: { - object: \"b\", - attributes: [\"1\", \"2\"]}}" + {:attributes (\"1\", \"2\") + :adjacency-list + [{:object \"b\", + :attributes (\"1\", \"2\")}]}" [ctx] - {:formal_context + {:attributes (into () (attributes ctx)) + :adjacency-list (mapv (partial object->json ctx) (objects ctx))}) (defn- json-ctx->incidence @@ -639,9 +644,10 @@ (defn json->ctx "Returns a Context object for the given json context." [json-ctx] - (let [objects (map :object json-ctx) - attributes (distinct (flatten (map :attributes json-ctx))) - incidence (apply union (mapv json-ctx->incidence json-ctx))] + (let [attributes (:attributes json-ctx) + ctx-list (:adjacency-list json-ctx) + objects (map :object ctx-list) + incidence (apply union (mapv json-ctx->incidence ctx-list))] (make-context objects attributes incidence))) ;; Json Format (src/main/resources/schemas/context_schema_v1.0.json) @@ -660,7 +666,7 @@ [file] (with-in-reader file (let [file-content (json/read *in* :key-fn keyword) - json-ctx (:formal_context file-content) + json-ctx file-content schema-file "src/main/resources/schemas/context_schema_v1.0.json"] (assert (matches-schema? file-content schema-file) (str "The input file does not match the schema given at " schema-file ".")) diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj index 00e0097cb..024748c73 100644 --- a/src/main/clojure/conexp/io/fcas.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -44,7 +44,7 @@ (let [json-ctx (:context json-fca) json-lattice (:lattice json-fca) json-implication-sets (:implication_sets json-fca)] - (cond-> {:context (json->ctx (:formal_context json-ctx))} + (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))))) diff --git a/src/main/resources/schemas/context_schema_v1.0.json b/src/main/resources/schemas/context_schema_v1.0.json index 9608c8f41..6c3348e2e 100644 --- a/src/main/resources/schemas/context_schema_v1.0.json +++ b/src/main/resources/schemas/context_schema_v1.0.json @@ -28,10 +28,18 @@ } }, "required": [ - "formal_context" + "attributes", + "adjacency-list" ], "properties": { - "formal_context": { + "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" diff --git a/src/test/clojure/conexp/io/contexts_test.clj b/src/test/clojure/conexp/io/contexts_test.clj index 277044330..cfcbd165f 100644 --- a/src/test/clojure/conexp/io/contexts_test.clj +++ b/src/test/clojure/conexp/io/contexts_test.clj @@ -230,6 +230,13 @@ 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) + :adjacency-list + [{:object 1, :attributes '(:a)} + {:object 2, :attributes '()}]})))) nil diff --git a/testing-data/digits-context.json b/testing-data/digits-context.json index a090cabfc..5406a5419 100644 --- a/testing-data/digits-context.json +++ b/testing-data/digits-context.json @@ -1 +1 @@ -{"formal_context":[{"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"]}]} \ No newline at end of file +{"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-fca.json b/testing-data/digits-fca.json index 96653f66d..f55dfa826 100644 --- a/testing-data/digits-fca.json +++ b/testing-data/digits-fca.json @@ -1,6 +1,7 @@ { "context": { - "formal_context": [ + "attributes": ["a","b","c","d","e","f","g"], + "adjacency-list": [ { "object": "9", "attributes": [ From bbe571d3f7e2636fe891b1d0d12156a9b04e073d Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:03:33 +0200 Subject: [PATCH 056/112] 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 --- project.clj | 3 +- src/main/clojure/conexp/io/contexts.clj | 116 +++++++++++------------- 2 files changed, 57 insertions(+), 62 deletions(-) diff --git a/project.clj b/project.clj index 7108e40ba..fd0658369 100644 --- a/project.clj +++ b/project.clj @@ -39,7 +39,8 @@ [ring/ring-json "0.5.0"] [http-kit "2.5.0"] [org.apache.commons/commons-math3 "3.6.1"] - [luposlip/json-schema "0.3.3"]] + [luposlip/json-schema "0.3.3"] + [org.clojure/data.csv "1.0.1"]] :profiles {:uberjar {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"]] diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index bab7f64e0..9fd846831 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -16,7 +16,9 @@ [clojure.string :refer (split)] [clojure.data.xml :as xml] [clojure.data.json :as json] - [json-schema.core :as json-schema]) + [json-schema.core :as json-schema] + [clojure.data.csv :as csv] + [clojure.java.io :as io]) (:import [java.io PushbackReader])) @@ -337,15 +339,13 @@ (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] @@ -353,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) @@ -366,20 +366,15 @@ (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] @@ -391,19 +386,14 @@ (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] @@ -415,20 +405,21 @@ (define-context-input-format :named-binary-csv [file] - (with-in-reader file - "named binary CSV" - (let [[_ & atts] (split (read-line) #",") - atts-idx (reduce #(assoc %1 %2 (.indexOf atts %2)) {} atts) - [o & second-line] (split (read-line) #",")] - (loop [objs #{o}, - incidence (set-of [o a] | a atts, :when (= (nth second-line (get atts-idx a)) "1"))] - (if-let [line (read-line)] - (let [[o & line] (split line #",")] - (recur (conj objs o) - (into incidence - (for [a atts :when (= (nth line (get atts-idx a)) "1")] - [o a])))) - (make-context objs atts incidence)))))) + (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] @@ -440,13 +431,16 @@ (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 (clojure.string/join "," (into ["NB"] atts))) - (doseq [g objs] - (println (clojure.string/join "," - (into [g] - (map #(if (incident? ctx g %) 1 0) atts)))))))) + 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 From 571389fd03d4e3fa64a3fe557f5d2dbbda1922d2 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Wed, 20 Jul 2022 19:43:21 +0200 Subject: [PATCH 057/112] Minor stuff in exploration --- src/main/clojure/conexp/fca/triadic_exploration.clj | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/clojure/conexp/fca/triadic_exploration.clj b/src/main/clojure/conexp/fca/triadic_exploration.clj index 813d421fd..4ec9124ca 100644 --- a/src/main/clojure/conexp/fca/triadic_exploration.clj +++ b/src/main/clojure/conexp/fca/triadic_exploration.clj @@ -7,6 +7,7 @@ )) + (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] @@ -55,8 +56,8 @@ (throw (Throwable. "context-family is not triadic")))) -(defn- context-union - "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)" +(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))) @@ -260,7 +261,7 @@ (recur (first other) (rest other) ED - (context-union C CD)))))))) + (context-union-nf C CD)))))))) (defn empty-triadic-context-from-triadic-context @@ -343,8 +344,8 @@ :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 ^{: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 From 2f4ee3d4fd8374592ad812786992cac0dce3fa2e Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Wed, 20 Jul 2022 20:28:25 +0200 Subject: [PATCH 058/112] Fast fix for CORS problem using api with browser frontend --- project.clj | 1 + src/main/clojure/conexp/api.clj | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/project.clj b/project.clj index fd0658369..4b69fc441 100644 --- a/project.clj +++ b/project.clj @@ -37,6 +37,7 @@ [ring/ring-devel "1.8.2"] [ring/ring-core "1.8.2"] [ring/ring-json "0.5.0"] + [ring-cors "0.1.13"] [http-kit "2.5.0"] [org.apache.commons/commons-math3 "3.6.1"] [luposlip/json-schema "0.3.3"] diff --git a/src/main/clojure/conexp/api.clj b/src/main/clojure/conexp/api.clj index 0030457af..ad07e8928 100644 --- a/src/main/clojure/conexp/api.clj +++ b/src/main/clojure/conexp/api.clj @@ -11,6 +11,7 @@ (:require [conexp.api.handler :refer [handler]] [ring.middleware.reload :refer [wrap-reload]] [ring.middleware.json :refer [wrap-json-body wrap-json-response]] + [ring.middleware.cors :refer [wrap-cors]] [org.httpkit.server :refer [run-server]])) ;;; @@ -20,12 +21,18 @@ [dev] (if dev (-> #'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)) From d1a5c294fb90e9afc033bdf90cc483b74eade70f Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Thu, 22 Sep 2022 11:59:09 +0300 Subject: [PATCH 059/112] 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 --- src/main/clojure/conexp/api/handler.clj | 17 +-- src/main/clojure/conexp/io/layouts.clj | 118 +++++++++++++++++- .../resources/schemas/layout_schema_v1.0.json | 74 +++++++++++ src/test/clojure/conexp/api/handler_test.clj | 87 ++++++------- src/test/clojure/conexp/io/layouts_test.clj | 29 ++++- 5 files changed, 256 insertions(+), 69 deletions(-) create mode 100644 src/main/resources/schemas/layout_schema_v1.0.json diff --git a/src/main/clojure/conexp/api/handler.clj b/src/main/clojure/conexp/api/handler.clj index 71606ba97..7d0caeb2f 100644 --- a/src/main/clojure/conexp/api/handler.clj +++ b/src/main/clojure/conexp/api/handler.clj @@ -47,15 +47,8 @@ (: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)})))) + "layout" (json->layout raw) + "method" (resolve (symbol raw)) raw))) (defn write-data @@ -79,11 +72,7 @@ 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)} + Layout (layout->json data) data))) ;;; Process functions 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/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/test/clojure/conexp/api/handler_test.clj b/src/test/clojure/conexp/api/handler_test.clj index be14e74b8..79fec2610 100644 --- a/src/test/clojure/conexp/api/handler_test.clj +++ b/src/test/clojure/conexp/api/handler_test.clj @@ -313,16 +313,37 @@ :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,12 @@ :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]) - 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")))) + (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))))) ;;; diff --git a/src/test/clojure/conexp/io/layouts_test.clj b/src/test/clojure/conexp/io/layouts_test.clj index df3938823..816aa91d3 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 (= (lattice lay) (lattice 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))))) ;;; From 2db09a7cc3b8594f4b6c69ddb71394f0672256e3 Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Thu, 22 Sep 2022 11:08:14 +0200 Subject: [PATCH 060/112] Bump flake (#88) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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`. --- .gitignore | 1 + flake.lock | 24 ++++++++++++------------ flake.nix | 3 --- nix/conexp-clj/dependencies.nix | 2 +- shell.nix | 2 +- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index ffb97da5c..0ddd41632 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ /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/ diff --git a/flake.lock b/flake.lock index be19e9206..12cbc7530 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1627913399, - "narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=", + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", "owner": "edolstra", "repo": "flake-compat", - "rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "flake-utils": { "locked": { - "lastModified": 1638122382, - "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", + "lastModified": 1649676176, + "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", "owner": "numtide", "repo": "flake-utils", - "rev": "74f7e4319258e287b0f9cb95426c9853b282730b", + "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", "type": "github" }, "original": { @@ -34,11 +34,11 @@ "gitignoresrc": { "flake": false, "locked": { - "lastModified": 1635165013, - "narHash": "sha256-o/BdVjNwcB6jOmzZjOH703BesSkkS5O7ej3xhyO8hAY=", + "lastModified": 1646480205, + "narHash": "sha256-kekOlTlu45vuK2L9nq8iVN17V3sB0WWPqTTW3a2SQG0=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "5b9e0ff9d3b551234b4f3eb3983744fa354b17f1", + "rev": "bff2832ec341cf30acb3a4d3e2e7f1f7b590116a", "type": "github" }, "original": { @@ -49,11 +49,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1639989170, - "narHash": "sha256-REf0rqdJs6XIPo/zc/FhJMecggjEXi45QyiV207y30Y=", + "lastModified": 1650921206, + "narHash": "sha256-RGlfTC2ktqLVw0gBvZeCM//B4ig2CdQJm39sDvm0DBQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "86453059bf8312f0f5bf1fe8a2f52da2be664489", + "rev": "3a9e0f239d80fa134e8fcbdee4dfc793902da37e", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index f2deb237d..ef91cb702 100644 --- a/flake.nix +++ b/flake.nix @@ -37,9 +37,6 @@ clojure-lsp leiningen ]; - shellHook = '' - export "PATH=${conexp-clj}/bin:$PATH" - ''; }; }); } diff --git a/nix/conexp-clj/dependencies.nix b/nix/conexp-clj/dependencies.nix index fb576e6c5..45665ef23 100644 --- a/nix/conexp-clj/dependencies.nix +++ b/nix/conexp-clj/dependencies.nix @@ -33,5 +33,5 @@ stdenv.mkDerivation { outputHashAlgo = "sha256"; outputHashMode = "recursive"; - outputHash = "sha256-o+DQDDs3Ch+cGuMoFGB6xGFwJKBM/946X6bFzlBiaVo="; + outputHash = "sha256-Kk5hx+CasiiQd5qYENbcOpq+DymomsiJIsTRdYLRtBg="; } diff --git a/shell.nix b/shell.nix index 9eb132a58..8a7465a15 100644 --- a/shell.nix +++ b/shell.nix @@ -10,4 +10,4 @@ ) { src = ./.; - }).shellNix + }).shellNix.default From 3201c4781e836e91d8364382e2db606362fe407c Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Thu, 22 Sep 2022 13:48:38 +0300 Subject: [PATCH 061/112] 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 --- doc/code/trace-context.clj | 2 +- src/main/clojure/conexp/fca/graph.clj | 23 +-- src/main/clojure/conexp/fca/lattices.clj | 23 +-- src/main/clojure/conexp/fca/posets.clj | 163 +++++++++++++++++ src/main/clojure/conexp/gui/draw.clj | 19 +- .../conexp/gui/draw/control/dim_draw.clj | 6 +- .../conexp/gui/draw/control/freese.clj | 2 +- .../conexp/gui/draw/control/parameters.clj | 8 +- .../clojure/conexp/gui/editors/lattices.clj | 4 +- src/main/clojure/conexp/layouts/base.clj | 135 +++++++------- src/main/clojure/conexp/layouts/common.clj | 46 +++-- src/main/clojure/conexp/layouts/dim_draw.clj | 18 +- src/main/clojure/conexp/layouts/force.clj | 31 +++- src/main/clojure/conexp/layouts/freese.clj | 20 +- src/main/clojure/conexp/layouts/layered.clj | 26 +-- src/main/clojure/conexp/layouts/util.clj | 79 +++++++- src/main/clojure/conexp/main.clj | 1 + src/main/clojure/conexp/math/algebra.clj | 80 +------- src/test/clojure/conexp/api/handler_test.clj | 4 +- src/test/clojure/conexp/fca/lattices_test.clj | 13 +- src/test/clojure/conexp/fca/posets_test.clj | 142 +++++++++++++++ src/test/clojure/conexp/io/layouts_test.clj | 2 +- 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 | 25 ++- 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 + 31 files changed, 906 insertions(+), 420 deletions(-) create mode 100644 src/main/clojure/conexp/fca/posets.clj create mode 100644 src/test/clojure/conexp/fca/posets_test.clj 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/src/main/clojure/conexp/fca/graph.clj b/src/main/clojure/conexp/fca/graph.clj index 139561fcd..73c785f9e 100644 --- a/src/main/clojure/conexp/fca/graph.clj +++ b/src/main/clojure/conexp/fca/graph.clj @@ -7,13 +7,14 @@ ;;; graph <-> 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/lattices.clj b/src/main/clojure/conexp/fca/lattices.clj index 7cb02ad08..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,10 @@ (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." 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/gui/draw.clj b/src/main/clojure/conexp/gui/draw.clj index 85cf68510..bd9e6deb6 100644 --- a/src/main/clojure/conexp/gui/draw.clj +++ b/src/main/clojure/conexp/gui/draw.clj @@ -21,11 +21,14 @@ [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])) ;;; Lattice Editor @@ -105,9 +108,19 @@ (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")) +;;; 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 @@ -120,7 +133,7 @@ & {:keys [visible dimension] :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) 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/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 b2955e9e2..366c696df 100644 --- a/src/main/clojure/conexp/gui/draw/control/parameters.clj +++ b/src/main/clojure/conexp/gui/draw/control/parameters.clj @@ -89,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)))))) @@ -135,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))))) 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/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..b2037e67b 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] @@ -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..47f2034f8 100644 --- a/src/main/clojure/conexp/layouts/layered.clj +++ b/src/main/clojure/conexp/layouts/layered.clj @@ -7,11 +7,11 @@ ;; 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.layouts.base)) ;;; Simple Layered Layout @@ -26,26 +26,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..d7db70dc7 100644 --- a/src/main/clojure/conexp/layouts/util.clj +++ b/src/main/clojure/conexp/layouts/util.clj @@ -11,8 +11,11 @@ (:require [conexp.base :refer :all] [conexp.math.algebra :refer :all] [conexp.fca.lattices :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])) ;;; @@ -59,7 +62,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,9 +115,21 @@ 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))) + +(defmethod edges Poset + [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 Lattice [lattice] (edges-by-border lattice)) @@ -229,6 +244,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 809e96cc7..f5ac3be2e 100644 --- a/src/main/clojure/conexp/main.clj +++ b/src/main/clojure/conexp/main.clj @@ -25,6 +25,7 @@ conexp.fca.dependencies conexp.fca.lattices conexp.fca.more + conexp.fca.posets conexp.io.latex conexp.io.contexts conexp.io.implications 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/test/clojure/conexp/api/handler_test.clj b/src/test/clojure/conexp/api/handler_test.clj index 79fec2610..c155d35a9 100644 --- a/src/test/clojure/conexp/api/handler_test.clj +++ b/src/test/clojure/conexp/api/handler_test.clj @@ -305,8 +305,8 @@ 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} 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/io/layouts_test.clj b/src/test/clojure/conexp/io/layouts_test.clj index 816aa91d3..e8278643f 100644 --- a/src/test/clojure/conexp/io/layouts_test.clj +++ b/src/test/clojure/conexp/io/layouts_test.clj @@ -32,7 +32,7 @@ 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 (= (lattice lay) (lattice out-in-lay)) + (and (= (poset lay) (poset out-in-lay)) (= (positions lay) (positions out-in-lay)) (= (connections lay) (connections out-in-lay)))))) 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..5a4034db7 100644 --- a/src/test/clojure/conexp/layouts/layered_test.clj +++ b/src/test/clojure/conexp/layouts/layered_test.clj @@ -9,7 +9,6 @@ (ns conexp.layouts.layered-test (:use conexp.base conexp.math.algebra - conexp.fca.lattices conexp.layouts.base conexp.layouts.util conexp.layouts.layered @@ -19,12 +18,12 @@ ;;; (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 +32,20 @@ (= (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))))))))) (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)) From 77155b1be2a3f18722076d27917c61abe812dc88 Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Fri, 23 Sep 2022 09:21:18 +0200 Subject: [PATCH 062/112] 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 --- .envrc | 2 +- .github/workflows/run-tests.yaml | 17 + .github/workflows/update-deps-lock.yaml | 22 + .gitignore | 1 + deps-lock.json | 1881 +++++++++++++++++++++++ flake.lock | 119 +- flake.nix | 111 +- nix/conexp-clj/default.nix | 49 - nix/conexp-clj/dependencies.nix | 37 - project.clj | 8 +- shell.nix | 13 - 11 files changed, 2101 insertions(+), 159 deletions(-) create mode 100644 .github/workflows/run-tests.yaml create mode 100644 .github/workflows/update-deps-lock.yaml create mode 100644 deps-lock.json delete mode 100644 nix/conexp-clj/default.nix delete mode 100644 nix/conexp-clj/dependencies.nix delete mode 100644 shell.nix diff --git a/.envrc b/.envrc index 051d09d29..3550a30f2 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -eval "$(lorri direnv)" +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 0ddd41632..d38ac958b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ /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/deps-lock.json b/deps-lock.json new file mode 100644 index 000000000..8370b4317 --- /dev/null +++ b/deps-lock.json @@ -0,0 +1,1881 @@ +{ + "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/1.0.1/better-cond-1.0.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-W9tqaXKucHYAFzcKbeHrALJTeg7NJvpMeWSnyL7ZUGE=" + }, + { + "mvn-path": "better-cond/better-cond/1.0.1/better-cond-1.0.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-N7drmN0Q4TANv9wBMrq33pS9oQJWhk2lKUudScqubzs=" + }, + { + "mvn-path": "cheshire/cheshire/3.0.0/cheshire-3.0.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ig93XiGeW+XK2AYO2WLkaAlXc/9mtUTAOqwouGREUhc=" + }, + { + "mvn-path": "cheshire/cheshire/3.1.0/cheshire-3.1.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-J9R5EA3poigtW7yovudwDCmaf0EDqXNOeGRKL0Yu9/o=" + }, + { + "mvn-path": "cheshire/cheshire/4.0.3/cheshire-4.0.3.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-mk9BzL9RixYRSli3HXnSQMtKwn1dKFGmBfug7SnRQ7Y=" + }, + { + "mvn-path": "cheshire/cheshire/5.10.2/cheshire-5.10.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-F7vNJXvbviEm94QkUPSb4DoDXCOgTwje5vke7TW9/b0=" + }, + { + "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-lite/clj-http-lite/0.2.0/clj-http-lite-0.2.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-kx2UO4XajuKn4jwTbnIOu1SoRdTMB5OQZjlXD9MJ+1A=" + }, + { + "mvn-path": "clj-http-lite/clj-http-lite/0.2.0/clj-http-lite-0.2.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-kuFNujSx/Jq2I1BH8Xd3J+oFpOk+mvnV6pnyGXW/H9s=" + }, + { + "mvn-path": "clj-http/clj-http/0.3.6/clj-http-0.3.6.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-sJ7kQCymPL44RmMhgWDZSVQh60F4Z6PHbcPF3Q5xcwY=" + }, + { + "mvn-path": "clj-http/clj-http/3.11.0/clj-http-3.11.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ANUzcJkhycHXeCf+WNgzJj4/PckxGE4oxOH+/V0HLSI=" + }, + { + "mvn-path": "clj-http/clj-http/3.11.0/clj-http-3.11.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-LgVfywRWSylAY7MrJ5ubkfImKEugxQXLUr797zPQEAo=" + }, + { + "mvn-path": "clj-stacktrace/clj-stacktrace/0.2.7/clj-stacktrace-0.2.7.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-aotYq1L3xjXPWHVl1ZaWijqlwqzvFeexQaJKyMnbEFE=" + }, + { + "mvn-path": "clj-stacktrace/clj-stacktrace/0.2.7/clj-stacktrace-0.2.7.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-zxhH3yxpzzvoW9cjUKCDb3SuVnYwshF1lnw57q4Ipyg=" + }, + { + "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.0.0/jackson-core-2.0.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-VagS+XgNophkzWqex3BkvXOnpR7vot3kcwvLCHTwmpU=" + }, + { + "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.0.6/jackson-core-2.0.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-TP8H6SgWSTsCAAh+PJvv62S+VZqF6L0pejcbFBGWbFo=" + }, + { + "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.12.4/jackson-core-2.12.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-B1AFGuFEwR9yMJdB540BNNE7KU9M5PeQCjogNWQyQxE=" + }, + { + "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.12.4/jackson-dataformat-cbor-2.12.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/WGct8Jkztc9nxg28yBi901UmHuYym8kA4EPRiRTiM8=" + }, + { + "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.0.0/jackson-dataformat-smile-2.0.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-iTdj0nXkW/84zCm7ZNT871MlKSIBXwGnCCXG4MEh+vo=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.0.6/jackson-dataformat-smile-2.0.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-I59p/oBCowU+d96eFdW3yECT/PzbpSf3uz9NjnQDizM=" + }, + { + "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.12.4/jackson-dataformat-smile-2.12.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-C51/GFtYIctOQ/Q6IbV9bv11jSbZ/6rBexR0BLAwFhE=" + }, + { + "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.12.4/jackson-dataformats-binary-2.12.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-LM0+oMFZqAvlgQw2R3upsdNZ+ARcRzVV6OwTEKZZenk=" + }, + { + "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.12.4/jackson-base-2.12.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-GSGu6Fvrq/AW6lBYOrm0EdUQt3VY9Wgj3LS6V8W5f/k=" + }, + { + "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.12.4/jackson-bom-2.12.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-6z5zvu64rKlVZDw4uZTdpvsM68MZZ2+A6tX7nsr/bj0=" + }, + { + "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.12/jackson-parent-2.12.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-YqocFnmt4J8XPb8bbDLTXFXnWAAjj9XkjxOqQzfAh1s=" + }, + { + "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/41/oss-parent-41.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-r2UPpN1AC8V2kyC87wjtk4E/NJyr6CE9RprK+72UXYo=" + }, + { + "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.12/commons-codec-1.12.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-I99Y+unIPRvNJ3uZ+UKenYwTTwYAtz4uhrI4XteTyB4=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.12/commons-codec-1.12.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-vPDVJQioXTBqz9lmi0idOFJ/4/T/J6pvYhtqHcR+I2g=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.4/commons-codec-1.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-9fMAaUIboBPna+znYEB9zIACJGHclVXcvXX9sG2aQI8=" + }, + { + "mvn-path": "commons-codec/commons-codec/1.5/commons-codec-1.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-DAXGK5NrrXQ2C/v+OZPTpdGhnxJ6Oj3z91NDcbPAzK4=" + }, + { + "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-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.2.1/commons-fileupload-1.2.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-I4L0wpLwYWiQkYpUx9/tjtQyocgRgNWHSJNzTva1Ako=" + }, + { + "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.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-pOwCM29JJT6lBAVpi3kjK4xcvwLLYN86Z013p0mh3vc=" + }, + { + "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-io/commons-io/1.4/commons-io-1.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-La5JahnIK46ZhaEkaqgM+YCCsa4iF89LLU1f8To2WvI=" + }, + { + "mvn-path": "commons-io/commons-io/2.1/commons-io-2.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-3jQSQ02u92qrYbc0nrjZY0kifC+o3BtiYdyyAjD6KgM=" + }, + { + "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-logging/commons-logging/1.1.1/commons-logging-1.1.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-0PLhbQVOi7l63ZyiZSXrI0b2koCfzSooeH2ozrPDXug=" + }, + { + "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-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": "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.5.0/http-kit-2.5.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-FmNomgwVOHMFggKJpf8DSaokRsEOqgyRZ7UmaNh9n5g=" + }, + { + "mvn-path": "http-kit/http-kit/2.5.0/http-kit-2.5.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-HamOPAvXTK6eyaO3HJovEnJSr8eW8EoIlXA/EMXLJ4Y=" + }, + { + "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": "luposlip/json-schema/0.3.4/json-schema-0.3.4.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-tiwgQt5tA60CJHKi4rRiVB0IFyEcEsmPSjns9pbUxYs=" + }, + { + "mvn-path": "luposlip/json-schema/0.3.4/json-schema-0.3.4.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-hdGqWrOh1aQG6Po6otJLachy9aK1cG+498uAu8MTeLw=" + }, + { + "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/bencode/1.0.0/bencode-1.0.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ygs4AH9c8Y43bQj34sgA26e5SZGyzSd3YNQG3nfPu/o=" + }, + { + "mvn-path": "nrepl/drawbridge/0.1.0/drawbridge-0.1.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-9g4WpaMeWpvotgC7iWCO+gIU9BdX73g/bOUsb9XsRFE=" + }, + { + "mvn-path": "nrepl/drawbridge/0.1.0/drawbridge-0.1.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Bi1zAx0z2Q5pYr7Z5Ntip7wAym0ZFCpDW9SFTvZPeiE=" + }, + { + "mvn-path": "nrepl/nrepl/0.4.0/nrepl-0.4.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-qU5NXvXNOmj4gWAkxYW6Ul5is1Tc6dsy5sYQWKQg5TY=" + }, + { + "mvn-path": "nrepl/nrepl/0.4.5/nrepl-0.4.5.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-Rh5MqGa6r8uImszQHYo9JlKqNL4ZiBF6MB12zUOeWCE=" + }, + { + "mvn-path": "nrepl/nrepl/0.6.0/nrepl-0.6.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-9pDMgjuxXPRJMtpHUla3RVtrFj4CP4MJ0rzdhxrugxE=" + }, + { + "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": "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/4/apache-4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-npMjomuo6yOU7+8MltMbcN9XCAhjDcFHyrHnNUHMUZQ=" + }, + { + "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/11/commons-parent-11.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-ueAwbzk0YBBbij+lEFJQxSkbHvqpmVSs4OwceDEJoCo=" + }, + { + "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/20/commons-parent-20.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-7cEfBOcgFjBhzYgsILqRscOuGlmwx5ypcTN2UzVkIVs=" + }, + { + "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/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/5/commons-parent-5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-i9YywAvfgKfeNsIrYPEkUsFH2Oyi8A151maZ6+faoCo=" + }, + { + "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/7/commons-parent-7.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-AelwZ6H5vQ4KNPLzK79D/m6bEZEPOw3+d/1/tBhhkQU=" + }, + { + "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/4.1.2/httpclient-4.1.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-nqRFFJ15UYBrnkPlg04PNOQMlAH6NkTKfF7ZMkagKH8=" + }, + { + "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.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.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.1.2/httpcomponents-client-4.1.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-g8I+Q7BnJWjTugL6ZhY52qU3i/AIQnXrSTdneoWx+60=" + }, + { + "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.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.1.2/httpcomponents-core-4.1.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-BXspb0yjs4WJwvlE4vfzHoaJJVZxdePzu23fKVHGIdw=" + }, + { + "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-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/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/4.1.2/httpcore-4.1.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-FhOqE3lbVAY26tb0N8qmSZ2MLjQn02JKwfi4Cv4mwO4=" + }, + { + "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.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-4G6J1AlDJF/Po57FN82/zjdirs3o+cWXeA0rAMK0NCQ=" + }, + { + "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/httpmime/4.1.2/httpmime-4.1.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-L9SZBSvyJ6acZmW4jzGVIBoKbBSHoqNQYZdfIvC0O0Q=" + }, + { + "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/project/4.1.1/project-4.1.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-IbtNRN/1TjOjfBGvaYWacUICrgCWmqtUU+unJ2aI+Ow=" + }, + { + "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.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.3.610/core.async-1.3.610.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-A7svz0ZslWFRMvZ5qVgYv9Y7nQry2ouCZ/q1ehgMYXc=" + }, + { + "mvn-path": "org/clojure/core.async/1.3.610/core.async-1.3.610.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-ZgSZVDqGbvrJPC73OojFzqbK719HDMI+oeSPRedNL3w=" + }, + { + "mvn-path": "org/clojure/core.cache/1.0.207/core.cache-1.0.207.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-j0TtpTM2iDtGH7nIfkixdDpNgmt9OvsEyI962ZdWXso=" + }, + { + "mvn-path": "org/clojure/core.cache/1.0.207/core.cache-1.0.207.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-cT6lL/F0jRLo7Fj/3Iz7u2vfDs2PFqcQz54plG6wwBs=" + }, + { + "mvn-path": "org/clojure/core.memoize/1.0.236/core.memoize-1.0.236.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-hJz3OP9Z7XTcxsxBxDsiD/Tk014GgItrwbF7kBgmVEE=" + }, + { + "mvn-path": "org/clojure/core.memoize/1.0.236/core.memoize-1.0.236.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Y06JD65bM83SsVieEfjmt4WU1XQTwC+hF49oi88iCeg=" + }, + { + "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.0.0/data.int-map-1.0.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-miIrvZqLSzV0Tx74g+uDmy8ZrZJsJhyeYoQVev4jd8A=" + }, + { + "mvn-path": "org/clojure/data.int-map/1.0.0/data.int-map-1.0.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-QvxXdpkP+HtoqC7gKVu1E5X0cPrFLCQ+hwmDPFMpOQA=" + }, + { + "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.0.0/data.priority-map-1.0.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-oelmVnOmC8I3A8FMT0fAIecgHL8yzUpZ4sJJyJ9bExU=" + }, + { + "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.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.1.6/math.combinatorics-0.1.6.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-hayruPhhPvNzpRh1yrfwMuK+BzsZNxZeItmC0okbUns=" + }, + { + "mvn-path": "org/clojure/math.combinatorics/0.1.6/math.combinatorics-0.1.6.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-5YEc6YpDSIBkBV4/jS8jXEbcdEL4f3dBMIcBOawhKp8=" + }, + { + "mvn-path": "org/clojure/math.numeric-tower/0.0.4/math.numeric-tower-0.0.4.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Crctj8l20hUlovMsUs37nJgIqefvjbewgDrKQ4GMgqU=" + }, + { + "mvn-path": "org/clojure/math.numeric-tower/0.0.4/math.numeric-tower-0.0.4.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-CngK5eJjqbgkFceq+FEk0QCfokGKfsEdjJ4DqwbWIpA=" + }, + { + "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.0.0/pom.contrib-1.0.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-EBH6rlyeSWhY5MZQujNxOr1Gml1S4Arrf1sBoryvR+k=" + }, + { + "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.1.0/tools.analyzer.jvm-1.1.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-36tXEmBdmBZdFWbHj91XE2l+FqN5yjD1qdnXJoJljk0=" + }, + { + "mvn-path": "org/clojure/tools.analyzer.jvm/1.1.0/tools.analyzer.jvm-1.1.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-oURBkMmdK5+OfSLhJgya+uVu2eiaZMgJl5XYK5CD3jM=" + }, + { + "mvn-path": "org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Rj9DH4m9wuaePN+9pf24wtO39tpe3aoDZ98NsEfsQVY=" + }, + { + "mvn-path": "org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-BFPP8KudRVDiyGfY6ZVfDAID4TloNw6zzke99ZMy5Pk=" + }, + { + "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.194/tools.cli-1.0.194.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-TD1eJoX8e62W2Z3ex1vW6pB4rrhRQibgzJSzEkP8aRA=" + }, + { + "mvn-path": "org/clojure/tools.cli/1.0.194/tools.cli-1.0.194.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-2P0j6g1cwhJ9JZu4oYb4WX8oc24MxN+h9b6N0hY+Zzo=" + }, + { + "mvn-path": "org/clojure/tools.logging/0.4.1/tools.logging-0.4.1.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Diam5eg2HQtIrJesD0vTD6p+/nGmqyNKdyBVlfKLDuI=" + }, + { + "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.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/1.3.2/tools.reader-1.3.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-E7dTbXkDdT+wJvVHGTDi7ny1Xhh6nhQR51crm56HaYA=" + }, + { + "mvn-path": "org/clojure/tools.reader/1.3.2/tools.reader-1.3.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-sc3Czi6cUhEEfa7M0kuo1bE2yOmYDDiZ6k4rovDZ0dg=" + }, + { + "mvn-path": "org/codehaus/jackson/jackson-core-asl/1.9.5/jackson-core-asl-1.9.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-fRWNcilZ9Tq8mCQHatuKxvLFPhf5scZuP8BCT9UoUOU=" + }, + { + "mvn-path": "org/codehaus/jackson/jackson-smile/1.9.5/jackson-smile-1.9.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-bJYO8CtN+fFAiKHoigqhxCiA4A/BamEuK7DI8CDTGd0=" + }, + { + "mvn-path": "org/json/json/20220320/json-20220320.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Ht9/zqeaFrjf3TvJiN3sf4kIsfd2L98A05rLA3VCdHo=" + }, + { + "mvn-path": "org/json/json/20220320/json-20220320.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-FKNkhDrCuoikgSaePGvGosDKURjCoHsd3/K/YN4ce/U=" + }, + { + "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-parent/5.2/asm-parent-5.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-y/rNXdgS2xbckQmUAH/ljZQColUH+BocIhjhXCwb7fo=" + }, + { + "mvn-path": "org/ow2/asm/asm/5.2/asm-5.2.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Pl6g19osUVXvT0cNkJLULeNOP1PbZYnHwH1nIa30uj4=" + }, + { + "mvn-path": "org/ow2/asm/asm/5.2/asm-5.2.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-KJ9/u9ewxXbVKG6K3PjQc1E005Fe/qZDbivjXBH88FA=" + }, + { + "mvn-path": "org/ow2/ow2/1.3/ow2-1.3.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-USFcZ9LAaNi30vb4D1E3KgmAdd7MxEjUvde5h7qDKPs=" + }, + { + "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.5/org.ow2.sat4j.core-2.3.5.jar", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-/6KEf3SDr7lcz2lt+mevLmPzMw1wPLQFKRg4UE3Uyss=" + }, + { + "mvn-path": "org/ow2/sat4j/org.ow2.sat4j.core/2.3.5/org.ow2.sat4j.core-2.3.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-Jo8VahIj2ts8ATkGDI98/YIwcyNeC4cXYZ3AzjGz3Aw=" + }, + { + "mvn-path": "org/ow2/sat4j/org.ow2.sat4j.pom/2.3.5/org.ow2.sat4j.pom-2.3.5.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-c4CkdFv8NbOLJWBlbzlT7ngyp4z0Zdxew6QRz2KFRM4=" + }, + { + "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": "org/thnetos/cd-client/0.3.6/cd-client-0.3.6.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ngdkTQLtVj1V4UwfiPKOoZl7dc04jYS28avz3XUn4jQ=" + }, + { + "mvn-path": "org/thnetos/cd-client/0.3.6/cd-client-0.3.6.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-8xneE0aDe5EM44egbOEcw794NIcEN3CMmK+x8tkpmo0=" + }, + { + "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.4.4/reply-0.4.4.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-eCSfzGwprV/uQpTJ8b/Dv/mc/GEVA2S27wmBG/RaQfc=" + }, + { + "mvn-path": "reply/reply/0.4.4/reply-0.4.4.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-eKSMuSIDpECTxEQo7dY/p1LnFe5FXCLnHO3j6j6XTBA=" + }, + { + "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.2/ring-codec-1.1.2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-dY0nO/1nr7z4O3k5Uj+eKI4ifJstIiMsPpX1ESsK0GA=" + }, + { + "mvn-path": "ring/ring-codec/1.1.2/ring-codec-1.1.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-sl/LVYL/wFWem+BnzQXP13SAm5IocZz8HqVojsnKH+c=" + }, + { + "mvn-path": "ring/ring-core/1.0.2/ring-core-1.0.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-nFgQUuxL3SvlaQTptGCUBQTuQ5Gd9yVBsyWjCtHkkp4=" + }, + { + "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.8.2/ring-core-1.8.2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-HWq9CMVYxvzzzQd12IY9G/QUNGfkF/KPKM9OEGn/QqE=" + }, + { + "mvn-path": "ring/ring-core/1.8.2/ring-core-1.8.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-AkLm16tSI3+T/a3ADfY0wIKK+2xOrAlgXZwb0aKF7CE=" + }, + { + "mvn-path": "ring/ring-devel/1.8.2/ring-devel-1.8.2.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ijUWbMPwbAJJOqIQldVogNR2jrb6mZ+/pCPdoVmZN24=" + }, + { + "mvn-path": "ring/ring-devel/1.8.2/ring-devel-1.8.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-xHrQH/eB4iR78DlxILT5lKNmOZhpi+uEmGMlIX3yn/o=" + }, + { + "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-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.1/rolling-stones-1.0.1.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-pXFiZpozPpl5h4aoSgKRAepUUNlsglpZWI8Ou7odxW8=" + }, + { + "mvn-path": "rolling-stones/rolling-stones/1.0.1/rolling-stones-1.0.1.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-EU6qRQ1GbPo53rBkHYcitPqmu/uDB912i74tFG9uzzA=" + }, + { + "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.10.2/slingshot-0.10.2.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-uktZMI2MBxSRHki1Jq6rKDdIzkdSTVSDz+nbr6rOUf8=" + }, + { + "mvn-path": "slingshot/slingshot/0.10.3/slingshot-0.10.3.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-uSqD0IT1dvpHTTi7XswyU7zg4CnXZaRFykgHFS4eqiM=" + }, + { + "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.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/flake.lock b/flake.lock index 12cbc7530..e1dc2b00b 100644 --- a/flake.lock +++ b/flake.lock @@ -1,28 +1,60 @@ { "nodes": { - "flake-compat": { - "flake": false, + "clj-nix": { + "inputs": { + "devshell": "devshell", + "flake-utils": [ + "utils", + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1650374568, - "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "lastModified": 1663854702, + "narHash": "sha256-gnoyYWvZl64WBqR3tf9bKHAznEtBCHmwx7taHghH9Lw=", + "owner": "mmarx", + "repo": "clj-nix", + "rev": "10b1e9544811c07e02c536872fcd6a6aeed96ba7", "type": "github" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", + "owner": "mmarx", + "ref": "fix-lein", + "repo": "clj-nix", + "type": "github" + } + }, + "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "clj-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1658746384, + "narHash": "sha256-CCJcoMOcXyZFrV1ag4XMTpAPjLWb4Anbv+ktXFI1ry0=", + "owner": "numtide", + "repo": "devshell", + "rev": "0ffc7937bb5e8141af03d462b468bd071eb18e1b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", "type": "github" } }, "flake-utils": { "locked": { - "lastModified": 1649676176, - "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", + "lastModified": 1642700792, + "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", "owner": "numtide", "repo": "flake-utils", - "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", + "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", "type": "github" }, "original": { @@ -31,14 +63,33 @@ "type": "github" } }, - "gitignoresrc": { - "flake": false, + "flake-utils_2": { "locked": { - "lastModified": 1646480205, - "narHash": "sha256-kekOlTlu45vuK2L9nq8iVN17V3sB0WWPqTTW3a2SQG0=", + "lastModified": 1644229661, + "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", "owner": "hercules-ci", "repo": "gitignore.nix", - "rev": "bff2832ec341cf30acb3a4d3e2e7f1f7b590116a", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", "type": "github" }, "original": { @@ -49,26 +100,44 @@ }, "nixpkgs": { "locked": { - "lastModified": 1650921206, - "narHash": "sha256-RGlfTC2ktqLVw0gBvZeCM//B4ig2CdQJm39sDvm0DBQ=", + "lastModified": 1663760840, + "narHash": "sha256-ym5Iycs5H4cOaLfE2/vC0tsLp8XuBJQIHGV8/uXSy8M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3a9e0f239d80fa134e8fcbdee4dfc793902da37e", + "rev": "9bdbbaa634aa666eb6a27096bdcb991c59181244", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-21.11", + "ref": "nixos-22.05", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "gitignoresrc": "gitignoresrc", - "nixpkgs": "nixpkgs" + "clj-nix": "clj-nix", + "gitignore": "gitignore", + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "utils": { + "inputs": { + "flake-utils": "flake-utils_2" + }, + "locked": { + "lastModified": 1657226504, + "narHash": "sha256-GIYNjuq4mJlFgqKsZ+YrgzWm0IpA4axA3MCrdKYj7gs=", + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "rev": "2bf0f91643c2e5ae38c1b26893ac2927ac9bd82a", + "type": "github" + }, + "original": { + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index ef91cb702..976ab4827 100644 --- a/flake.nix +++ b/flake.nix @@ -1,42 +1,91 @@ { - description = "conexp-clj, a general purpose software tool for Formal Concept Analysis"; + description = + "conexp-clj, a general purpose software tool for Formal Concept Analysis"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.11"; - flake-utils.url = "github:numtide/flake-utils"; - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.05"; + utils.url = "github:gytis-ivaskevicius/flake-utils-plus"; + + clj-nix = { + #url = "github:jlesquembre/clj-nix"; + url = + "github:mmarx/clj-nix/fix-lein"; # we need to wait for PR 31 to go through. + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "utils/flake-utils"; + }; }; - gitignoresrc = { + + gitignore = { url = "github:hercules-ci/gitignore.nix"; - flake = false; + inputs.nixpkgs.follows = "nixpkgs"; }; }; - outputs = { self, nixpkgs, flake-utils, ... }@inputs: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; }; - gitignoreSource = (import inputs.gitignoresrc { inherit (pkgs) lib; }).gitignoreSource; - conexp-clj = pkgs.callPackage ./nix/conexp-clj - { - inherit gitignoreSource; + outputs = { self, nixpkgs, utils, ... }@inputs: + let inherit (utils.lib) mkApp mkFlake; + in mkFlake { + inherit self inputs; + + channels.nixpkgs.overlaysBuilder = channels: + [ inputs.clj-nix.overlays.default ]; + + outputsBuilder = channels: + let + inherit (inputs.gitignore.lib) gitignoreSource; + inherit (inputs.clj-nix.lib) mk-deps-cache; + inherit (channels.nixpkgs) mkCljBin mkShell writeShellScriptBin; + + conexp = let + pname = "conexp-clj"; + version = "2.3.0-SNAPSHOT"; + in mkCljBin rec { + name = "conexp/${pname}"; + inherit version; + + projectSrc = gitignoreSource ./.; + main-ns = "conexp"; + + buildCommand = '' + lein uberjar + mkdir -p target + cp builds/uberjar/${pname}-${version}-standalone.jar target + ''; + doCheck = true; + checkPhase = "lein test"; }; - in - rec { - packages = flake-utils.lib.flattenTree { - conexp-clj = conexp-clj; - }; - defaultPackage = packages.conexp-clj; - apps.conexp-clj = flake-utils.lib.mkApp { drv = packages.conexp-clj; }; - defaultApp = apps.conexp-clj; - - devShell = pkgs.mkShell { - buildInputs = with pkgs; [ - clojure-lsp - leiningen - ]; + + in { + packages = rec { + conexp-clj = conexp; + default = conexp-clj; + }; + + apps = rec { + conexp-clj = mkApp { drv = conexp; }; + default = conexp-clj; + + deps-lock = mkApp { + drv = writeShellScriptBin "deps-lock" '' + ${channels.nixpkgs.deps-lock}/bin/deps-lock --lein $@ + ''; + }; + + test = let deps = mk-deps-cache { lock-file = ./deps-lock.json; }; + in mkApp { + drv = writeShellScriptBin "conexp-clj-tests" '' + lein test $@ + ''; + }; + }; + + devShells.default = mkShell { + buildinputs = with channels.nixpkgs; [ clojure-lsp leiningen ]; + }; + + formatter = channels.nixpkgs.alejandra; + }; - }); + + }; } diff --git a/nix/conexp-clj/default.nix b/nix/conexp-clj/default.nix deleted file mode 100644 index 83e217cb4..000000000 --- a/nix/conexp-clj/default.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ pkgs -, lib -, stdenv -, makeWrapper -, strip-nondeterminism -, gitignoreSource -, leiningen -, jdk -}: - -let dependencies = pkgs.callPackage ./dependencies.nix { inherit gitignoreSource leiningen; }; -in -stdenv.mkDerivation rec { - pname = "conexp-clj"; - version = "2.3.0-SNAPSHOT"; - src = gitignoreSource ../..; - - buildInputs = [ makeWrapper ]; - nativeBuildInputs = [ leiningen ]; - - preBuild = '' - mkdir -p $out - mkdir -p $out/maven - cp -R ${dependencies}/.m2 $out/maven - chmod -R +w $out/maven - ''; - - buildPhase = '' - runHook preBuild - - export HOME=$PWD - export LEIN_HOME=$HOME/.lein - mkdir -p $LEIN_HOME - - _JAVA_OPTIONS="-Duser.home=$out/maven" lein -o uberjar - - runHook postBuild - ''; - - postFixup = '' - ${strip-nondeterminism}/bin/strip-nondeterminism $out/${pname}-${version}-standalone.jar - ''; - - installPhase = '' - cp builds/uberjar/${pname}-${version}-standalone.jar $out - makeWrapper ${jdk}/bin/java $out/bin/${pname} --add-flags "-jar $out/${pname}-${version}-standalone.jar" - makeWrapper ${leiningen}/bin/lein $out/bin/lein --add-flags "-o" --set "_JAVA_OPTIONS" "-Duser.home=$out/maven" - ''; -} diff --git a/nix/conexp-clj/dependencies.nix b/nix/conexp-clj/dependencies.nix deleted file mode 100644 index 45665ef23..000000000 --- a/nix/conexp-clj/dependencies.nix +++ /dev/null @@ -1,37 +0,0 @@ -{ pkgs -, lib -, stdenv -, gitignoreSource -, leiningen -}: - -stdenv.mkDerivation { - name = "conexp-clj-dependencies"; - nativeBuildInputs = [ leiningen ]; - src = gitignoreSource ../..; - - buildPhase = '' - runHook preBuild - - export HOME=$PWD - export LEIN_HOME=$HOME/.lein - mkdir -p $LEIN_HOME - - _JAVA_OPTIONS="-Duser.home=$out" lein with-profile uberjar,dev,debug deps - # hack to get repl dependencies into the repo - echo | _JAVA_OPTIONS="-Duser.home=$out" lein repl - runHook postBuild - ''; - - installPhase = '' - runHook preInstall - - find $out/.m2 -type f -regex '.+\(\.lastUpdated\|resolver-status\.properties\|_remote\.repositories\|maven-metadata-local\.xml\)' -delete - - runHook postInstall - ''; - - outputHashAlgo = "sha256"; - outputHashMode = "recursive"; - outputHash = "sha256-Kk5hx+CasiiQd5qYENbcOpq+DymomsiJIsTRdYLRtBg="; -} diff --git a/project.clj b/project.clj index 4b69fc441..334520e19 100644 --- a/project.clj +++ b/project.clj @@ -40,15 +40,17 @@ [ring-cors "0.1.13"] [http-kit "2.5.0"] [org.apache.commons/commons-math3 "3.6.1"] - [luposlip/json-schema "0.3.3"] + [luposlip/json-schema "0.3.4"] [org.clojure/data.csv "1.0.1"]] :profiles {:uberjar {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] - [ring/ring-mock "0.4.0"]] + [ring/ring-mock "0.4.0"] + [nrepl/nrepl "0.6.0"]] :aot :all} :dev {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] - [ring/ring-mock "0.4.0"]] + [ring/ring-mock "0.4.0"] + [nrepl/nrepl "0.6.0"]] :javac-options ["-Xlint:deprecation" "-Xlint:unchecked"]} :gorilla {:main conexp.main :plugins [[org.clojars.benfb/lein-gorilla "0.7.0"]]}} diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 8a7465a15..000000000 --- a/shell.nix +++ /dev/null @@ -1,13 +0,0 @@ -(import - ( - let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - in - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } - ) - { - src = ./.; - }).shellNix.default From 2767b80e8eb5af457b59e81914d4a017ecffd636 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Mon, 26 Sep 2022 23:15:34 +0200 Subject: [PATCH 063/112] Fixed some of the deprecated code in latdraw. --- .../org/latdraw/orderedset/ChainDecomposition.java | 4 ++-- src/main/java/org/latdraw/orderedset/OrderedSet.java | 2 +- .../java/org/latdraw/partition/BasicPartition.java | 10 +++++----- .../java/org/latdraw/partition/ReadPartitions.java | 2 +- src/main/java/org/latdraw/util/SimpleList.java | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) 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); } From cc6516991d35b858a7aada03a060f8e7802c48e2 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:33:24 +0300 Subject: [PATCH 064/112] 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 --- README.md | 1 + doc/Concept-Lattices.org | 3 +- doc/Protoconcepts.org | 91 ++++++++++ doc/images/protoconcept-lattice-dimdraw.png | Bin 0 -> 88507 bytes doc/images/protoconcept-lattice.png | Bin 0 -> 92565 bytes src/main/clojure/conexp/fca/cover.clj | 2 + src/main/clojure/conexp/fca/protoconcepts.clj | 93 +++++++++++ src/main/clojure/conexp/gui/draw.clj | 36 +++- src/main/clojure/conexp/layouts/layered.clj | 5 +- src/main/clojure/conexp/layouts/util.clj | 14 +- src/test/clojure/conexp/fca/cover_test.clj | 9 +- .../clojure/conexp/fca/protoconcepts_test.clj | 158 ++++++++++++++++++ .../clojure/conexp/layouts/layered_test.clj | 24 ++- 13 files changed, 422 insertions(+), 14 deletions(-) create mode 100644 doc/Protoconcepts.org create mode 100644 doc/images/protoconcept-lattice-dimdraw.png create mode 100644 doc/images/protoconcept-lattice.png create mode 100644 src/main/clojure/conexp/fca/protoconcepts.clj create mode 100644 src/test/clojure/conexp/fca/protoconcepts_test.clj diff --git a/README.md b/README.md index 4d1b014fb..b87ea6e2b 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ much more. 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) 6. [API documentation](doc/API.md) 7. [Development](doc/Development.org) diff --git a/doc/Concept-Lattices.org b/doc/Concept-Lattices.org index 370a52800..ecefa0671 100644 --- a/doc/Concept-Lattices.org +++ b/doc/Concept-Lattices.org @@ -173,4 +173,5 @@ switched on. 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/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/images/protoconcept-lattice-dimdraw.png b/doc/images/protoconcept-lattice-dimdraw.png new file mode 100644 index 0000000000000000000000000000000000000000..b0c5b41b32826392125646860c2106c4f70e5e51 GIT binary patch literal 88507 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

wxcE(xy&E zf$#w|E@~&Aw2e+tzvRsRU^7yObm%C&C;k&=hc&vEw3Mm`Yhouh9r)T80S?dAMgDtH z(N0h?9&K5Su=tDB{@Lfk8!<7Xt)8DSq{3K5*U2%>xXKFgR#XmuPEq*6%O$!a> z-g@#bgpFLGN4|9q$Yh)49{Ov<7XJJwh!b78Z-(PDA={_Au9yH=MxKU<2*gH$jEw+} zd}bkUf!}lyPfbAEhB^Y|{=qp8^{`x756N^skU?{SzXZ1Wo}C|IxbRS1vaFeb`HNSZ z7P_YLj9&M1^MK_=0FPd1L(EUL&~N8Oanf<%ux8V?OQ-iGMf@u`pZ%|$)JR?9TdJOU zSqcXS9ePRI^e=>wNE4(#IS>CE2Ye?rwNmI*^0+ys>-rEU5qm)($YmN?d`?@1eT4TsWQhx!Ke#8HG z$Ue6b7zp@I$FcA=6Ui$i{{cU1O#TMC6gI%k;`&z^1k74LXStH^FwXSal1_-=xT>b6 z*BrB_V6Tw=x%(uWjJw}@awqE~{dXY{W59HiDEHU*mt}ee{&l$kZJFxWq3zqx^VBFI zvylJap0FNmF*sJ^CsF(A2#wT{;+C!sg1dC!fm$=m$b4KvlhYpfR)Xm8jD|H4FJd z{Z*(0#(>M}gV_3o%Xp#rnTQAU;#H1$;SgHWbmnR)TLu^XxAVi-Z8TE(xJ%Sm#YknS zRic^6T?I@ib|yYD^W=U&p(sLXWJf~p4;;&9qhtd7x;DdjgUg401PwsYs)VSUH>8h* zD4Zu%cQ<&Rf7iObp^o}j+#>~K9Mas6jIi5fNNc65O~5nCwTC(b-x6K=&iKsVrlCQw zp`g=|bp$U1?%IjWV*k*tfVlL2MdVxsAE^S%vTi#ey+4?dFJssc%5&;TV*O1I%UkJH z9Avt6q3eb^{U?MQO)-;-1`>mKi(EPac}8R0vJnqrF6Hg^JoGKSF=IFH z^1#3VSo4{eN&(3EU7zwRu_qblYnqbIPNMKw^JBJ7UaU;|PcS zWA)OjQZwjOn54FU7%Fx3!inU^ zxgekp>ek}FHkbOBLJ{9rQ-U`J1P1{}jD15V_T+_a!s**DUy2^9sJNo@udXvY+8J0S z@oI`GOu||jqid2QRK@E;;1L3L&0X z$|iI8P!OUrJM|0dv>vg^#56J;OAiG9D{G%w%H)h@4%e{To#3?Iw(U8%Ab|RKA;Z%j zY!ieYCf{_ZD1M)VDw0q}@fp??&g1>p#fCP5)=%Ds!>$BN^;=rkov!T7iUt!+`;*_m!DcVRmlntS8f$ z!0GNW0U@KaF9NLd)NkM97T>;+86W|`Zlkz2$+97JuIS+8ld~NzA;qx41Ho_xB(ac!^6!ss!`j>(n6kQ@Z#){zBohno@(+Aj z7PBM@wFK?@H~jgZQ@1z%YBHanx9-mXYT_0k_&6{OageqAPaJLuzm_*TYCW~2A(=La zzz6=`s&37Z3&{J^P{FWuGWoy*J5p$MOnuLudeq02V_?&QwAl?T6)QWwiAM_ea2p~^ zum)Hxx=lotolN|(W8J^%F6>xwqO`y{%Uk(JXh`y57>bjQ&PAk=Ntt#xSV%@jmqAGa zOma0QX8p2>t6Z<+wmwqILq0s(;~ zv8y|bH15zv8W23=Q_fI(#3BUUe|`;uDTw`tn@{#1FjaFu*?+VD*?;z3az;mQ|6bCN zLhcOdQ%=}fMn(61SOl|1DH|O!_jS553&|})TN=W(kZ}a)eUwoL! z%z&;F)WB)s=znZiK=`Ro2G?V=C2D_5(@7+T@&lXn_|v0i6Ukvx$XwhV)h@Sm2^*Uf9#+XG zcmKQUwQ&;M;(E0m^L-A|;ed^``SvSLCX>7=a#6r{NFtYSGP$(?<0No|jSEc@+8J~Y z-nZ7-Oitj*tNwZ=({mYZ?0G>p&hLF*RATYpRv(&|BX6`X@yC+kBAN?wYH-Y-xHmm_ z`@bY+&$InuwTSx_B0qL`+qSdH&t;lxjGu-f;Q$|Nx69PImr!h7Cl38``kzhztE2_6 zR-i;}s5Bk}&BpJ@mEpA^qJU=ifh+Wh`y)&gg^=IY%{ftdR zcpWS&@D6a_by2gt z{Equ02g^7l)$af`LT?8UhHoi^+Yw%SXRfU@u_0i{A+SD1G(PZ6N7JF!A~=U;MfQ=$ z;d#v=@2it;Huykql`v?XIZ|ZkE<76ZQ$7rO)xSMhWV;XSKpBxX`**JbNW`9!0(5`d z)H;chfo*V-l%LP_d}I8Sox}{#XGAw)#8qTj#naXeQkkACdyr(2AwJo_xng-ExsC~@ zP=yL1<;XhukQqA{NlRD|loX+lN#|d+H;BNQB6Ngr)WDYxfCHd9I5O^y30dHeK#>*mj0XH*7g{<17we)12kDy)d?x72 z9FGweUQ(mb9?ve>_Ty(U2~FiA?I3_R_v*6EuQ;NNvh=4~tMyfwicRXUFfm?@a1sHs=k z=`6bz@0mB^vR!yBa>V0sh2XLfARZy3^7G3v8_ia7ab?WB{|D5np&euwa?it;1MC$G ze-p2SR3(qrKD)90J&(4$MvnEfB5_$}?^_<%_{9=GVTTa%YU$#&2vY9?dJ)XDZ-(eX z?fJY>`7Ij$<~llN#qre8rrCttr9l^QVYWu_pm4s z%b)rYFVCY4fMhe#Aa}3LcT{#*eI>U+NQlq7b49sf;5b^wRDOB&-a%@cTh9*fp-K@V zq`=u8eV3M6<5qLpCOTJkhl%eZcLfeheJ}^ zG)N`~$O>3H5#ix6WdmpQ`+rQmMQ86rjD+XFg~q+yN{;N#z2g-~$(@}KzSUly={mR< z_k9p^0*#7L8NJr7kTgibqcSJ6(^A`3VIa|N<-zl&8{u4JPrDBK-TCBO>=&_=dfHmh zi?J*f_313ravl>ZuIh?@Q)6?NDc@2+O2CHrV9YJLdV0RUTRqEHgqdy9xIcXOd_Nj= zo1s6D2h-|}rYCR{T{-sG_CG#Ys=a{v?DWwCEd-9VHUw5!bPJ%yap;o3252(`WFS(< ztDg2bemq0D;z{hno<@)i^cMD*c}hLk&UX8XWH@kJgnl=M?TMRULe>$}GcAb6@MYT#nQ@$N{x1o@=Rq zbTg@k0{sKB!fKq#Cvf*Lg2ll6DROWNw_&r$wy2J$wCm(Q`Bk$uJ|V}pyqvi|?NTRp z-=sw850ccqtBy%XM{NmmHKm!QLsQ)(r zB3F)y+U|tgtABE#B*&!#{V2~p+`~Pjx?5=IE&p859+0#aiwGz~&7|=or-LKnT`&_s z=3Eu#zS~b5Fu)ywY6i@N{a6Xl<_KR{fNFaM-}X z0jMEhhUa+Nmg%~m=_Y;GZQ~6kOh7Gx9abqZXTvkMqro>+03jf(TE8>0tTutSoHm3G zGq+@Z!enl^clfwQ+o-pmpZUilz+j*YfpgdL{U`)JcjQg{!N=}ksTU%M<jtQ4&HXN;?XzHfux^BAeh5}G8gx?Jt(rdn; z^;PAU;cF+Q1owi~++Xr599J70FmNKHw4bU2^(QI3IOkc5_UymYKa`%d?eS!=L{Ui# zbLtA`zsT3QDZAD2Q#7q_P3zvAlNT1RL2~usD$w0Oy1fw#7vjP#>(-^eiw#}zGrivs z5w9_#WUn;h}Qozrpl$g%E%Ewi|-1Zov{y zlEekihc*N&@>{lS0WVxO*Y^zU?$c5NEN*=DAUzCTE5OWKuX~advHe_3LqJ#kzp#}i7AB3)mMqO^_n*_`0U5&^SKoLZjN!9}H zcp*;sw?*yd8fK5}FZ#b`>?z@`QuZ=d$2AVNj*n&4SIHpCBfCN*t-IJu> z7+7VVR}=qZ7`eIRebsm$k}$3)Hw1(f^CaGhFSd7)xx;E1s2xk7OhFee1Xrt~_Yme9 zdB2F;S->HX=DY*!)Ugn&v5uK7t3I7M-_Qg*Z?4n|{3r;+?S6CG;c<=j^^EirP z2Ud|6ZI#4_wbqjq=1foAb{|8Y){8Xw_%O^$C^k}|s7(mo4bkwT{ zJ`LwXSNvUrpreCT>a&)q!};;Z*Rg$gxb^ypb@%W!BIG4EB{hzB+%a*y-9A3&vbe(>t{<`U=aLKE;9(_H^j_w7-%nHfBKVVa33EzC-6ME#K|TU z3*clkZk$O@+jSY+PDvVENHok8{k^H4qD;AnOr9Q7d5C0Y2u>g5);oZAki^EEwMgxc z!RwN_F${!M46F_UgoCoJgS>OBp()YBgEkln*F~rQP%Tv(v z$z}~j^@S10EAVb~Gk@iGKrwq!=(UFj&nv`Jt{@{fz3a*!B+B*u1!K14xQz#kWMyTI z9)JDE)eAHPeC`hPPAr_Q8?dw5rcJwe(J10EfpFhn65hkTj`IS_8Ktc0AP-=3Z$w86 zE<0OE`dkxC%KwP>P$!NmoadxfaJk(wbe={so@MdkJvk@QEJX2q9f^$h0_DBHc8pTz zCsg{}O~3Bo`!j&!{GBLM^xvyvf_M=enl(Xa_}ut0#7vY4{H)c&riBY($9`LPrhWIUB}sH8t1t zpmfTUFy^NuE=g!*ZguU{K?ASGYCtn}q?QPqOL)<(MGtjGkf}RE7OT!!tfI^vIVQPE z4t92V6exk4M5fdGKS=!nI0_+0kMrJXyfV&p%<`xOEhZldgVjp*9PolrwqqUwpb49? z&ZQ1{64-Opwz)y;(0Wc}2=}G$5xy{E=LDO%2LZU)4tuK}JwSVx2Z|{|0@y`lHbsK0E~5J! z+G<~9>&AS5I;@ctajPGaGCp25s!IpJoeA?0q5-eAJA##`r@aWKrSX$5P$SX{S+Va> zM!DS@NFwY73CLjw8Fhpl?EnXA*t!9oMB;oYr%Pm+{J&D<9Aa%~5fOYqJnU|QyZjlI zCEzE^{!Nh<`-(~4>({TZ5_{E;$D;OlZKv!9==8s8-3*!$2yMG~o;qU_$-#kniy_eciuv zQ@7N(^wF&joUq4A@+e=D^5`*AbU0gXw;vlyluxP~NKVQ@k`vXW=5MuF(hM8^!%MXU zG?m&6gAfq#^;1KjV>AH_Fa#tIP~}fdhMle}IDmplRMxEt`cojMGk2<(<8?RmIPP*} zyJ*}bI%LAKVbFuJ!+Xi7Z$B_}is*eCQa;IFJ1+Im3dx%3x{Bz35FdwQe=7e{)A-nv z8FM~?*Y`A%W{-Gmm*i3xFzdjxzNjog9#lY>&Qxit+{QR^-X{J?wGJaGfJCaY3nph6 z{@!2WH3-j6tS6`3P7dQj2uBOFB2-RCmPx*VlJ0ScBKHkaPI;0%;?vmyz2jv?x^%Si z*X#^70)FzG5Fl5m(+P3|v_7Lub#}^4tmkUE9WU8JE!v6A*<)i~-t&g9g7*t>sU8xp z9z*WTXMfIw=G?>4gHQ$$%0m|`0>E{UZ(X)zfwb_RW1Oesg9k(F){zEL>!%l6D;!91 z!vVq6>`x)2WE>_n6Y(sa#a@_m)ykC!Jm3N2Jf8qzNwiz+gkFY-(+_GakkFDLvuTkQ zy1bOD(rPK}9=wTJRHP~{MVCENj1aJu<9c(h0Vr2qp~FGj{AFc5K&is+rvJ*Yt4kuV zihV^z`FBr}MI6(KC4E|%lP!q=uT?TKYC;Q(GO+!K0Dop93$qEtwny3gZ!Sq=8Ng1E ztnasfUqMh?bB(}FT57~2RXtT;dKex&a5Q#+MF95-pM}NHY;(VIF}rGuq6`6)WJAEsSa*8C#w} z0Y8jS0cUZ;108UB8GZ3n-UKuRUcW>`RiFE)Hooo`{uQCjRND-NrJluEOE56kYs?i3`JUbRZ-N%p71 zvOBkLe{pV^YMMgEHx#gm2PKwbU}qCsH|S~=LTH#{LM(EmV%#2XjGzJQjXtrGHK>L zGL?ny-O)UOGjJ@k_1BGjVsoDC@N2-U=R~e<3-``B53ZPSR z7&!$++t6WBdlUp;OpCFhTexn^(RQO<_)iZ63;=)xntj9dW$*VwG?_{)$zzh{e#(6#MGm7s zPrv_wc3sk2CV1g6J& z2qR2fFk3e$PDHH+SdqQ^tirdK+ed^Gl7A3gJq`6oU}@_4ShfE=Ntj72xsN>qMFWgMOb ziPVD^55pYao>oud4vA31RhYqhE{zLBx)0#^c1gOQgg1tZy&;^dp5a0e(1Tp%v8vvN zKwe!sz+*_sn-6@`K$_m-s{tSUXnS0&)B-9ZDkR+C$M&-&M>H6JouWkn7fGu&;GGk< z>0pu3sWC6O9grU}A~-YT<8RIBAEMn5;Y4${j%L1Ho7OPBK=UaKb~x$Lj31I-h87#O zEX|8gVrt>Z9vKiHexZ&ds;t%w=}P;Y$LrgEBYf5(s%(Z(-Y7?}DBA|fO1Kr{c#^rp zeUv*rM2|ZhwSDRI=mEl+8Mg#W1esME4=JD5Bg^TCM=Gtpzxp5;2i5sTb#f%yPg#vDSfOe2Z12~B`QnE>`rZ=Ludq$4VH4xwu-9~pO9%&Hm5;d zx@2#^GEs`GAyS_5D1+(o+~V98``oHy6KWS9=n%dYzy}g{afHH!p!T~a*t*}ANx7S3 zUn8Zb7>)kq z-NQbBU0C8PEXi~pKhJ&K2YZ(*M&7zYbz(W*N?F(S24@>!6d*&&8ocCZU z05cU*@5Xlr$y~5YJvo;M5!XN97~=rg1>bP6vF(HFM_{={BIOWJ^!s^BhlcH!uQIDniCM&J47MeC&5#I1%f{} z&lpKP^ItSL!}y4gXiuQb^)wDGz{;LKU#&fo)WoUv*YqgJ2ch%PTi?Gb2z91{(@?NP zM;~G$(MheI)^v@TV9^$lo!34CCRT6}T*Ug+4G81IqKln5M-N5`YGSZBvIGJPZ`}tw zB6L|#IyhgbyCTX?lA)BpKmT1Nc1Lj5@8hQFx&U@r-QP!h)a-34^aV3a#IJAk2BqgYi?*wvO4kWys*S9O8uY@;ZkqJ`! zIVD4j888QH3qpI@+k072co}J|WV|+yV?={ozjx)IBB8pk>3g#{M#RizI-~zVJ&u8a@1y^VG|eTIe1BHYM+}SXdRrj^tK%oV+IxZVhl06j;WvK7MJmCkM~tZWT%|wH z`?+(w-A+Ggit|!a1gSt89-B8F;0iDUt^Gv-N&u6qB7*Q&pE3FN*vMLDZx;T{PUDmn zajJi*o|r;Yk0i|^NIXKzD`m(R-e9P`dxDxF6^e^CMK)1+J1jJtqY@UyV=f_wysF z`*Bfb0y&~Nt6d`bxURe8>wp&!Mr6hz_y-#QHoqG(JWX@pb_T8*#_R#K(g6gAx}?vWLvHKRb8~YMAOBj^&x;aT|L)YQFH(J> z0PL_${L3r+f1~#^*O1hk@69*8VZcED3C@A3R1p+xuerl?Vj-$hggEINUEpJ&0+k^# zVt?Ly0IGVh{ghVSHLzcDssSyD*VF5~11JJIlU$y2XAcj)O& zBFT<{$ray*GOwA2Hs63O1XgHORr72eH#*Tr)QsK>j1E* zlgQBxG4p>mq^BjKp8>ol1SH=;R3esh9;0pV`W8s)T_-1gH~`!|M6@SVcGSkUelT z8SECSC6QLQmB8+M?}Ynw17#+2!N|%^klG(=UJQbdR^SmyDT7x`EMPODP@3=;XR;?_ zdn_1%=2MLD@B`SXscVhwFCwXR5tPFjY5Ls|1i|?_B%+-X!a)u25Jk<@00tnJtlcfq z9q88r)D)94yH*5TAqoinx2E6t&j{^Lflo^uO6nuyV?Xb}8@|PP!U`mcg7a&PVl=@B zY9kms8^@92(F}FRfddCvNB%~d_{jn4D6@ZpKqoYs4o~`Jd3mJ}m5Hx)Ls~~rAk*i| z1$M?av=O&~auN|1MiZfZmt~QVgUjRy?*AT9>)}ZI&|!FgM5zV5Bmy5WivhuyLD4H7 zLU?=v8i5A&%%MrNIt_4EE5TkX>V$I&=)#KZV&obk3{PiuckJL&@gZ3M8qIWz#}j~) z1W*nZAx+;|PN!oa9iUq{<)mW-r4H(uQ{7+J@pL3}=r3O)Ji4DmliG`{TKd7_{GzAu z!}Q4gtzAquG%_6SJ6Z(kU6uv&2r;W|WE%fZ0`?0>r`0-6yCRzE-%9k3NW`oJb+7Y@VB8uGWwOp+{B95U_jl~7Gm z3H?JQ5O9eAZSjRWqDXaqGCCQYZEf?z;?p}evDC?dc6iO^8W(ST6^ zMGZ)%{)L~B!inw%Z2NYA5^4n`Xe8$&$sXIzYP$~%N86Yy$;=rJUZYzXSi zG_bIWqyHqpA_SvQCGbgz{-$;@)@HVfk8hU1BhhrwRzQ~2=AFZVi&%+MWt=}FtxuH@ zKN)<*4I$Bh9Fcp#sjA^rjd$*R1zpO&KgD;XN0V&2Bq3Q-pHp%1(HTnxB}ayr(K`*5 z#k=w3v#6Q}GC>-~ku|h3c3$;Ekq3mrSW}m9r=ZK0YH=pN#R72OR=?DiS&JTy#OQwy zCNaZ+v4cY&iGp@;am(7}qVKBryj$E=9L}FU2xeIKhSm96tEy7k>rV>O4d{yp&>bv zWfzr=6gaq-jEkqnSdmglc(@;&gzdW42-;DcPX1u!wIj5ivp%pMv771{m^)se#t;?v zN2q>BTrC|jwxLMP5B7J zGN%ZsjE$HCbZ8snLbwp>7>1*38j?!;WA*EHaDBO;zQLLn9*()yK{~al3=Sgq_>ey1 z_le*RO%8VpNv}txo7$h=3ma}#abTBDFjz`#-K2>RIr(dUglZvDx0_LbnIwuF7kuX3 zDB6cv@?k>V5~;#s>xMC72)7L56Q?>rABM}aQaB9S0&7lDW-8D_iLjQoHl_pKKkPC5 zqva}NNq&qasexLQ2TF5D5;{rkj90!MtyQps{|F6FMtcsJV#cjzlnqi-eaJh2Rj^v~ z03vI2b$Wo_5zObM>CR8akz;H^$LLjhki`p()44r>vaQQV02*FKuG$YR;=wQ&VxA}0 za#c!8Q`4ir?VwEyT6nXQ@>raaq!iJ0Pd5Tu9t8dsyiN}d82WDLo%QTHNGDt=av9-_ z13zYb@LK{FQm>8xHilHbSH|<_9Hb((zV*wn+;JfDQ zz#mIsY{bEJoM&b~wZ$#;KG@_>`7e2XRhWIvuMg^aAACI>NVZ_Wl&kEngEXOGT<})1vr&;a1k? zSe7o|=IHnXLr-`kDldF&-~iX|t&iYz%cmJR-nmrg*~k@CMGAskXEZO=-Qv)YW(dAT zxO_#ysRj->z}I4RP#V?;@0a$HqPzvj$2q_sMuUh&0>eFxz2RGbUVLe@kM%e$EG#Tb zEz1rGcigFPd5Ut~UcpS+w4j7)E28#wRh-CmqingIMOw6X3mpo(FRyy@Gc4GEgDiO2 z&!XOJbh{2(zJJCSt_R&(S{T1O;8d7;`!=2YOqBdgqz&jIaChl++5D-bF8>Y|X~E(x zX#P!iKHYnu^5%f8+T?X?+ARgLY1tp~>#QsE;-5tucrW>U$dY<8RJ8#v)-L@N|KG)* z$kCdw!qIx)NAq1UFhv~mV2es1CxZ*bV(Pd$@aeI!I{gouEYSa8A33kBQ2a@pee16_Lu=VsQB}hDL)KxzC$KMAmP?FQ;BR`$50}Q@5FotIVa0 zg2~C1f?pJd$%*N*yl$_gkTDID*EzGak%OCyYk!+g<<_3dd-c)9X;;Y{%0LpkcT3c% z_(whf`@rRJlKbewaU z+5i+U=65BTDgg|_Ybh0gj~6X~rt%-Jw$v97`!rtT+0)zG!IH7@^%i0OU{UI#>tch3!?t<){tB zTRvOPfHKPGS|Olo`@{FsWc&{6NgcsD739SR z7p%Eb!)2|ZQ3gI7*$0P}kea9dUU)fpkWn@})yAGJ+jrM=g|mTx*j*_lSjhLvIH3Sj zH@9EF-Afzy9k{SOAEsHG@3d^&EeHIl?L-+%W$-YR*dz!KFRhU{i6=|+M@@d)Ukle> zHsbOhE8~-sm3_f|S-^M;9n2G+)5CR@pGLaxB(ya&tab)c@E3VYMB~Sgr+z$a0vTQ9 zd~O-*R%Ix{!38&k@mFx?(7sJUF$$I0$06{Cgdmrc>a$NXqmSmYD)6TlPLI7*b8>K? z$-m#;oi1eo203u>Mf&iQs)TToV>YCqZlWi%*rS(i8;7i2E$QOvoLjeNkF_~?Jt)eK z?i-`Y)SSQ$8nBX$q5GhVAU>*eUCcz~ZofR}G_P}Ek%3$taN43*bwe+x4LqGYhz$HL zBAoB%kyqd<+aEu^jd|;j2X6`V;+*@}u{0?Q&sGm?6C5JY9F|ASq29ZbPu?}!Y@frz zr>cBDx18-jtb4sKn6`c;&t86i&t-l3^y%KO)>yH6s_RLoB zsmR{8v&`tde8*w?`o_bz`dfuX>WK_tk8Uj`uXz0%ZG?^h28NC%_kTx6pvR%Gdn4O@ zfSD6PG;kq5%e#y6YCvwg#lz4@@w%Mj{*q@tYmKe%=H#gLq$Nl5kKMgr<4S(cLVmt@ z>{r5U3D9a`-FKi4PJq<$g8M>~z!o~VR&`K}>RXpXgBj7WQ2*&(OA+}(C^&@cwjE(i zy^&lU&|NJ)f{(%1#MM#nBBv?d9pOLK0dI60Gi;?2Qjy4DlzAmbHW$)OEiL^!qqNtT z*Mo@OLZ1H+mz@&7o1WfHzOO#9<;c~8Q0Xh2SMS`~#9`_6|vdT}EMIux{xZubETb`Wl)F_EyeY?O0#VOvfyCxV%S^2SkIlhw|h z`zISXz;qpdx2hYeO98jo;0W2n49H2*_ayN5T&tHqM@Oj{)RVs8R%}$=LI*!iDLs_; z#M3!sElTIfTH;EE>-Bv5?rHBiMoP($Ra)!ZssGTPh7JX@6HTS@u{sHEHHwTs2qAwk z4LSXKcFATP@vwyPSGM5QuW|0w!5?Ia)PpX68XF1~PyNt=O{{IhxX|ejZ~WxW=XS*$ zh{t-pvt;+>&`rg!WEb^hKoO(U6T`xT3pBYBhwC1I{9OXHG$2b>fm1)-5(syvB+8{R zQ|et~V;Ib3Bdk&AKMO1*dQs!*|~!tEpnFrUzx>%1|m;I_?PeP_3L>Y;P3VkQX) zK`3^exO+MeQ3Zb#E(@!G`;X=k*pgjtsrRP1I?xFiFjC`CBvyLx#IyOwW{%FG?DWWr zPGXYOGqF3ddC*1Ymyg2mf)lr;P3O#zJq?SEh3gPNBPVZDhFVtSP@|2wathi*9z2|? zN|%b+k%~qKkK%P^JNRZV!q?J~hZa>oS^}07ASylaFgP&%2x-}z-6LI((~ujUY;JlI z6RSfm>#-%fp%jVJ6+jyYN{*SxpO~Gw>*%6lUA%!8hU?E&u)eRQI zdm_6|WbH$@5);2@t4$cPWrG|TR6^lUv;%;tkifVyz;l0r8V%^N8~c4vxoxQ*s} z_Yn-rdH3OA4)nih6BUXn1G@fK7 zCS)b2(0HZ%cj@eOO1&3Y-OdHh@82P-3qSFa#8krm+o-CZdBJ5}?S|l?ICp0I1d{2* zWAD63&fpC?&m=;pLf?$MJMhWG|7)-*OX5np-Nj*Fy)4XUy`!bSiJPX-SjKA_w1e#p44*Kv0WDFGx`LX%tx z(en#=y*}?vm@r~a1jhL2qpP0LvA$x978AKW!xzAmwO5Jq>q)=Uyb(MSTk?fTrhtGS zl}`UisUN$NbE*p=i@wFDzVlL1r@UKMb!&h?7U$U({2N;+K~{B-2nm=uO9dS6zm`L! zWPg2;byC+Ncp|#`DH4U9D;bL+)Ii61SR)HA~J|9SvI zJxE1|N%gy-S1}?lbFjId|F?x0ZNWkYcT!aSn>$#@*`8vHspFjDGwR@rY{OkNFu$^i zci$+G6`PC8&Qesl@w1;+^ZDkZ*#l!cvINyKQg^6nEkIyQRPyR(ymsMI4g>uf(;3vZ zR49=nsuNau@p|h?r}2yscY!%ng)II@YBn$3#Y(Ym9YraUv-$i5UInFzmV~V^BdFhyG)`z4vLDD-Fe}{im3I2cn??e782F(ICy;_Q4 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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 (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/gui/draw.clj b/src/main/clojure/conexp/gui/draw.clj index bd9e6deb6..10e729ecc 100644 --- a/src/main/clojure/conexp/gui/draw.clj +++ b/src/main/clojure/conexp/gui/draw.clj @@ -28,7 +28,8 @@ (:import [java.awt BorderLayout Dimension] [javax.swing BoxLayout JFrame JPanel JScrollBar JScrollPane] [conexp.fca.posets Poset] - [conexp.fca.lattices Lattice])) + [conexp.fca.lattices Lattice] + [conexp.fca.protoconcepts Protoconcepts])) ;;; Lattice Editor @@ -120,6 +121,10 @@ [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 @@ -130,7 +135,7 @@ - dimension [600 600] " [layout - & {:keys [visible dimension] + & {:keys [visible dimension title] :or {visible true, dimension [600 600]}}] (let [frame (make-frame layout), @@ -142,14 +147,24 @@ {: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 [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] + (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) - value-fn (get map :value-fn (constantly nil))] - (apply draw-layout (-> lattice layout-fn (to-valued-layout value-fn)) args))) + (draw-poset lattice args)) (defn draw-concept-lattice "Draws the concept lattice of a given context, passing all remaining @@ -157,6 +172,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/layouts/layered.clj b/src/main/clojure/conexp/layouts/layered.clj index 47f2034f8..b25383767 100644 --- a/src/main/clojure/conexp/layouts/layered.clj +++ b/src/main/clojure/conexp/layouts/layered.clj @@ -11,7 +11,10 @@ (:use conexp.base conexp.math.algebra conexp.layouts.util - conexp.layouts.base)) + conexp.layouts.base + conexp.math.algebra + conexp.fca.lattices + conexp.fca.protoconcepts)) ;;; Simple Layered Layout diff --git a/src/main/clojure/conexp/layouts/util.clj b/src/main/clojure/conexp/layouts/util.clj index d7db70dc7..9e042138e 100644 --- a/src/main/clojure/conexp/layouts/util.clj +++ b/src/main/clojure/conexp/layouts/util.clj @@ -11,11 +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]) (:import [conexp.fca.posets Poset] - [conexp.fca.lattices Lattice])) + [conexp.fca.lattices Lattice] + [conexp.fca.protoconcepts Protoconcepts])) ;;; @@ -120,7 +122,7 @@ are directly neighboured in poset." (fn [poset] (type poset))) -(defmethod edges Poset +(defn- poset-edges [poset] (into #{} (filter #(directly-neighboured? poset (first %) (second %)) @@ -129,10 +131,18 @@ :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] 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/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/layouts/layered_test.clj b/src/test/clojure/conexp/layouts/layered_test.clj index 5a4034db7..7c06d4d98 100644 --- a/src/test/clojure/conexp/layouts/layered_test.clj +++ b/src/test/clojure/conexp/layouts/layered_test.clj @@ -9,6 +9,9 @@ (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 conexp.layouts.layered @@ -17,6 +20,13 @@ ;;; +(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 [poset (apply conj test-lattices test-posets)] (let [simply-layered (simple-layered-layout poset), @@ -34,7 +44,19 @@ (forall [[_ coords] simple-layers] (forall [x coords] (exists [y coords] - (= x (- y))))))))) + (= 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 [poset (apply conj test-lattices test-posets)] From 1388ff3e47d76af591b24b3c647b1524deb7a4c5 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Sat, 5 Nov 2022 00:31:05 +0200 Subject: [PATCH 065/112] 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 --- src/main/clojure/conexp/gui/editors/contexts.clj | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 {})] From f2f7a8c831a959fbe94b7998db070063f2dc204f Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Thu, 26 Jan 2023 11:48:57 +0100 Subject: [PATCH 066/112] Updated copyright notice and added reference request. --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b87ea6e2b..4465ccc72 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,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. From 9b98999d250f5f027d481853eabbd29efb91df35 Mon Sep 17 00:00:00 2001 From: Johannes Hirth <58222089+hirthjo@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:50:55 +0100 Subject: [PATCH 067/112] Update Common-FCA-File-Formats-for-Formal-Contexts.org (#102) --- doc/Common-FCA-File-Formats-for-Formal-Contexts.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org index d67f96f47..af45c44eb 100644 --- a/doc/Common-FCA-File-Formats-for-Formal-Contexts.org +++ b/doc/Common-FCA-File-Formats-for-Formal-Contexts.org @@ -190,7 +190,7 @@ Conexp-clj I/O #+BEGIN_SRC clojure (write-context :named-binary-csv context "path/to/context.csv") -(read-context :named-binary-csv) +(read-context "path/to/context.csv" :named-binary-csv) #+END_SRC #+BEGIN_SRC python From 23cfb644dac057027c30605d3b47b1f0244b07f1 Mon Sep 17 00:00:00 2001 From: maximilian-felde <43608455+maximilian-felde@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:53:49 +0100 Subject: [PATCH 068/112] New context randomization (#103) * added edge swapping and edge rewiring for contexts * added tests for swapping and rewiring * fixed function aliases --- .../clojure/conexp/fca/random_contexts.clj | 128 +++++++++++++++++- .../conexp/fca/random_contexts_test.clj | 26 ++++ 2 files changed, 151 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/conexp/fca/random_contexts.clj b/src/main/clojure/conexp/fca/random_contexts.clj index 6802fa422..342e3ca63 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 @@ -270,5 +271,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/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))))))) From 900e8dea8c994e48f9dec4c1e4214076b1cb8c82 Mon Sep 17 00:00:00 2001 From: maximilian-felde <43608455+maximilian-felde@users.noreply.github.com> Date: Thu, 23 Feb 2023 01:44:47 +0100 Subject: [PATCH 069/112] 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 --- README.md | 1 + doc/IncompleteContexts.org | 72 +++ .../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 ++++ .../clojure/conexp/fca/random_contexts.clj | 36 ++ .../clojure/conexp/io/incomplete_contexts.clj | 203 ++++++ src/main/clojure/conexp/layouts/dim_draw.clj | 4 +- 12 files changed, 2599 insertions(+), 2 deletions(-) create mode 100644 doc/IncompleteContexts.org 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/io/incomplete_contexts.clj diff --git a/README.md b/README.md index 4465ccc72..6f9636f0c 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ much more. 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) 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/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/random_contexts.clj b/src/main/clojure/conexp/fca/random_contexts.clj index 342e3ca63..a4ef21a2e 100644 --- a/src/main/clojure/conexp/fca/random_contexts.clj +++ b/src/main/clojure/conexp/fca/random_contexts.clj @@ -165,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] 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/layouts/dim_draw.clj b/src/main/clojure/conexp/layouts/dim_draw.clj index b2037e67b..cc83bd360 100644 --- a/src/main/clojure/conexp/layouts/dim_draw.clj +++ b/src/main/clojure/conexp/layouts/dim_draw.clj @@ -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) From 9f53c71913fa39b95516c388d9e426bec34e3a77 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Thu, 23 Feb 2023 02:47:02 +0200 Subject: [PATCH 070/112] 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 --- src/main/clojure/conexp/api/handler.clj | 42 +- src/main/clojure/conexp/io/contexts.clj | 40 +- src/main/clojure/conexp/io/implications.clj | 4 +- src/main/clojure/conexp/io/lattices.clj | 55 +- .../conexp/io/many_valued_contexts.clj | 48 +- .../schemas/context_schema_v1.1.json | 48 ++ .../resources/schemas/fca_schema_v1.0.json | 11 +- .../schemas/implications_schema_v1.0.json | 5 +- .../schemas/lattice_schema_v1.1.json | 71 +++ .../schemas/mv-context_schema_v1.0.json | 45 ++ src/test/clojure/conexp/api/handler_test.clj | 61 +- src/test/clojure/conexp/io/contexts_test.clj | 80 ++- src/test/clojure/conexp/io/fcas_test.clj | 39 +- src/test/clojure/conexp/io/lattices_test.clj | 9 +- .../conexp/io/many_valued_contexts_test.clj | 43 +- testing-data/digits-context.json | 13 +- testing-data/digits-context1.1.json | 13 + testing-data/digits-fca-2.json | 591 ++++++++++++++++++ testing-data/digits-lattice1.1.json | 1 + testing-data/mv-context.json | 1 + 20 files changed, 1081 insertions(+), 139 deletions(-) create mode 100644 src/main/resources/schemas/context_schema_v1.1.json create mode 100644 src/main/resources/schemas/lattice_schema_v1.1.json create mode 100644 src/main/resources/schemas/mv-context_schema_v1.0.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-lattice1.1.json create mode 100644 testing-data/mv-context.json diff --git a/src/main/clojure/conexp/api/handler.clj b/src/main/clojure/conexp/api/handler.clj index 7d0caeb2f..7016bf286 100644 --- a/src/main/clojure/conexp/api/handler.clj +++ b/src/main/clojure/conexp/api/handler.clj @@ -30,23 +30,18 @@ (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) + "lattice" (json->lattice raw) + "implication" (json->implication raw) + "implication_set" (json->implications raw) "layout" (json->layout raw) "method" (resolve (symbol raw)) raw))) @@ -60,19 +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)] + 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 @@ -161,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/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 9fd846831..6b237e070 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -334,7 +334,7 @@ (let [first-line (read-line)] (and (re-matches #"^[^,]+,[^,]+$" first-line) ;; do not read empty json context as csv - (not= first-line "{\"attributes\":[],\"adjacency-list\":[]}"))) + (not= first-line "{\"objectsn:[],\"attributes\":[],\"incidence\":[]}"))) (catch Exception _)))) (define-context-input-format :csv @@ -425,11 +425,11 @@ [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 :binary-csv format, object or attribute names contain \",\".")) + (unsupported-operation "Cannot export to :named-binary-csv format, object or attribute names contain \",\".")) (let [objs (sort (objects ctx)), atts (sort (attributes ctx)) output-matrix (conj (for [obj objs] @@ -621,14 +621,22 @@ "Returns a formal context as a map that can easily be converted into json format. Example: - {:attributes (\"1\", \"2\") - :adjacency-list - [{:object \"b\", - :attributes (\"1\", \"2\")}]}" + {:objects #{\"b\"} + :attributes #{\"1\" \"2\"} + :incidence #{[\"b\" \"1\"] [\"b\" \"2\"]}" [ctx] - {:attributes (into () (attributes ctx)) - :adjacency-list - (mapv (partial object->json ctx) (objects 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." @@ -639,12 +647,11 @@ "Returns a Context object for the given json context." [json-ctx] (let [attributes (:attributes json-ctx) - ctx-list (:adjacency-list json-ctx) - objects (map :object ctx-list) - incidence (apply union (mapv json-ctx->incidence ctx-list))] + objects (:objects json-ctx) + incidence (:incidence json-ctx)] (make-context objects attributes incidence))) -;; Json Format (src/main/resources/schemas/context_schema_v1.0.json) +;; Json Format (src/main/resources/schemas/context_schema_v1.1.json) (add-context-input-format :json (fn [rdr] @@ -660,11 +667,10 @@ [file] (with-in-reader file (let [file-content (json/read *in* :key-fn keyword) - json-ctx file-content - schema-file "src/main/resources/schemas/context_schema_v1.0.json"] + 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 json-ctx)))) + (json->ctx file-content)))) ;;; TODO diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index fb47532b3..3531ce854 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -22,7 +22,7 @@ ;; Json helpers -(defn- implication->json +(defn implication->json "Returns one implication in json format." [implication] {:premise (premise implication) @@ -34,7 +34,7 @@ {:implications (mapv implication->json implication-list)}) -(defn- json->implication +(defn json->implication "Returns an Implication object for the given json implication." [json-impl] (make-implication (into #{} (:premise json-impl)) diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 0c73a9c0d..5a83501f0 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -52,56 +52,41 @@ (apply make-lattice lattice)))) ;; Json helpers - -(defn- concept->json - "Returns the concept in json format." - [concept] - {:extent (first concept) - :intent (second concept)}) - -(defn- concept-pair->json - "Returns the pair of concepts in json format." - [pair] - {:start_concept (concept->json (first pair)) - :end_concept (concept->json (second pair))}) - -(defn- lattice-order->json - "Returns the order of the concept lattice in json format." - [lattice] - (let [pairs (set-of [x y] - [x (base-set lattice) - y (base-set lattice) - :when ((order lattice) [x y])])] - (map concept-pair->json pairs))) - (defn lattice->json "Returns a concept lattice, consisting of the base set and order, in json format." [lat] - {:formal_concepts (mapv concept->json (base-set lat)) - :lattice_structure (lattice-order->json 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 one map each for extent and intent." + "Returns a vector containing both extent and intent." [json-concept] - [(into #{} (:extent json-concept)) - (into #{} (: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] - [(into [] (json->concept (:start_concept json-concept-pair))) - (into [] (json->concept (:end_concept 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-concepts (:formal_concepts json-lattice) - json-lattice-order (:lattice_structure json-lattice) - lattice-base-set (map json->concept json-concepts) - lattice-order (map json->concept-pair json-lattice-order)] + (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.0.json) +;; Json Format (src/main/resources/schemas/lattice_schema_v1.1.json) (add-lattice-input-format :json (fn [rdr] @@ -117,7 +102,7 @@ [file] (with-in-reader file (let [json-lattice (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/lattice_schema_v1.0.json"] + 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)))) 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/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 index 1759c3edb..d4c3c85b5 100644 --- a/src/main/resources/schemas/fca_schema_v1.0.json +++ b/src/main/resources/schemas/fca_schema_v1.0.json @@ -34,10 +34,10 @@ "description": "In 'source', the source of the context / data can be added." }, "context": { - "$ref": "context_schema_v1.0.json" + "$ref": "context_schema_v1.1.json" }, "lattice": { - "$ref": "lattice_schema_v1.0.json" + "$ref": "lattice_schema_v1.1.json" }, "implication_sets": { "type": "array", @@ -46,6 +46,13 @@ "$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" diff --git a/src/main/resources/schemas/implications_schema_v1.0.json b/src/main/resources/schemas/implications_schema_v1.0.json index 86dd1f994..4e99db2f4 100644 --- a/src/main/resources/schemas/implications_schema_v1.0.json +++ b/src/main/resources/schemas/implications_schema_v1.0.json @@ -65,7 +65,8 @@ "$comment": "Further base_type enums can be added.", "type": "string", "enum": [ - "canonical" + "canonical", + "ganter" ] }, "implications": { @@ -101,4 +102,4 @@ "is_base" ] } -} \ 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/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 c155d35a9..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")))) @@ -367,6 +367,61 @@ (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 "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"))))) + ;;; nil diff --git a/src/test/clojure/conexp/io/contexts_test.clj b/src/test/clojure/conexp/io/contexts_test.clj index cfcbd165f..253a88e77 100644 --- a/src/test/clojure/conexp/io/contexts_test.clj +++ b/src/test/clojure/conexp/io/contexts_test.clj @@ -31,12 +31,15 @@ (deftest test-context-out-in (with-testing-data [ctx contexts-oi, - fmt (remove #{:binary-csv :anonymous-burmeister :fcalgs} + fmt (remove #{:binary-csv :anonymous-burmeister :fcalgs :named-binary-csv} (list-context-formats))] - (try (= ctx (out-in ctx 'context fmt)) - (catch UnsupportedOperationException _ true))) - - ;; fcalgs throws UnsupportedOperationException at writing and thus needs to be tested with another context + (= 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)))) @@ -50,9 +53,13 @@ (incidence-relation ctx1) (incidence-relation ctx2) (concepts ctx1) (concepts ctx2))) -(deftest test-anonymous-burmeister-out-in +(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)))) ;; @@ -63,9 +70,13 @@ (deftest test-context-out-in-out-in (with-testing-data [ctx contexts-oioi, - fmt (remove #{:anonymous-burmeister} (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 @@ -78,24 +89,37 @@ (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 (remove #{:anonymous-burmeister} (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), @@ -179,12 +203,13 @@ (deftest test-json-matching-schema "Read a json format that matches the given schema." - (if-not (.exists (java.io.File. "testing-data/digits-context.json")) - (warn "Could not verify validation of context schema. Testing file not found.") - (let [ctx (read-context "testing-data/digits-context.json" :json)] - (is (= 7 (count (attributes ctx)))) - (is (= 10 (count (objects ctx)))) - (is (= 47 (count (incidence-relation ctx))))))) + (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)))))))) ;;; @@ -234,9 +259,8 @@ ;; 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) - :adjacency-list - [{:object 1, :attributes '(:a)} - {:object 2, :attributes '()}]})))) + {: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 index e4fc3da1a..db97f4539 100644 --- a/src/test/clojure/conexp/io/fcas_test.clj +++ b/src/test/clojure/conexp/io/fcas_test.clj @@ -12,7 +12,8 @@ conexp.fca.lattices conexp.fca.implications conexp.io.fcas - conexp.io.util-test) + conexp.io.util-test + conexp.layouts) (:use clojure.test)) ;;; @@ -141,22 +142,40 @@ 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." - (if-not (.exists (java.io.File. "testing-data/digits-lattice.json")) - (warn "Could not verify failing validation of fca schema. Testing file not found.") - (is (thrown? - AssertionError - (read-fca "testing-data/digits-lattice.json" :json))))) + (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." - (if-not (.exists (java.io.File. "testing-data/digits-fca.json")) - (warn "Could not verify validation of fca schema. Testing file not found.") - (let [fca (read-fca "testing-data/digits-fca.json" :json)] - (is (= 6 (count (first (:implication-sets fca)))))))) + (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." diff --git a/src/test/clojure/conexp/io/lattices_test.clj b/src/test/clojure/conexp/io/lattices_test.clj index f9045a942..c8dcbd40a 100644 --- a/src/test/clojure/conexp/io/lattices_test.clj +++ b/src/test/clojure/conexp/io/lattices_test.clj @@ -33,10 +33,11 @@ (deftest test-json-matching-schema "Read a json format that matches the given schema." - (if-not (.exists (java.io.File. "testing-data/digits-lattice.json")) - (warn "Could not verify validation of lattice schema. Testing file not found.") - (let [lattice (read-lattice "testing-data/digits-lattice.json" :json)] - (is (= 48 (count (lattice-base-set lattice))))))) + (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." 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/testing-data/digits-context.json b/testing-data/digits-context.json index 5406a5419..2c593cc76 100644 --- a/testing-data/digits-context.json +++ b/testing-data/digits-context.json @@ -1 +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"]}]} +{"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-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/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 From c5b251ca609d6cb360f1b8b5265c1d1ddc7f602c Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Fri, 24 Feb 2023 16:28:51 +0200 Subject: [PATCH 071/112] 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 --- src/main/clojure/conexp/gui/draw.clj | 9 ++++-- src/test/clojure/conexp/gui/draw_test.clj | 36 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/test/clojure/conexp/gui/draw_test.clj diff --git a/src/main/clojure/conexp/gui/draw.clj b/src/main/clojure/conexp/gui/draw.clj index 10e729ecc..773a1f5e0 100644 --- a/src/main/clojure/conexp/gui/draw.clj +++ b/src/main/clojure/conexp/gui/draw.clj @@ -158,13 +158,18 @@ (defn draw-protoconcepts "Draws protoconcepts with given layout." [protoconcepts & args] - (draw-poset 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] - (draw-poset 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 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..021ab3577 --- /dev/null +++ b/src/test/clojure/conexp/gui/draw_test.clj @@ -0,0 +1,36 @@ +;; 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-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))))) From 24dafecd1add05ca311d7e0ef66493e93cc96dc6 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Fri, 24 Feb 2023 15:30:08 +0100 Subject: [PATCH 072/112] Version bump --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 334520e19..8f581d361 100644 --- a/project.clj +++ b/project.clj @@ -6,7 +6,7 @@ ;; the terms of this license. ;; You must not remove this notice, or any other, from this software. -(defproject conexp-clj "2.3.0-SNAPSHOT" +(defproject conexp-clj "2.3.1-SNAPSHOT" :min-lein-version "2.0.0" :description "A ConExp rewrite in clojure -- and so much more ..." From 789970e46dbe255d54b9e9da4e79ef1bca0cda8b Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Sat, 15 Apr 2023 15:17:31 +0200 Subject: [PATCH 073/112] Feature/fix flake (#108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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) --- flake.lock | 6 +++--- flake.nix | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index e1dc2b00b..b2fe9a0d3 100644 --- a/flake.lock +++ b/flake.lock @@ -100,11 +100,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1663760840, - "narHash": "sha256-ym5Iycs5H4cOaLfE2/vC0tsLp8XuBJQIHGV8/uXSy8M=", + "lastModified": 1672580127, + "narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9bdbbaa634aa666eb6a27096bdcb991c59181244", + "rev": "0874168639713f547c05947c76124f78441ea46c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 976ab4827..d4ccb5ab6 100644 --- a/flake.nix +++ b/flake.nix @@ -80,7 +80,7 @@ }; devShells.default = mkShell { - buildinputs = with channels.nixpkgs; [ clojure-lsp leiningen ]; + buildInputs = with channels.nixpkgs; [ clojure-lsp leiningen ]; }; formatter = channels.nixpkgs.alejandra; From 84432a475d2c5fd1628c234f07dde5b67d599c84 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Fri, 26 May 2023 16:23:11 +0200 Subject: [PATCH 074/112] Updated depdencies --- project.clj | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/project.clj b/project.clj index 8f581d361..6bbf76949 100644 --- a/project.clj +++ b/project.clj @@ -15,42 +15,42 @@ :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.3.610"] - [org.clojure/data.int-map "1.0.0"] + [org.clojure/core.async "1.6.673"] + [org.clojure/data.int-map "1.2.1"] [org.clojure/data.json "2.4.0"] [org.clojure/data.xml "0.0.8"] - [org.clojure/math.combinatorics "0.1.6"] - [org.clojure/math.numeric-tower "0.0.4"] - [org.clojure/tools.cli "1.0.194"] + [org.clojure/math.combinatorics "0.2.0"] + [org.clojure/math.numeric-tower "0.0.5"] + [org.clojure/tools.cli "1.0.219"] [org.apache.commons/commons-math "2.2"] [org.clojure/algo.generic "0.1.3"] [seesaw "1.5.0"] - [reply "0.4.4" + [reply "0.5.1" :exclusions [org.clojure/clojure clojure-complete com.cemerick/drawbridge]] [aysylu/loom "1.0.2"] - [rolling-stones "1.0.1" + [rolling-stones "1.0.3" :exclusions [org.clojure/clojure]] - [clj-http "3.11.0"] + [clj-http "3.12.3"] [clojure-complete "0.2.5"] - [ring/ring-devel "1.8.2"] - [ring/ring-core "1.8.2"] - [ring/ring-json "0.5.0"] + [ring/ring-devel "1.10.0"] + [ring/ring-core "1.10.0"] + [ring/ring-json "0.5.1"] [ring-cors "0.1.13"] - [http-kit "2.5.0"] + [http-kit "2.6.0"] [org.apache.commons/commons-math3 "3.6.1"] - [luposlip/json-schema "0.3.4"] + [luposlip/json-schema "0.4.1"] [org.clojure/data.csv "1.0.1"]] :profiles {:uberjar {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"] - [nrepl/nrepl "0.6.0"]] + [nrepl/nrepl "1.0.0"]] :aot :all} :dev {:main conexp.main :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"] - [nrepl/nrepl "0.6.0"]] + [nrepl/nrepl "1.0.0"]] :javac-options ["-Xlint:deprecation" "-Xlint:unchecked"]} :gorilla {:main conexp.main :plugins [[org.clojars.benfb/lein-gorilla "0.7.0"]]}} @@ -61,4 +61,4 @@ :resource-paths ["src/main/resources"] :target-path "builds/%s" :compile-path "%s/classes/" - :java-opts ["-Dawt.useSystemAAFontSettings=on" "-Xmx4G"]) + :java-opts ["-Dawt.useSystemAAFontSettings=lcd_hbgr" "-Xmx4G"]) From 766dfcc9315f685ab69a18c3110a6cda981fba81 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 16:35:11 +0200 Subject: [PATCH 075/112] Update deps-lock.json (#111) Co-authored-by: tomhanika --- deps-lock.json | 614 ++++++++++++++++++++++++++----------------------- 1 file changed, 332 insertions(+), 282 deletions(-) diff --git a/deps-lock.json b/deps-lock.json index 8370b4317..938a0351e 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -13,34 +13,29 @@ "hash": "sha256-lIYWiawdZ+UH7ryGfP+CLmSB5jojpcgU4xfhHCO2IZY=" }, { - "mvn-path": "better-cond/better-cond/1.0.1/better-cond-1.0.1.jar", + "mvn-path": "better-cond/better-cond/2.1.0/better-cond-2.1.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-W9tqaXKucHYAFzcKbeHrALJTeg7NJvpMeWSnyL7ZUGE=" + "hash": "sha256-xu/E7NU0+oVCiFIXtX/hsyX7ei6vozTKwjUBfMtuQdE=" }, { - "mvn-path": "better-cond/better-cond/1.0.1/better-cond-1.0.1.pom", + "mvn-path": "better-cond/better-cond/2.1.0/better-cond-2.1.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-N7drmN0Q4TANv9wBMrq33pS9oQJWhk2lKUudScqubzs=" + "hash": "sha256-s7nq7nG2p2PmnQJCpwAHR5w6wVz10Pl4Y3TSH2DB3wQ=" }, { - "mvn-path": "cheshire/cheshire/3.0.0/cheshire-3.0.0.pom", + "mvn-path": "cheshire/cheshire/5.10.0/cheshire-5.10.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-ig93XiGeW+XK2AYO2WLkaAlXc/9mtUTAOqwouGREUhc=" + "hash": "sha256-c6zJFpWQejJApipvINBXBlDGDlNf9vehb6Na5lvcT9g=" }, { - "mvn-path": "cheshire/cheshire/3.1.0/cheshire-3.1.0.pom", + "mvn-path": "cheshire/cheshire/5.10.0/cheshire-5.10.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-J9R5EA3poigtW7yovudwDCmaf0EDqXNOeGRKL0Yu9/o=" + "hash": "sha256-l3PHeIYLYeUXTVeUfJQIGwaJY6aaL7ABmhGpGuN9l8w=" }, { - "mvn-path": "cheshire/cheshire/4.0.3/cheshire-4.0.3.pom", + "mvn-path": "cheshire/cheshire/5.11.0/cheshire-5.11.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-mk9BzL9RixYRSli3HXnSQMtKwn1dKFGmBfug7SnRQ7Y=" - }, - { - "mvn-path": "cheshire/cheshire/5.10.2/cheshire-5.10.2.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-F7vNJXvbviEm94QkUPSb4DoDXCOgTwje5vke7TW9/b0=" + "hash": "sha256-63Enn5Ak0AUpOBdVZKScWG8P/QAnWaVNPmIPS891Leo=" }, { "mvn-path": "cheshire/cheshire/5.8.1/cheshire-5.8.1.pom", @@ -68,39 +63,24 @@ "hash": "sha256-2Psx89fYmDr18J8kk6KO0ZKFxqxyxZ/Y1/+9B212gy0=" }, { - "mvn-path": "clj-http-lite/clj-http-lite/0.2.0/clj-http-lite-0.2.0.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-kx2UO4XajuKn4jwTbnIOu1SoRdTMB5OQZjlXD9MJ+1A=" - }, - { - "mvn-path": "clj-http-lite/clj-http-lite/0.2.0/clj-http-lite-0.2.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-kuFNujSx/Jq2I1BH8Xd3J+oFpOk+mvnV6pnyGXW/H9s=" - }, - { - "mvn-path": "clj-http/clj-http/0.3.6/clj-http-0.3.6.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-sJ7kQCymPL44RmMhgWDZSVQh60F4Z6PHbcPF3Q5xcwY=" - }, - { - "mvn-path": "clj-http/clj-http/3.11.0/clj-http-3.11.0.jar", + "mvn-path": "clj-http/clj-http/3.12.3/clj-http-3.12.3.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-ANUzcJkhycHXeCf+WNgzJj4/PckxGE4oxOH+/V0HLSI=" + "hash": "sha256-OJ0pdhKo6KzGbF0NZKbJO99lDe1iXG9q4Wk7kzaKkaU=" }, { - "mvn-path": "clj-http/clj-http/3.11.0/clj-http-3.11.0.pom", + "mvn-path": "clj-http/clj-http/3.12.3/clj-http-3.12.3.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-LgVfywRWSylAY7MrJ5ubkfImKEugxQXLUr797zPQEAo=" + "hash": "sha256-Jx0VRYS9iV/S4w9jEVAE8ce4xySq6tLLKpl8XFWEBfA=" }, { - "mvn-path": "clj-stacktrace/clj-stacktrace/0.2.7/clj-stacktrace-0.2.7.jar", + "mvn-path": "clj-http/clj-http/3.9.1/clj-http-3.9.1.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-aotYq1L3xjXPWHVl1ZaWijqlwqzvFeexQaJKyMnbEFE=" + "hash": "sha256-auQ1vOjy7MN5WZIjmb4xpuJ3k8xTZEUQBkIaUuyOKFE=" }, { - "mvn-path": "clj-stacktrace/clj-stacktrace/0.2.7/clj-stacktrace-0.2.7.pom", + "mvn-path": "clj-stacktrace/clj-stacktrace/0.2.8/clj-stacktrace-0.2.8.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-zxhH3yxpzzvoW9cjUKCDb3SuVnYwshF1lnw57q4Ipyg=" + "hash": "sha256-Kr0ARCxfN1I92ZgWggw0RUVsxahotJuiNLt7LFDjSYA=" }, { "mvn-path": "clj-stacktrace/clj-stacktrace/0.2.8/clj-stacktrace-0.2.8.pom", @@ -158,19 +138,19 @@ "hash": "sha256-oDZXy9TnBD1AcQjaP4cFmkyBr/dBueFWp01Sjci3Ma4=" }, { - "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.0.0/jackson-core-2.0.0.pom", + "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-VagS+XgNophkzWqex3BkvXOnpR7vot3kcwvLCHTwmpU=" + "hash": "sha256-TEHyKkj267KHUrrrbSW/CbpP8K2L+4JlDd5EiSi52k8=" }, { - "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.0.6/jackson-core-2.0.6.pom", + "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-TP8H6SgWSTsCAAh+PJvv62S+VZqF6L0pejcbFBGWbFo=" + "hash": "sha256-Mlo8lNkkrw9s6sykDp6IehiIZMFT+fw0zpxPAT2ogo4=" }, { - "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.12.4/jackson-core-2.12.4.pom", + "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-B1AFGuFEwR9yMJdB540BNNE7KU9M5PeQCjogNWQyQxE=" + "hash": "sha256-PTyORDm/LbQ3YKYSB9/JV62ZpZt1LMhHaZC0neAM3vw=" }, { "mvn-path": "com/fasterxml/jackson/core/jackson-core/2.9.6/jackson-core-2.9.6.pom", @@ -188,9 +168,19 @@ "hash": "sha256-xWqygptlPMfKZIEGCV8GA4Vm2AlcAfIYE1XckHskT2E=" }, { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.12.4/jackson-dataformat-cbor-2.12.4.pom", + "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-/WGct8Jkztc9nxg28yBi901UmHuYym8kA4EPRiRTiM8=" + "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", @@ -208,19 +198,19 @@ "hash": "sha256-ljd1drO9v9kPc89b39Cx1NRpheC/7RqEVuP0NhCswrY=" }, { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.0.0/jackson-dataformat-smile-2.0.0.pom", + "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-iTdj0nXkW/84zCm7ZNT871MlKSIBXwGnCCXG4MEh+vo=" + "hash": "sha256-xKawR9uF1XiUo+tFpiv4MvOThtDvZbH4kvZqvQ1ifro=" }, { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.0.6/jackson-dataformat-smile-2.0.6.pom", + "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-I59p/oBCowU+d96eFdW3yECT/PzbpSf3uz9NjnQDizM=" + "hash": "sha256-UQrdLrPqpS5nWh86riCWsSziu7AuJrjyifFTgjNeBU8=" }, { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.12.4/jackson-dataformat-smile-2.12.4.pom", + "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-C51/GFtYIctOQ/Q6IbV9bv11jSbZ/6rBexR0BLAwFhE=" + "hash": "sha256-YkR6KghMq4L5Yb4fPulnJu+Wj+Q9qbb7rhPKJXEMzfw=" }, { "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.9.6/jackson-dataformat-smile-2.9.6.pom", @@ -238,9 +228,14 @@ "hash": "sha256-vadQqQnsL/gnrG6zMjmVlpN7Bcpu4DrZScJ3L2TIryk=" }, { - "mvn-path": "com/fasterxml/jackson/dataformat/jackson-dataformats-binary/2.12.4/jackson-dataformats-binary-2.12.4.pom", + "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-LM0+oMFZqAvlgQw2R3upsdNZ+ARcRzVV6OwTEKZZenk=" + "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", @@ -253,9 +248,14 @@ "hash": "sha256-MSo3eyxurmKp4LC2ahSDt63DomhRFExolvXZ+QH/vpg=" }, { - "mvn-path": "com/fasterxml/jackson/jackson-base/2.12.4/jackson-base-2.12.4.pom", + "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-GSGu6Fvrq/AW6lBYOrm0EdUQt3VY9Wgj3LS6V8W5f/k=" + "hash": "sha256-ctZykYdsY+GJa8fY/3mQM9OkuQKQIBEEiK+clzFe2Tk=" }, { "mvn-path": "com/fasterxml/jackson/jackson-base/2.9.6/jackson-base-2.9.6.pom", @@ -268,9 +268,14 @@ "hash": "sha256-QzaIZ9LYww3EbwzebLWjIL+5/XRjcovlAd5hVFJll10=" }, { - "mvn-path": "com/fasterxml/jackson/jackson-bom/2.12.4/jackson-bom-2.12.4.pom", + "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-6z5zvu64rKlVZDw4uZTdpvsM68MZZ2+A6tX7nsr/bj0=" + "hash": "sha256-32dbg7bKunYC+0fXXUu1E8KvTAphVdfjl8DsDzQRLHU=" }, { "mvn-path": "com/fasterxml/jackson/jackson-bom/2.9.6/jackson-bom-2.9.6.pom", @@ -283,9 +288,14 @@ "hash": "sha256-I39YkwqwLX1S6a/MglsuLYoKvdDobR1dobV53GWAnJE=" }, { - "mvn-path": "com/fasterxml/jackson/jackson-parent/2.12/jackson-parent-2.12.pom", + "mvn-path": "com/fasterxml/jackson/jackson-parent/2.10/jackson-parent-2.10.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-YqocFnmt4J8XPb8bbDLTXFXnWAAjj9XkjxOqQzfAh1s=" + "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", @@ -308,9 +318,14 @@ "hash": "sha256-mnXz4yv51uAGeNlEes5N6FlqLSIa9c9bvH9XHKx5UAY=" }, { - "mvn-path": "com/fasterxml/oss-parent/41/oss-parent-41.pom", + "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-r2UPpN1AC8V2kyC87wjtk4E/NJyr6CE9RprK+72UXYo=" + "hash": "sha256-5VhcwcNwebLjgXqJl5RXNvFYgxhE1Z0OTTpFsnYR+SY=" }, { "mvn-path": "com/fifesoft/rsyntaxtextarea/2.5.6/rsyntaxtextarea-2.5.6.jar", @@ -393,29 +408,24 @@ "hash": "sha256-wecUDR3qj981KLwePFRErAtUEpcxH0X5gGwhPsPumhA=" }, { - "mvn-path": "commons-codec/commons-codec/1.12/commons-codec-1.12.jar", + "mvn-path": "commons-codec/commons-codec/1.15/commons-codec-1.15.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-I99Y+unIPRvNJ3uZ+UKenYwTTwYAtz4uhrI4XteTyB4=" + "hash": "sha256-s+n21jp5AQm/DQVmEfvtHPaQVYJt7+uYlKcTadJG7WM=" }, { - "mvn-path": "commons-codec/commons-codec/1.12/commons-codec-1.12.pom", + "mvn-path": "commons-codec/commons-codec/1.15/commons-codec-1.15.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-vPDVJQioXTBqz9lmi0idOFJ/4/T/J6pvYhtqHcR+I2g=" + "hash": "sha256-yG7hmKNaNxVIeGD0Gcv2Qufk2ehxR3eUfb5qTjogq1g=" }, { - "mvn-path": "commons-codec/commons-codec/1.4/commons-codec-1.4.pom", + "mvn-path": "commons-codec/commons-codec/1.6/commons-codec-1.6.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-9fMAaUIboBPna+znYEB9zIACJGHclVXcvXX9sG2aQI8=" + "hash": "sha256-oG410//zprgT2UiU6/PkmPlUDIZMWzmueDkH46bHKIk=" }, { - "mvn-path": "commons-codec/commons-codec/1.5/commons-codec-1.5.pom", + "mvn-path": "commons-codec/commons-codec/1.9/commons-codec-1.9.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-DAXGK5NrrXQ2C/v+OZPTpdGhnxJ6Oj3z91NDcbPAzK4=" - }, - { - "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=" + "hash": "sha256-5e/PA5zZCWiMIB3FR5sUT9bwHw5AJSt/xefS4bXAeZA=" }, { "mvn-path": "commons-collections/commons-collections/3.2.2/commons-collections-3.2.2.jar", @@ -437,11 +447,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-FaWcDnV8bAfD0baJ1zXI46nsVpXWzrapQdQGKrIpAbc=" }, - { - "mvn-path": "commons-fileupload/commons-fileupload/1.2.1/commons-fileupload-1.2.1.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-I4L0wpLwYWiQkYpUx9/tjtQyocgRgNWHSJNzTva1Ako=" - }, { "mvn-path": "commons-fileupload/commons-fileupload/1.3.3/commons-fileupload-1.3.3.jar", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -453,24 +458,24 @@ "hash": "sha256-zjST9PzEbftnF+KKcyJH4Qwjr81uGd99lXGRQTPxPMg=" }, { - "mvn-path": "commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.jar", + "mvn-path": "commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-pOwCM29JJT6lBAVpi3kjK4xcvwLLYN86Z013p0mh3vc=" + "hash": "sha256-iFF8Wh+NYqr0uLPiMSYVucJ5XbhuaVvzcuNiZ0zz9gA=" }, { - "mvn-path": "commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.pom", + "mvn-path": "commons-fileupload/commons-fileupload/1.5/commons-fileupload-1.5.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-iFF8Wh+NYqr0uLPiMSYVucJ5XbhuaVvzcuNiZ0zz9gA=" + "hash": "sha256-Ufez3LTlDHZimU2i9HIxUZ/5lwelx/t7BfTE06FyjBQ=" }, { - "mvn-path": "commons-io/commons-io/1.4/commons-io-1.4.pom", + "mvn-path": "commons-fileupload/commons-fileupload/1.5/commons-fileupload-1.5.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-La5JahnIK46ZhaEkaqgM+YCCsa4iF89LLU1f8To2WvI=" + "hash": "sha256-zHIPHgWDV52f5Tk8iE7kbQ10+Z0fm6AXsXKzHqGJ4rE=" }, { - "mvn-path": "commons-io/commons-io/2.1/commons-io-2.1.pom", + "mvn-path": "commons-io/commons-io/2.11.0/commons-io-2.11.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-3jQSQ02u92qrYbc0nrjZY0kifC+o3BtiYdyyAjD6KgM=" + "hash": "sha256-LgFv1+MkS18sIKytg02TqkeQSG7h5FZGQTYaPoMe71k=" }, { "mvn-path": "commons-io/commons-io/2.2/commons-io-2.2.pom", @@ -488,9 +493,14 @@ "hash": "sha256-DCOGOJOiKR9aev29jRWSOzlIr9h+Vj+jQc3Pbq4zimA=" }, { - "mvn-path": "commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.pom", + "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-0PLhbQVOi7l63ZyiZSXrI0b2koCfzSooeH2ozrPDXug=" + "hash": "sha256-18hkGjfW5282+56B/BQg4moJ1j+jLwD3R2TeBnyoNH0=" }, { "mvn-path": "commons-logging/commons-logging/1.2/commons-logging-1.2.jar", @@ -532,6 +542,16 @@ "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/", @@ -542,6 +562,16 @@ "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/", @@ -593,14 +623,14 @@ "hash": "sha256-NPdrS86tzY9CC7MgnLsRifay9QO9TtBKnk2c2lTcV8A=" }, { - "mvn-path": "http-kit/http-kit/2.5.0/http-kit-2.5.0.jar", + "mvn-path": "http-kit/http-kit/2.6.0/http-kit-2.6.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-FmNomgwVOHMFggKJpf8DSaokRsEOqgyRZ7UmaNh9n5g=" + "hash": "sha256-wUyefSAkSIDL7oATYbOEo/HeIfX3556wSAOskuW0qjQ=" }, { - "mvn-path": "http-kit/http-kit/2.5.0/http-kit-2.5.0.pom", + "mvn-path": "http-kit/http-kit/2.6.0/http-kit-2.6.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-HamOPAvXTK6eyaO3HJovEnJSr8eW8EoIlXA/EMXLJ4Y=" + "hash": "sha256-mzngH1mQUDDBHh5BwOgEZ+jhv2Rc7n2gl2hVz/W2mSM=" }, { "mvn-path": "instaparse/instaparse/1.4.8/instaparse-1.4.8.jar", @@ -663,14 +693,14 @@ "hash": "sha256-/D20nRP2Bh7bFXdK1aKnsnnqUaqQCXCHmIsFqAbez/8=" }, { - "mvn-path": "luposlip/json-schema/0.3.4/json-schema-0.3.4.jar", + "mvn-path": "luposlip/json-schema/0.4.1/json-schema-0.4.1.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-tiwgQt5tA60CJHKi4rRiVB0IFyEcEsmPSjns9pbUxYs=" + "hash": "sha256-pgkJvS1IZEGc0g3FEJpu3eI+Uldyh2cLP4cFXlk9ftg=" }, { - "mvn-path": "luposlip/json-schema/0.3.4/json-schema-0.3.4.pom", + "mvn-path": "luposlip/json-schema/0.4.1/json-schema-0.4.1.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-hdGqWrOh1aQG6Po6otJLachy9aK1cG+498uAu8MTeLw=" + "hash": "sha256-0jmKkRGLe9T7zNhtSiQZSsP6qWFfshG9LHw6MzWL1JI=" }, { "mvn-path": "medley/medley/1.0.0/medley-1.0.0.jar", @@ -708,49 +738,44 @@ "hash": "sha256-KBRAgRJo5l2eJms8yJgpfiFOBPCXQNA4bO60qJI9Y78=" }, { - "mvn-path": "nrepl/bencode/1.0.0/bencode-1.0.0.pom", + "mvn-path": "nrepl/drawbridge/0.2.1/drawbridge-0.2.1.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-ygs4AH9c8Y43bQj34sgA26e5SZGyzSd3YNQG3nfPu/o=" + "hash": "sha256-zYIIl7rNL2G5HYG/FtCabOymorXYsGIMWUvTt0RfYgA=" }, { - "mvn-path": "nrepl/drawbridge/0.1.0/drawbridge-0.1.0.jar", + "mvn-path": "nrepl/drawbridge/0.2.1/drawbridge-0.2.1.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-9g4WpaMeWpvotgC7iWCO+gIU9BdX73g/bOUsb9XsRFE=" + "hash": "sha256-tJzj0EJ0EacF6t2pVZpq0YHweEBGuD2RWEU++HguYao=" }, { - "mvn-path": "nrepl/drawbridge/0.1.0/drawbridge-0.1.0.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-Bi1zAx0z2Q5pYr7Z5Ntip7wAym0ZFCpDW9SFTvZPeiE=" - }, - { - "mvn-path": "nrepl/nrepl/0.4.0/nrepl-0.4.0.pom", + "mvn-path": "nrepl/nrepl/0.6.0/nrepl-0.6.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-qU5NXvXNOmj4gWAkxYW6Ul5is1Tc6dsy5sYQWKQg5TY=" + "hash": "sha256-QQlhb1Xl2+WcMt0Q6ibhbfeeJDciWW+f5QLw8tMh8A0=" }, { - "mvn-path": "nrepl/nrepl/0.4.5/nrepl-0.4.5.pom", + "mvn-path": "nrepl/nrepl/0.7.0/nrepl-0.7.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-Rh5MqGa6r8uImszQHYo9JlKqNL4ZiBF6MB12zUOeWCE=" + "hash": "sha256-fMYRtNYyT5Djwmh7K2XuSjTjt/vnYgayk4NS8oWNP8E=" }, { - "mvn-path": "nrepl/nrepl/0.6.0/nrepl-0.6.0.jar", + "mvn-path": "nrepl/nrepl/0.7.0/nrepl-0.7.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-9pDMgjuxXPRJMtpHUla3RVtrFj4CP4MJ0rzdhxrugxE=" + "hash": "sha256-yqn3nivBpvotTftjmLD38a5fkWDNNyMxo9WVk2aJMbg=" }, { - "mvn-path": "nrepl/nrepl/0.6.0/nrepl-0.6.0.pom", + "mvn-path": "nrepl/nrepl/0.8.3/nrepl-0.8.3.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-QQlhb1Xl2+WcMt0Q6ibhbfeeJDciWW+f5QLw8tMh8A0=" + "hash": "sha256-GKe1bWh2slPlItP9+wTBq4ieNiDr9Znyic2iDSOUdD0=" }, { - "mvn-path": "nrepl/nrepl/0.7.0/nrepl-0.7.0.jar", + "mvn-path": "nrepl/nrepl/1.0.0/nrepl-1.0.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-fMYRtNYyT5Djwmh7K2XuSjTjt/vnYgayk4NS8oWNP8E=" + "hash": "sha256-owuXNP8uY582Xw11U7SHcwbypUPPAh4pnRW/HqvWpbs=" }, { - "mvn-path": "nrepl/nrepl/0.7.0/nrepl-0.7.0.pom", + "mvn-path": "nrepl/nrepl/1.0.0/nrepl-1.0.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-yqn3nivBpvotTftjmLD38a5fkWDNNyMxo9WVk2aJMbg=" + "hash": "sha256-dHN5LZGfjodYUES5nySqLqFVNCBgRI2opHhGvkXz2nI=" }, { "mvn-path": "ns-tracker/ns-tracker/0.4.0/ns-tracker-0.4.0.jar", @@ -798,9 +823,9 @@ "hash": "sha256-vBBiTgYj82V3+sVjnKKTbTJA7RUvttjVM6tNJwVDSRw=" }, { - "mvn-path": "org/apache/apache/4/apache-4.pom", + "mvn-path": "org/apache/apache/29/apache-29.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-npMjomuo6yOU7+8MltMbcN9XCAhjDcFHyrHnNUHMUZQ=" + "hash": "sha256-PkkDcXSCC70N9jQgqXclWIY5iVTCoGKR+mH3J6w1s3c=" }, { "mvn-path": "org/apache/apache/7/apache-7.pom", @@ -832,11 +857,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-+tcjNup9fdBtoQMUTjdA21CPpLF9nFTXhHc37cJKfmA=" }, - { - "mvn-path": "org/apache/commons/commons-parent/11/commons-parent-11.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-ueAwbzk0YBBbij+lEFJQxSkbHvqpmVSs4OwceDEJoCo=" - }, { "mvn-path": "org/apache/commons/commons-parent/17/commons-parent-17.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -847,11 +867,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-qlKBSXsVknTuOULf4s8Qo2jCT0khUVCkzAmKsoVFmyg=" }, - { - "mvn-path": "org/apache/commons/commons-parent/20/commons-parent-20.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-7cEfBOcgFjBhzYgsILqRscOuGlmwx5ypcTN2UzVkIVs=" - }, { "mvn-path": "org/apache/commons/commons-parent/22/commons-parent-22.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -862,6 +877,11 @@ "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/", @@ -893,19 +913,24 @@ "hash": "sha256-io7LVwVTv58f+uIRqNTKnuYwwXr+WSkzaPunvZtC/Lc=" }, { - "mvn-path": "org/apache/commons/commons-parent/5/commons-parent-5.pom", + "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-i9YywAvfgKfeNsIrYPEkUsFH2Oyi8A151maZ6+faoCo=" + "hash": "sha256-ddvo806Y5MP/QtquSi+etMvNO18QR9VEYKzpBtu0UC4=" }, { - "mvn-path": "org/apache/commons/commons-parent/51/commons-parent-51.pom", + "mvn-path": "org/apache/commons/commons-parent/56/commons-parent-56.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-m3edGLItjeVZYFVY57sKCjGz8Awqu5yHgRfDmKrKvso=" + "hash": "sha256-VgxwUd3HaOE3LkCHlwdk5MATkDxdxutSwph3Nw2uJpQ=" }, { - "mvn-path": "org/apache/commons/commons-parent/7/commons-parent-7.pom", + "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.3/httpasyncclient-4.1.3.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-AelwZ6H5vQ4KNPLzK79D/m6bEZEPOw3+d/1/tBhhkQU=" + "hash": "sha256-PaAuVYrN/VVGzpb3k4287Bvjjg54PuJMYqz9Slc1Ysk=" }, { "mvn-path": "org/apache/httpcomponents/httpasyncclient/4.1.4/httpasyncclient-4.1.4.jar", @@ -928,9 +953,9 @@ "hash": "sha256-o1h75UcWw7gKMonBHfhlqWK8cr5HiRReQgxpSL9FpKQ=" }, { - "mvn-path": "org/apache/httpcomponents/httpclient/4.1.2/httpclient-4.1.2.pom", + "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-nqRFFJ15UYBrnkPlg04PNOQMlAH6NkTKfF7ZMkagKH8=" + "hash": "sha256-5fDcW+r/uQjJuhuA9X3aTJDpHWiV2DIXsYGuq+Zd11M=" }, { "mvn-path": "org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar", @@ -942,20 +967,30 @@ "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.4/httpcomponents-asyncclient-4.1.4.pom", + "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-6WXrNprx2LWmi31LeGHVOlJhaugD4TFi6N+EImFwAYA=" + "hash": "sha256-heaQHESTgHNAodUcJWVc/6/KMQbTtb1Gf64gq8E56xY=" }, { - "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.1.2/httpcomponents-client-4.1.2.pom", + "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-g8I+Q7BnJWjTugL6ZhY52qU3i/AIQnXrSTdneoWx+60=" + "hash": "sha256-6WXrNprx2LWmi31LeGHVOlJhaugD4TFi6N+EImFwAYA=" }, { "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.13/httpcomponents-client-4.5.13.pom", @@ -963,14 +998,19 @@ "hash": "sha256-nLpZTAjbcnHQwg6YRdYiuznmlYORC0Xn1d+C9gWNTdk=" }, { - "mvn-path": "org/apache/httpcomponents/httpcomponents-client/4.5.6/httpcomponents-client-4.5.6.pom", + "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-sEK0HyOR7bANNff05Qmu0hI2SMHSRs5Y0Pe5Bcn+H3M=" + "hash": "sha256-Q8fSl1oRJbsAzwWKOHf1SaHxiU+LitR+LD5G4/xGI9w=" }, { - "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.1.2/httpcomponents-core-4.1.2.pom", + "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-BXspb0yjs4WJwvlE4vfzHoaJJVZxdePzu23fKVHGIdw=" + "hash": "sha256-sEK0HyOR7bANNff05Qmu0hI2SMHSRs5Y0Pe5Bcn+H3M=" }, { "mvn-path": "org/apache/httpcomponents/httpcomponents-core/4.4.10/httpcomponents-core-4.4.10.pom", @@ -982,6 +1022,21 @@ "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/", @@ -992,6 +1047,11 @@ "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/", @@ -1003,9 +1063,9 @@ "hash": "sha256-pMmVtzjRBLdcyLEWTbYAUzFWwEfsy0yO5dknshoX7HM=" }, { - "mvn-path": "org/apache/httpcomponents/httpcore/4.1.2/httpcore-4.1.2.pom", + "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-FhOqE3lbVAY26tb0N8qmSZ2MLjQn02JKwfi4Cv4mwO4=" + "hash": "sha256-Slb+Yua6ijj46jhdhEM5IXm5XQ9WKMjpGJ2cwkj5enw=" }, { "mvn-path": "org/apache/httpcomponents/httpcore/4.4.10/httpcore-4.4.10.pom", @@ -1013,19 +1073,29 @@ "hash": "sha256-xcEgZt8rO4iomiyGArgeqaYWJ+l25RKe6hiZ67rqOSs=" }, { - "mvn-path": "org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar", + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-4G6J1AlDJF/Po57FN82/zjdirs3o+cWXeA0rAMK0NCQ=" + "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.14/httpcore-4.4.14.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-j4Etn6e3Kj1Kp/glJ4kypd80S0Km2DmJBYeUMaG/mpc=" + "hash": "sha256-+VYgnkUMsdDFF3bfvSPlPp3Y25oSmO1itwvwlEumOyg=" }, { - "mvn-path": "org/apache/httpcomponents/httpmime/4.1.2/httpmime-4.1.2.pom", + "mvn-path": "org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-L9SZBSvyJ6acZmW4jzGVIBoKbBSHoqNQYZdfIvC0O0Q=" + "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", @@ -1038,9 +1108,9 @@ "hash": "sha256-k0GN8hCu7VBQJUjbzysXwPHZFEMDDnL+++7RZSscKN0=" }, { - "mvn-path": "org/apache/httpcomponents/project/4.1.1/project-4.1.1.pom", + "mvn-path": "org/apache/httpcomponents/httpmime/4.5.5/httpmime-4.5.5.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-IbtNRN/1TjOjfBGvaYWacUICrgCWmqtUU+unJ2aI+Ow=" + "hash": "sha256-5qZxutaVb3QY/hCRYuho60VZxk+gnu9G6+RxqEqt6lw=" }, { "mvn-path": "org/apache/httpcomponents/project/7/project-7.pom", @@ -1188,34 +1258,34 @@ "hash": "sha256-3NxQZHI5f265GqOAiE+3EtqBrtaqUyvAzh5o5Zp8wwI=" }, { - "mvn-path": "org/clojure/core.async/1.3.610/core.async-1.3.610.jar", + "mvn-path": "org/clojure/core.async/1.6.673/core.async-1.6.673.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-A7svz0ZslWFRMvZ5qVgYv9Y7nQry2ouCZ/q1ehgMYXc=" + "hash": "sha256-FoHdGIjHVAH0RLURyDU/vaPOsadgiBCiPd0l0QRfkHo=" }, { - "mvn-path": "org/clojure/core.async/1.3.610/core.async-1.3.610.pom", + "mvn-path": "org/clojure/core.async/1.6.673/core.async-1.6.673.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-ZgSZVDqGbvrJPC73OojFzqbK719HDMI+oeSPRedNL3w=" + "hash": "sha256-S8rQJfFQpWa3+vdJPQSEy1momBySO3jFC88ORiHr3jg=" }, { - "mvn-path": "org/clojure/core.cache/1.0.207/core.cache-1.0.207.jar", + "mvn-path": "org/clojure/core.cache/1.0.225/core.cache-1.0.225.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-j0TtpTM2iDtGH7nIfkixdDpNgmt9OvsEyI962ZdWXso=" + "hash": "sha256-wVOqlH7aXNvYqTiCyPur1QN9StcxGAK0vNgBVGn2pbE=" }, { - "mvn-path": "org/clojure/core.cache/1.0.207/core.cache-1.0.207.pom", + "mvn-path": "org/clojure/core.cache/1.0.225/core.cache-1.0.225.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-cT6lL/F0jRLo7Fj/3Iz7u2vfDs2PFqcQz54plG6wwBs=" + "hash": "sha256-OeNB9nv+85PkeDkNSYjxGad5ykSQZssNM/gLQv8E9D0=" }, { - "mvn-path": "org/clojure/core.memoize/1.0.236/core.memoize-1.0.236.jar", + "mvn-path": "org/clojure/core.memoize/1.0.253/core.memoize-1.0.253.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-hJz3OP9Z7XTcxsxBxDsiD/Tk014GgItrwbF7kBgmVEE=" + "hash": "sha256-SpEFhRgqsybB0KINNDFb4VY7WlhDfUHAId1/6ZEeHtY=" }, { - "mvn-path": "org/clojure/core.memoize/1.0.236/core.memoize-1.0.236.pom", + "mvn-path": "org/clojure/core.memoize/1.0.253/core.memoize-1.0.253.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-Y06JD65bM83SsVieEfjmt4WU1XQTwC+hF49oi88iCeg=" + "hash": "sha256-hML6t6Mso8HkDEGm7Mm9U26UezBYDne41dwjKjSSXqw=" }, { "mvn-path": "org/clojure/core.specs.alpha/0.1.24/core.specs.alpha-0.1.24.pom", @@ -1273,14 +1343,14 @@ "hash": "sha256-1cESYLiumGM6l3FThM8ENeAknrIhTi7VPaTMGMkToAE=" }, { - "mvn-path": "org/clojure/data.int-map/1.0.0/data.int-map-1.0.0.jar", + "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-miIrvZqLSzV0Tx74g+uDmy8ZrZJsJhyeYoQVev4jd8A=" + "hash": "sha256-EEdYYvpJt4MszXZeV34rlcIYLgKR/N88IwAkLyi6sRQ=" }, { - "mvn-path": "org/clojure/data.int-map/1.0.0/data.int-map-1.0.0.pom", + "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-QvxXdpkP+HtoqC7gKVu1E5X0cPrFLCQ+hwmDPFMpOQA=" + "hash": "sha256-bO0gykWPpQ4MVVm9tG5lmIiAHT8jHJESk2j44dtGZT8=" }, { "mvn-path": "org/clojure/data.json/0.2.6/data.json-0.2.6.pom", @@ -1308,9 +1378,9 @@ "hash": "sha256-OuRzO1o2KIhJOJMC+4SXnXxSqtiOWINYMsf/jTIf/Sg=" }, { - "mvn-path": "org/clojure/data.priority-map/1.0.0/data.priority-map-1.0.0.pom", + "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-oelmVnOmC8I3A8FMT0fAIecgHL8yzUpZ4sJJyJ9bExU=" + "hash": "sha256-RlIA+U9W2IaOD9eqC+zGL/sCz69CCkmtEXkQ5jr13/4=" }, { "mvn-path": "org/clojure/data.xml/0.0.8/data.xml-0.0.8.jar", @@ -1353,24 +1423,24 @@ "hash": "sha256-3HT8YeCMPpABhqeoyvAAQj9EG9Q9XQ6aiRccOFj79qg=" }, { - "mvn-path": "org/clojure/math.combinatorics/0.1.6/math.combinatorics-0.1.6.jar", + "mvn-path": "org/clojure/math.combinatorics/0.2.0/math.combinatorics-0.2.0.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-hayruPhhPvNzpRh1yrfwMuK+BzsZNxZeItmC0okbUns=" + "hash": "sha256-sIzH3pFeSK/V2mGUuYgXxHqlCvZ3RyHMv7lKn0vWbWM=" }, { - "mvn-path": "org/clojure/math.combinatorics/0.1.6/math.combinatorics-0.1.6.pom", + "mvn-path": "org/clojure/math.combinatorics/0.2.0/math.combinatorics-0.2.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-5YEc6YpDSIBkBV4/jS8jXEbcdEL4f3dBMIcBOawhKp8=" + "hash": "sha256-9jURjWWI35hOA+nLRKjWuuL9x7nGrnub7QAqkoOex+U=" }, { - "mvn-path": "org/clojure/math.numeric-tower/0.0.4/math.numeric-tower-0.0.4.jar", + "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-Crctj8l20hUlovMsUs37nJgIqefvjbewgDrKQ4GMgqU=" + "hash": "sha256-5NIRNXlWkT5DvcbD/csjExqWLjHX6G16GnTTFRm9+/8=" }, { - "mvn-path": "org/clojure/math.numeric-tower/0.0.4/math.numeric-tower-0.0.4.pom", + "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-CngK5eJjqbgkFceq+FEk0QCfokGKfsEdjJ4DqwbWIpA=" + "hash": "sha256-w/lnehWxSPzvMAsGC29fn2fToTWUMhq+svIFpau+qZE=" }, { "mvn-path": "org/clojure/pom.contrib/0.0.25/pom.contrib-0.0.25.pom", @@ -1392,11 +1462,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-fxgrOypUPgV0YL+T/8XpzvasUn3xoTdqfZki6+ee8Rk=" }, - { - "mvn-path": "org/clojure/pom.contrib/1.0.0/pom.contrib-1.0.0.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-EBH6rlyeSWhY5MZQujNxOr1Gml1S4Arrf1sBoryvR+k=" - }, { "mvn-path": "org/clojure/pom.contrib/1.1.0/pom.contrib-1.1.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", @@ -1438,24 +1503,24 @@ "hash": "sha256-bY3hTDrIdXYMX/kJVi/5hzB3AxxquTnxyxOeFp/pB1g=" }, { - "mvn-path": "org/clojure/tools.analyzer.jvm/1.1.0/tools.analyzer.jvm-1.1.0.jar", + "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-36tXEmBdmBZdFWbHj91XE2l+FqN5yjD1qdnXJoJljk0=" + "hash": "sha256-kQz/AjiTHtiIYstmWmd+ldk+hIDyIzIAiG0zHX7QDl4=" }, { - "mvn-path": "org/clojure/tools.analyzer.jvm/1.1.0/tools.analyzer.jvm-1.1.0.pom", + "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-oURBkMmdK5+OfSLhJgya+uVu2eiaZMgJl5XYK5CD3jM=" + "hash": "sha256-EOGi60Q6PFfsGd7e8ylC63SbrmnyFZiI/lYLpnuwj0c=" }, { - "mvn-path": "org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0.jar", + "mvn-path": "org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-Rj9DH4m9wuaePN+9pf24wtO39tpe3aoDZ98NsEfsQVY=" + "hash": "sha256-E2i2vDvd98OY1XhNEFSPRMTtLXwB6hBawO/enPXg3yE=" }, { - "mvn-path": "org/clojure/tools.analyzer/1.0.0/tools.analyzer-1.0.0.pom", + "mvn-path": "org/clojure/tools.analyzer/1.1.0/tools.analyzer-1.1.0.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-BFPP8KudRVDiyGfY6ZVfDAID4TloNw6zzke99ZMy5Pk=" + "hash": "sha256-NyBxL7knYaNclNDuQV1r8VhB70afBzZGd2h1553JtwY=" }, { "mvn-path": "org/clojure/tools.cli/0.3.1/tools.cli-0.3.1.pom", @@ -1463,19 +1528,14 @@ "hash": "sha256-5Ra0m3LZ+cymDu5ojQR8aCD1YGNUEdVWZ5Juv2b6buM=" }, { - "mvn-path": "org/clojure/tools.cli/1.0.194/tools.cli-1.0.194.jar", + "mvn-path": "org/clojure/tools.cli/1.0.219/tools.cli-1.0.219.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-TD1eJoX8e62W2Z3ex1vW6pB4rrhRQibgzJSzEkP8aRA=" + "hash": "sha256-lGADrC8iiODAxYhhYk3z75gRHJ6Id+tAHKUozDd/l6k=" }, { - "mvn-path": "org/clojure/tools.cli/1.0.194/tools.cli-1.0.194.pom", + "mvn-path": "org/clojure/tools.cli/1.0.219/tools.cli-1.0.219.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-2P0j6g1cwhJ9JZu4oYb4WX8oc24MxN+h9b6N0hY+Zzo=" - }, - { - "mvn-path": "org/clojure/tools.logging/0.4.1/tools.logging-0.4.1.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-Diam5eg2HQtIrJesD0vTD6p+/nGmqyNKdyBVlfKLDuI=" + "hash": "sha256-v9jf44Bp4mJIzqRyQ9+Zvv/0mjGGzDyk1fNTefp9u3M=" }, { "mvn-path": "org/clojure/tools.macro/0.1.5/tools.macro-0.1.5.jar", @@ -1503,34 +1563,34 @@ "hash": "sha256-EE60zx3DmRcTb/FT1KevfEmmjxK73667naNvOG3+MJo=" }, { - "mvn-path": "org/clojure/tools.reader/1.3.2/tools.reader-1.3.2.jar", + "mvn-path": "org/clojure/tools.reader/1.3.6/tools.reader-1.3.6.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-E7dTbXkDdT+wJvVHGTDi7ny1Xhh6nhQR51crm56HaYA=" + "hash": "sha256-EdGzHyxlwzVbKSu5tEuPyv2lS0TaY+NKuXt5qKs7uOA=" }, { - "mvn-path": "org/clojure/tools.reader/1.3.2/tools.reader-1.3.2.pom", + "mvn-path": "org/clojure/tools.reader/1.3.6/tools.reader-1.3.6.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-sc3Czi6cUhEEfa7M0kuo1bE2yOmYDDiZ6k4rovDZ0dg=" + "hash": "sha256-rvXugot8sUocWPRbn4oQ/zQMV2mSXqDvXDXR5J2SC+o=" }, { - "mvn-path": "org/codehaus/jackson/jackson-core-asl/1.9.5/jackson-core-asl-1.9.5.pom", + "mvn-path": "org/json/json/20230227/json-20230227.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-fRWNcilZ9Tq8mCQHatuKxvLFPhf5scZuP8BCT9UoUOU=" + "hash": "sha256-ntJnkdwthin9+KIH8a663LUNZBvmN2ZDEO9RwPc+Jps=" }, { - "mvn-path": "org/codehaus/jackson/jackson-smile/1.9.5/jackson-smile-1.9.5.pom", + "mvn-path": "org/json/json/20230227/json-20230227.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-bJYO8CtN+fFAiKHoigqhxCiA4A/BamEuK7DI8CDTGd0=" + "hash": "sha256-w5JpSWHKzw6oY/WCyHTfHYbrWRu/tSaqBY7/eULakHA=" }, { - "mvn-path": "org/json/json/20220320/json-20220320.jar", + "mvn-path": "org/junit/junit-bom/5.7.2/junit-bom-5.7.2.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-Ht9/zqeaFrjf3TvJiN3sf4kIsfd2L98A05rLA3VCdHo=" + "hash": "sha256-zRSqqGmZH4ICHFhdVw0x/zQry6WLtEIztwGTdxuWSHs=" }, { - "mvn-path": "org/json/json/20220320/json-20220320.pom", + "mvn-path": "org/junit/junit-bom/5.9.1/junit-bom-5.9.1.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-FKNkhDrCuoikgSaePGvGosDKURjCoHsd3/K/YN4ce/U=" + "hash": "sha256-sWPBz8j8H9WLRXoA1YbATEbphtdZBOnKVMA6l9ZbSWw=" }, { "mvn-path": "org/mozilla/rhino/1.7R5/rhino-1.7R5.jar", @@ -1553,24 +1613,14 @@ "hash": "sha256-1LvcRQ5FaR2KfmrAGBqOr5amU4spUZZWo/zO6JVNY0s=" }, { - "mvn-path": "org/ow2/asm/asm-parent/5.2/asm-parent-5.2.pom", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-y/rNXdgS2xbckQmUAH/ljZQColUH+BocIhjhXCwb7fo=" - }, - { - "mvn-path": "org/ow2/asm/asm/5.2/asm-5.2.jar", - "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-Pl6g19osUVXvT0cNkJLULeNOP1PbZYnHwH1nIa30uj4=" - }, - { - "mvn-path": "org/ow2/asm/asm/5.2/asm-5.2.pom", + "mvn-path": "org/ow2/asm/asm/9.2/asm-9.2.jar", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-KJ9/u9ewxXbVKG6K3PjQc1E005Fe/qZDbivjXBH88FA=" + "hash": "sha256-udT+TXGTjfOIOfDspCqqpkz4sxPWeNoDbwyzyhmbR/U=" }, { - "mvn-path": "org/ow2/ow2/1.3/ow2-1.3.pom", + "mvn-path": "org/ow2/asm/asm/9.2/asm-9.2.pom", "mvn-repo": "https://repo1.maven.org/maven2/", - "hash": "sha256-USFcZ9LAaNi30vb4D1E3KgmAdd7MxEjUvde5h7qDKPs=" + "hash": "sha256-37EqGyJL8Bvh/WBAIEZviUJBvLZF3M45Xt2M1vilDfQ=" }, { "mvn-path": "org/ow2/ow2/1.5/ow2-1.5.pom", @@ -1578,19 +1628,19 @@ "hash": "sha256-D4obEW52C4/mOJxRuE5LB6cPwRCC1Pk25FO1g91QtDs=" }, { - "mvn-path": "org/ow2/sat4j/org.ow2.sat4j.core/2.3.5/org.ow2.sat4j.core-2.3.5.jar", + "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-/6KEf3SDr7lcz2lt+mevLmPzMw1wPLQFKRg4UE3Uyss=" + "hash": "sha256-Dnqs3Zdt2VgpHmpVmb2uDUAkb+Ene3tjqA2w/JLUadc=" }, { - "mvn-path": "org/ow2/sat4j/org.ow2.sat4j.core/2.3.5/org.ow2.sat4j.core-2.3.5.pom", + "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-Jo8VahIj2ts8ATkGDI98/YIwcyNeC4cXYZ3AzjGz3Aw=" + "hash": "sha256-X6UiKuspIeDBmsYcM3x4WjeIWcUZktBLr4Ob+yoOiGk=" }, { - "mvn-path": "org/ow2/sat4j/org.ow2.sat4j.pom/2.3.5/org.ow2.sat4j.pom-2.3.5.pom", + "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-c4CkdFv8NbOLJWBlbzlT7ngyp4z0Zdxew6QRz2KFRM4=" + "hash": "sha256-oKFkiu2kvKpd0AvsWq77eLrZCreLstrwloU5M3haXDI=" }, { "mvn-path": "org/sonatype/oss/oss-parent/5/oss-parent-5.pom", @@ -1672,16 +1722,6 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-Mf7YCTza2mFHjDCUFFYiZIndn6yw5mC2KPK8Fgp8KiU=" }, - { - "mvn-path": "org/thnetos/cd-client/0.3.6/cd-client-0.3.6.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-ngdkTQLtVj1V4UwfiPKOoZl7dc04jYS28avz3XUn4jQ=" - }, - { - "mvn-path": "org/thnetos/cd-client/0.3.6/cd-client-0.3.6.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-8xneE0aDe5EM44egbOEcw794NIcEN3CMmK+x8tkpmo0=" - }, { "mvn-path": "potemkin/potemkin/0.4.5/potemkin-0.4.5.jar", "mvn-repo": "https://repo.clojars.org/", @@ -1693,14 +1733,14 @@ "hash": "sha256-3tL5YlDzDlqPmI60YeMvKzDzbBy0Qz+6qHu82kJRTDo=" }, { - "mvn-path": "reply/reply/0.4.4/reply-0.4.4.jar", + "mvn-path": "reply/reply/0.5.1/reply-0.5.1.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-eCSfzGwprV/uQpTJ8b/Dv/mc/GEVA2S27wmBG/RaQfc=" + "hash": "sha256-UkcQm+0oBiDtjWoL0AAQPUl/hMDnL17DOG3mxaqFJtk=" }, { - "mvn-path": "reply/reply/0.4.4/reply-0.4.4.pom", + "mvn-path": "reply/reply/0.5.1/reply-0.5.1.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-eKSMuSIDpECTxEQo7dY/p1LnFe5FXCLnHO3j6j6XTBA=" + "hash": "sha256-qkKSptY8RMcRBq+9gLw83Om5XLlP/7wgpEszkAqvicQ=" }, { "mvn-path": "riddley/riddley/0.1.12/riddley-0.1.12.jar", @@ -1738,19 +1778,29 @@ "hash": "sha256-/iYsnPUyUprMi/VMioni1S7XjGJxe2n2iH06u+3HBE0=" }, { - "mvn-path": "ring/ring-codec/1.1.2/ring-codec-1.1.2.jar", + "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-dY0nO/1nr7z4O3k5Uj+eKI4ifJstIiMsPpX1ESsK0GA=" + "hash": "sha256-b9itBQBiBVtXtzgRBnH2j0NXdEQ9GCbmL07GTSxFZcI=" }, { - "mvn-path": "ring/ring-codec/1.1.2/ring-codec-1.1.2.pom", + "mvn-path": "ring/ring-codec/1.2.0/ring-codec-1.2.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-sl/LVYL/wFWem+BnzQXP13SAm5IocZz8HqVojsnKH+c=" + "hash": "sha256-de3pMoKzj49m+yTFILdNGDfQsbtdpUIW+AOglmzp2s4=" }, { - "mvn-path": "ring/ring-core/1.0.2/ring-core-1.0.2.pom", + "mvn-path": "ring/ring-core/1.10.0/ring-core-1.10.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-nFgQUuxL3SvlaQTptGCUBQTuQ5Gd9yVBsyWjCtHkkp4=" + "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", @@ -1763,24 +1813,19 @@ "hash": "sha256-MZUZuyMA+z2xtjry80cfyc3yrSJWqg2OzrXEQoPeKaU=" }, { - "mvn-path": "ring/ring-core/1.8.2/ring-core-1.8.2.jar", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-HWq9CMVYxvzzzQd12IY9G/QUNGfkF/KPKM9OEGn/QqE=" - }, - { - "mvn-path": "ring/ring-core/1.8.2/ring-core-1.8.2.pom", + "mvn-path": "ring/ring-core/1.9.2/ring-core-1.9.2.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-AkLm16tSI3+T/a3ADfY0wIKK+2xOrAlgXZwb0aKF7CE=" + "hash": "sha256-03k0Epji4KSB/e+zKMZluPCsiLT5GlevXiO3+JIXNDU=" }, { - "mvn-path": "ring/ring-devel/1.8.2/ring-devel-1.8.2.jar", + "mvn-path": "ring/ring-devel/1.10.0/ring-devel-1.10.0.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-ijUWbMPwbAJJOqIQldVogNR2jrb6mZ+/pCPdoVmZN24=" + "hash": "sha256-PR0RDPdBzvmbv/xLilmJZ7WvQmGMcTAm1qrZXmBUaVo=" }, { - "mvn-path": "ring/ring-devel/1.8.2/ring-devel-1.8.2.pom", + "mvn-path": "ring/ring-devel/1.10.0/ring-devel-1.10.0.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-xHrQH/eB4iR78DlxILT5lKNmOZhpi+uEmGMlIX3yn/o=" + "hash": "sha256-CVk/j1Kyuozf5B7Y7emC9PP8D+bxPRKoqrZy+MmjaGc=" }, { "mvn-path": "ring/ring-json/0.5.0/ring-json-0.5.0.jar", @@ -1792,6 +1837,16 @@ "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/", @@ -1803,14 +1858,14 @@ "hash": "sha256-I/fFTWW8mmul1f3XtJNX92RvkEd0idYFjLJdnMlXVJE=" }, { - "mvn-path": "rolling-stones/rolling-stones/1.0.1/rolling-stones-1.0.1.jar", + "mvn-path": "rolling-stones/rolling-stones/1.0.3/rolling-stones-1.0.3.jar", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-pXFiZpozPpl5h4aoSgKRAepUUNlsglpZWI8Ou7odxW8=" + "hash": "sha256-i4oCrGaiCCSWjFiJ4JYaCSViCuX3ME6eyclGBQz2Sck=" }, { - "mvn-path": "rolling-stones/rolling-stones/1.0.1/rolling-stones-1.0.1.pom", + "mvn-path": "rolling-stones/rolling-stones/1.0.3/rolling-stones-1.0.3.pom", "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-EU6qRQ1GbPo53rBkHYcitPqmu/uDB912i74tFG9uzzA=" + "hash": "sha256-Rv0cpeJy2+7DSwTKboUhjHbPLqFO1wy3p/YM1UD7wZ4=" }, { "mvn-path": "seesaw/seesaw/1.5.0/seesaw-1.5.0.jar", @@ -1822,16 +1877,6 @@ "mvn-repo": "https://repo.clojars.org/", "hash": "sha256-Zr57ZKhCO9jNfh4juQC7s86HfCbKn8KK/c2JkkyqZfU=" }, - { - "mvn-path": "slingshot/slingshot/0.10.2/slingshot-0.10.2.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-uktZMI2MBxSRHki1Jq6rKDdIzkdSTVSDz+nbr6rOUf8=" - }, - { - "mvn-path": "slingshot/slingshot/0.10.3/slingshot-0.10.3.pom", - "mvn-repo": "https://repo.clojars.org/", - "hash": "sha256-uSqD0IT1dvpHTTi7XswyU7zg4CnXZaRFykgHFS4eqiM=" - }, { "mvn-path": "slingshot/slingshot/0.12.2/slingshot-0.12.2.jar", "mvn-repo": "https://repo.clojars.org/", @@ -1862,6 +1907,11 @@ "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/", From 7b5214020b4e5fc3b9dfe3d2d8575e564c4f51f2 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Wed, 31 May 2023 16:05:14 +0200 Subject: [PATCH 076/112] add lein-aot-order plugin (#112) --- project.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 6bbf76949..cc59e9738 100644 --- a/project.clj +++ b/project.clj @@ -46,7 +46,8 @@ :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"] [nrepl/nrepl "1.0.0"]] - :aot :all} + :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"] From 5a0f4b1369bee17f06a1d1beff48dcb3fa5efecf Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Wed, 31 May 2023 16:08:34 +0200 Subject: [PATCH 077/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 --- deps-lock.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ flake.lock | 41 ++++++++++++++--------------------------- flake.nix | 8 +++----- project.clj | 1 + 4 files changed, 63 insertions(+), 32 deletions(-) diff --git a/deps-lock.json b/deps-lock.json index 938a0351e..32c5b33c0 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -692,6 +692,16 @@ "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/", @@ -1217,6 +1227,11 @@ "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/", @@ -1412,6 +1427,16 @@ "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/", @@ -1557,11 +1582,31 @@ "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/", diff --git a/flake.lock b/flake.lock index b2fe9a0d3..d197ce887 100644 --- a/flake.lock +++ b/flake.lock @@ -12,23 +12,25 @@ ] }, "locked": { - "lastModified": 1663854702, - "narHash": "sha256-gnoyYWvZl64WBqR3tf9bKHAznEtBCHmwx7taHghH9Lw=", - "owner": "mmarx", + "lastModified": 1677342613, + "narHash": "sha256-BqhKj7jQahSVThEwLHt164kJHGx9LXzBARFZaFNLPW8=", + "owner": "jlesquembre", "repo": "clj-nix", - "rev": "10b1e9544811c07e02c536872fcd6a6aeed96ba7", + "rev": "7d9e244ea96988524ba3bd6c2bbafdf0a5340b96", "type": "github" }, "original": { - "owner": "mmarx", - "ref": "fix-lein", + "owner": "jlesquembre", "repo": "clj-nix", "type": "github" } }, "devshell": { "inputs": { - "flake-utils": "flake-utils", + "flake-utils": [ + "clj-nix", + "flake-utils" + ], "nixpkgs": [ "clj-nix", "nixpkgs" @@ -49,21 +51,6 @@ } }, "flake-utils": { - "locked": { - "lastModified": 1642700792, - "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { "locked": { "lastModified": 1644229661, "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", @@ -100,16 +87,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1672580127, - "narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=", + "lastModified": 1685451684, + "narHash": "sha256-Y5iqtWkO82gHAnrBvNu/yLQsiVNJRCad4wWGz2a1urk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "0874168639713f547c05947c76124f78441ea46c", + "rev": "6b0edc9c690c1d8a729f055e0d73439045cfda55", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-22.05", + "ref": "nixos-23.05", "repo": "nixpkgs", "type": "github" } @@ -124,7 +111,7 @@ }, "utils": { "inputs": { - "flake-utils": "flake-utils_2" + "flake-utils": "flake-utils" }, "locked": { "lastModified": 1657226504, diff --git a/flake.nix b/flake.nix index d4ccb5ab6..e9bdae41b 100644 --- a/flake.nix +++ b/flake.nix @@ -3,13 +3,11 @@ "conexp-clj, a general purpose software tool for Formal Concept Analysis"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.05"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; utils.url = "github:gytis-ivaskevicius/flake-utils-plus"; clj-nix = { - #url = "github:jlesquembre/clj-nix"; - url = - "github:mmarx/clj-nix/fix-lein"; # we need to wait for PR 31 to go through. + url = "github:jlesquembre/clj-nix"; inputs = { nixpkgs.follows = "nixpkgs"; flake-utils.follows = "utils/flake-utils"; @@ -38,7 +36,7 @@ conexp = let pname = "conexp-clj"; - version = "2.3.0-SNAPSHOT"; + version = "2.3.1-SNAPSHOT"; in mkCljBin rec { name = "conexp/${pname}"; inherit version; diff --git a/project.clj b/project.clj index cc59e9738..f043ce9cb 100644 --- a/project.clj +++ b/project.clj @@ -52,6 +52,7 @@ :dependencies [[javax.servlet/servlet-api "2.5"] [ring/ring-mock "0.4.0"] [nrepl/nrepl "1.0.0"]] + :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"]]}} From 1c71541ba97056e453a44b39c6176ac83b1dad02 Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Wed, 31 May 2023 16:24:49 +0200 Subject: [PATCH 078/112] Fix build status badge in README (#114) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f9636f0c..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 From 0baac0055f893816a3159164ed81e87f78823593 Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Fri, 2 Jun 2023 17:39:35 +0200 Subject: [PATCH 079/112] Explain the flake in the documentation (#115) * Add overlay to flake * Add documentation on the nix flake --- doc/Getting-Started.org | 55 +++++++++++++++++++++++++++++++++++++++++ flake.nix | 4 +++ 2 files changed, 59 insertions(+) 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/flake.nix b/flake.nix index e9bdae41b..d2309e274 100644 --- a/flake.nix +++ b/flake.nix @@ -28,6 +28,10 @@ channels.nixpkgs.overlaysBuilder = channels: [ inputs.clj-nix.overlays.default ]; + overlays.default = final: prev: { + inherit (self.packages."${final.system}") conexp-clj; + }; + outputsBuilder = channels: let inherit (inputs.gitignore.lib) gitignoreSource; From fd8801cccfead1036cfabbf0026638af6b0cf166 Mon Sep 17 00:00:00 2001 From: Jana Date: Fri, 30 Jun 2023 12:14:03 +0200 Subject: [PATCH 080/112] add ben-and-jerrys context --- testing-data/ben-and-jerrys.ctx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 testing-data/ben-and-jerrys.ctx diff --git a/testing-data/ben-and-jerrys.ctx b/testing-data/ben-and-jerrys.ctx new file mode 100644 index 000000000..9a1466fd7 --- /dev/null +++ b/testing-data/ben-and-jerrys.ctx @@ -0,0 +1,28 @@ +B + +7 +9 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +Choco Ice +Peanut Ice +Choco Pieces +Brownie +Dough +Peanut Butter +Caramel Ice +Vanilla +Caramel +.XX..X... +X..X..... +X.X...X.X +..XX...XX +..X...X.X +X.XXX..X. +..X.X..X. From 3cefab42327c77eb7c570487cb7676cddca684b4 Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 10 Jul 2023 10:50:22 +0200 Subject: [PATCH 081/112] first version of icfca2023 conexp tutorial --- .../icfca-2023/icfca-2023-tutorial.org | 431 ++++++++++++++++++ ...-jerrys-lattice-dimdraw-labels-support.png | Bin 0 -> 130576 bytes ...n-and-jerrys-lattice-manual_valuations.png | Bin 0 -> 129301 bytes .../images/ben-and-jerrys-lattice.png | Bin 0 -> 87165 bytes 4 files changed, 431 insertions(+) create mode 100644 doc/tutorials/icfca-2023/icfca-2023-tutorial.org create mode 100644 doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-support.png create mode 100644 doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-manual_valuations.png create mode 100644 doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice.png diff --git a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org new file mode 100644 index 000000000..23d72c314 --- /dev/null +++ b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org @@ -0,0 +1,431 @@ +#+property: header-args :wrap src text +#+property: header-args:text :eval never + +* ~conexp-clj~ Tutorial at ICFCA 2023 + +This is a tutorial for the ICFCA 2023. It contains an example analysis of a +formal context with the tool ~conexp-clj~. + +** Basic FCA operations + +*** Getting started + +To run ~conexp-clj~, a Java Runtime Environment with version 1.8 or higher is necessary. +A pre-compiled version of ~conexp-clj~ is available [[https://algebra20.de/conexp/][here]]. The jar file can be used +like this: + +#+begin_src sh :eval never +java -jar conexp-clj-2.3.0-SNAPSHOT-standalone.jar +#+end_src + +A prompt for ~conexp-clj~ like this will appear: + +#+RESULTS +#+begin_src text +conexp.main=> +#+end_src + +*** Read a context + +During the workshop, you can use your own context or, for example, the +[[../../../testing-data/Living-Beings-and-Water.ctx][Living-Beings-and-Water]] or [[../../../testing-data/ben-and-jerrys.ctx][Ben-and-Jerrys]] context. The examples in this tutorial use the +Ben-and-Jerrys ice cream context. + +It is possible to read formal contexts in several formats, e.g., Burmeister and csv. +A more detailed overview of the context formats can be found in [[../../IO.org][Input/Output of Formal Contexts]]. +When reading a context, the format will be automatically determined: + +#+begin_src clojure :results silent +(def ben-and-jerrys-ctx (read-context "path-to-file/ben-and-jerrys.ctx")) +#+end_src + +To see the formal context, evaluate the ~ben-and-jerrys-ctx~ variable explicitly. + +#+begin_src clojure :exports both +ben-and-jerrys-ctx +#+end_src + +The ben-and-jerrys context contains ice cream types as objects and ingredients as +attributes: + +#+RESULTS +#+begin_src text + |Brownie Caramel Caramel Ice Choco Ice Choco Pieces Dough Peanut Butter Peanut Ice Vanilla +-----------------------+------------------------------------------------------------------------------------------ +Caramel Chew Chew |. x x . x . . . . +Caramel Sutra |. x x x x . . . . +Cookie Dough |. . . . x x . . x +Fudge Brownie |x . . x . . . . . +Half Baked |x . . x x x . . x +Peanut Butter Cup |. . . . x . x x . +Salted Caramel Brownie |x x . . x . . . x +#+end_src + +*** Remove objects and attributes + +In case you want to make your formal context smaller (to decrease computation time +for some of the later analysis steps), there are several options. You can create a new +formal context that only contains the objects and attributes that you want to keep. The +new incidence relation is derived from the old one by only considering the object-attribute +pairs of objects and attributes that are contained in the new context. + +#+begin_src clojure :results silent +(def small-ben-and-jerrys-ctx + (make-context + ["Cookie Dough" "Half Baked" "Fudge Brownie"] + ["Brownie" "Choco Ice" "Choco Pieces" "Dough" "Vanilla"] + (fn [A B] + (contains? (incidence-relation ben-and-jerrys-ctx) [A B])))) +#+end_src + +The resulting context of this example is: + +#+begin_src clojure :exports both +small-ben-and-jerrys-ctx +#+end_src + +#+RESULTS +#+begin_src text + |Brownie Choco Ice Choco Pieces Dough Vanilla +--------------+--------------------------------------------- +Cookie Dough |. . x x x +Fudge Brownie |x x . . . +Half Baked |x x x x x +#+end_src + +With the function ~rename-attributes~, it is also possible to rename attributes and +combine several attributes in one, e.g., ~Choco Ice~ and ~Choco Pieces~ in the new +attribute ~Choco~ and ~Peanut Butter~ and ~Peanut Ice~ in ~Peanut~. + +#+begin_src clojure :results silent +(rename-attributes ben-and-jerrys-ctx + {"Brownie" "Brownie" + "Caramel" "Caramel" + "Caramel Ice" "Caramel" + "Choco Ice" "Choco" + "Choco Pieces" "Choco" + "Dough" "Dough" + "Peanut Butter" "Peanut" + "Peanut Ice" "Peanut" + "Vanilla" "Vanilla"}) +#+end_src + +#+RESULTS +#+begin_src text + |Brownie Caramel Choco Dough Peanut Vanilla +-----------------------+------------------------------------------- +Caramel Chew Chew |. x x . . . +Caramel Sutra |. x x . . . +Cookie Dough |. . x x . x +Fudge Brownie |x . x . . . +Half Baked |x . x x . x +Peanut Butter Cup |. . x . x . +Salted Caramel Brownie |x x x . . x +#+end_src + +The same can be applied to objects with ~rename-objects~. + +*** Reduce, clarify + +With ~reduced?~ and ~clarified?~, you can check if a context is reduced or clarified. +The attributes, objects and the whole context can be reduced with ~reduce-attributes~, +~reduce-objects~ and ~reduce-context~. The same applies to ~clarify~. + +The whole example context can be clarified as follows: + +#+begin_src clojure :results silent +(def ben-and-jerrys-clarified + (clarify-context ben-and-jerrys-ctx) +#+end_src + +#+begin_src clojure :exports both +ben-and-jerrys-clarified +#+end_src + +#+RESULTS +#+begin_src text + |Brownie Caramel Caramel Ice Choco Ice Choco Pieces Dough Peanut Ice Vanilla +-----------------------+---------------------------------------------------------------------------- +Caramel Chew Chew |. x x . x . . . +Caramel Sutra |. x x x x . . . +Cookie Dough |. . . . x x . x +Fudge Brownie |x . . x . . . . +Half Baked |x . . x x x . x +Peanut Butter Cup |. . . . x . x . +Salted Caramel Brownie |x x . . x . . x +#+end_src + +As the attributes ~Peanut Butter~ and ~Peanut Ice~ have the same derivation, one of them (in this +case ~Peanut Butter~ is removed. + +*** Compute the concept lattice + +The extents and intents of a formal context can be computed via: + +#+begin_src clojure :export :both +(extents ben-and-jerrys-ctx) +#+end_src + +#+RESULTS +#+begin_src text +(#{} + #{"Half Baked"} + #{"Half Baked" "Cookie Dough"} + #{"Salted Caramel Brownie"} + #{"Salted Caramel Brownie" "Half Baked"} + #{"Salted Caramel Brownie" "Half Baked" "Cookie Dough"} + #{"Caramel Sutra"} + #{"Caramel Sutra" "Half Baked"} + #{"Caramel Sutra" "Caramel Chew Chew"} + #{"Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew"} + #{"Fudge Brownie" "Half Baked"} + #{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked"} + #{"Fudge Brownie" "Caramel Sutra" "Half Baked"} + #{"Peanut Butter Cup"} + #{"Peanut Butter Cup" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} + #{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"}) +#+end_src + +#+begin_src clojure :export :both +(extents ben-and-jerrys-ctx) +#+end_src + +#+RESULTS +#+begin_src text +(#{} + #{"Brownie"} + #{"Choco Pieces"} + #{"Choco Pieces" "Caramel"} + #{"Choco Pieces" "Vanilla"} + #{"Choco Pieces" "Brownie" "Vanilla"} + #{"Choco Pieces" "Brownie" "Vanilla" "Caramel"} + #{"Choco Pieces" "Caramel Ice" "Caramel"} + #{"Choco Pieces" "Dough" "Vanilla"} + #{"Peanut Ice" "Choco Pieces" "Peanut Butter"} + #{"Choco Ice"} + #{"Choco Ice" "Brownie"} + #{"Choco Ice" "Choco Pieces"} + #{"Choco Ice" "Choco Pieces" "Caramel Ice" "Caramel"} + #{"Choco Ice" "Choco Pieces" "Brownie" "Dough" "Vanilla"} + #{"Choco Ice" "Peanut Ice" "Choco Pieces" "Brownie" "Dough" "Peanut Butter" "Caramel Ice" "Vanilla" "Caramel"}) +#+end_src + +In combination, the extents and intents form the formal concepts: + +#+begin_src clojure :export :both +(concepts ben-and-jerrys-ctx) +#+end_src + +#+RESULTS +#+begin_src text +([#{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} #{}] + [#{"Fudge Brownie" "Caramel Sutra" "Half Baked"} #{"Choco Ice"}] + [#{} #{"Choco Ice" "Peanut Ice" "Choco Pieces" "Brownie" "Dough" "Peanut Butter" "Caramel Ice" "Vanilla" "Caramel"}] + [#{"Caramel Sutra" "Half Baked"} #{"Choco Ice" "Choco Pieces"}] + [#{"Half Baked"} #{"Choco Ice" "Choco Pieces" "Brownie" "Dough" "Vanilla"}] + [#{"Caramel Sutra"} #{"Choco Ice" "Choco Pieces" "Caramel Ice" "Caramel"}] + [#{"Fudge Brownie" "Half Baked"} #{"Choco Ice" "Brownie"}] + [#{"Peanut Butter Cup"} #{"Peanut Ice" "Choco Pieces" "Peanut Butter"}] + [#{"Peanut Butter Cup" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} #{"Choco Pieces"}] + [#{"Salted Caramel Brownie" "Half Baked"} #{"Choco Pieces" "Brownie" "Vanilla"}] + [#{"Salted Caramel Brownie"} #{"Choco Pieces" "Brownie" "Vanilla" "Caramel"}] + [#{"Half Baked" "Cookie Dough"} #{"Choco Pieces" "Dough" "Vanilla"}] + [#{"Caramel Sutra" "Caramel Chew Chew"} #{"Choco Pieces" "Caramel Ice" "Caramel"}] + [#{"Salted Caramel Brownie" "Half Baked" "Cookie Dough"} #{"Choco Pieces" "Vanilla"}] + [#{"Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew"} #{"Choco Pieces" "Caramel"}] + [#{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked"} #{"Brownie"}]) +#+end_src + +The concept lattice can be computed via + +#+begin_src clojure :result silent +(def ben-and-jerrys-lattice (concept-lattice ben-and-jerrys-ctx) +#+end_src + +#+begin_src clojure :export :both +ben-and-jerrys-lattice +#+end_src + +#+RESULTS +#+begin_src text +Lattice on 16 elements. +#+end_src + +The lattice consists of a ~base-set~ (~(base-set ben-and-jerrys-lattice)~ contains the +concepts from the previous example) and an ~order~ function. The next section will +explain how to draw a concept lattice. + +*** Draw the concept lattice + +To be able to draw concept lattices, first use this command once: +#+begin_src clojure :results silent +(use 'conexp.gui.draw) + #+end_src + +You can either draw the lattice from the initial context. + +#+begin_src clojure :results silent +(draw-concept-lattice ben-and-jerrys-ctx) +#+end_src + +You can also draw the already computed ~ben-and-jerrys-lattice~. + +#+begin_src clojure :result silent +(draw-lattice ben-and-jerrys-lattice) +#+end_src + +The lattice will appear in a new window. + +#+caption: Concept lattice of ben-and-jerrys context +[[./images/ben-and-jerrys-lattice.png]] + +In left bar of the ~Lattice~ window, you have several options, e.g., you can change the +layout and turn on the labels. In addition, you have the option to show several +valuations, like probability, distributivity and support. + +The ~ben-and-jerrys-lattice~ with DimDraw layout, labels and support looks like this: + +#+caption: Concept lattice of ben-and-jerrys context with DimDraw layout, labels and +support +[[./images/ben-and-jerrys-lattice-dimdraw-labels-support.png]] + +You can also create your own valuations, e.g., the extent and intent size of each formal +concept. + +#+begin_src clojure :result silent +(draw-concept-lattice ben-and-jerrys-ctx + :value-fn (fn [concept] + [(count (first c)) (count (second c))])) +#+end_src + +After enabeling the labels, the concept lattice looks like this: + +#+caption: Concept lattice of ben-and-jerrys context with manually set valuations +[[./images/ben-and-jerrys-lattice-manual_valuations.png]] + +*** Outputs + + +** Scaling data and scale-measures + +** Computing implications + +*** Canonical base + +The canonical base of a context can be computed with: + +#+begin_src clojure :exports both +(canonical-base ben-and-jerrys-ctx) +#+end_src + +#+RESULTS +#+begin_src text +((#{"Caramel"} ⟶ #{"Choco Pieces"}) + (#{"Vanilla"} ⟶ #{"Choco Pieces"}) + (#{"Caramel Ice"} ⟶ #{"Choco Pieces" "Caramel"}) + (#{"Peanut Butter"} ⟶ #{"Peanut Ice" "Choco Pieces"}) + (#{"Dough"} ⟶ #{"Choco Pieces" "Vanilla"}) + (#{"Choco Pieces" "Vanilla" "Caramel"} ⟶ #{"Brownie"}) + (#{"Choco Pieces" "Brownie"} ⟶ #{"Vanilla"}) + (#{"Choco Pieces" "Brownie" "Caramel Ice" "Vanilla" "Caramel"} ⟶ #{"Choco Ice" "Peanut Ice" "Dough" "Peanut Butter"}) + (#{"Choco Pieces" "Brownie" "Dough" "Vanilla"} ⟶ #{"Choco Ice"}) + (#{"Peanut Ice"} ⟶ #{"Choco Pieces" "Peanut Butter"}) + (#{"Peanut Ice" "Choco Pieces" "Peanut Butter" "Caramel"} ⟶ #{"Choco Ice" "Brownie" "Dough" "Caramel Ice" "Vanilla"}) + (#{"Peanut Ice" "Choco Pieces" "Peanut Butter" "Vanilla"} ⟶ #{"Choco Ice" "Brownie" "Dough" "Caramel Ice" "Caramel"}) + (#{"Choco Ice" "Choco Pieces" "Caramel"} ⟶ #{"Caramel Ice"}) + (#{"Choco Ice" "Choco Pieces" "Vanilla"} ⟶ #{"Brownie" "Dough"}) + (#{"Choco Ice" "Peanut Ice" "Choco Pieces" "Peanut Butter"} ⟶ #{"Brownie" "Dough" "Caramel Ice" "Vanilla" "Caramel"})) +#+end_src + + +** Attribute exploration + +~conexp-clj~ offers a function for attribute exploration. + +#+begin_src clojure :results silent +(attribute-exploration :context small-ben-and-jerrys-ctx) +#+end_src + +The following attribute exploration is interactive. For a suggested implication, the +user accepts or rejects it with ~yes~ or ~no~: + +#+begin_src text +Does the implication (#{Vanilla} ⟶ #{Choco Pieces Dough}) hold? no +#+end_src + +If an implication is rejected, a counterexample needs to be provided. First, the object +of the counterexample needs to be given. In this case, we use the "Salted Caramel Brownie" +ice cream type from the original ben-and-jerrys-ctx. + +#+begin_src text +Then please provide a counterexample +counterexample> object +Please enter new object: "Salted Caramel Brownie" +#+end_src + +After that, the attributes of the counterexample are given in the following input format. + +#+begin_src text +counterexample> attributes +Please enter new attributes: "Brownie" "Choco Pieces" "Vanilla" +#+end_src + +The process of providing a counterexample is finished with the input ~q~. It is possible +to give another counterexample. + +#+begin_src text +counterexample> q +Do you want to give another counterexample? no +#+end_src + +The following example shows the attribute exploration of the small-ben-and-jerrys-ctx +with knowledge from the original ben-and-jerrys-ctx. In the end, the attribute exploration +returns the list of learned implications and the new context, which in this case is a +subcontext of the original ben-and-jerrys context. + +#+begin_src text +conexp.main=> (explore-attributes :context small-ben-and-jerrys-ctx) +Does the implication (#{Vanilla} ⟶ #{Choco Pieces Dough}) hold? no +Then please provide a counterexample +counterexample> object +Please enter new object: "Salted Caramel Brownie" +counterexample> attributes +Please enter new attributes: "Brownie" "Choco Pieces" "Vanilla" +counterexample> q +Do you want to give another counterexample? no +Does the implication (#{Vanilla} ⟶ #{Choco Pieces}) hold? yes +Does the implication (#{Dough} ⟶ #{Choco Pieces Vanilla}) hold? yes +Does the implication (#{Choco Pieces} ⟶ #{Vanilla}) hold? no +Then please provide a counterexample +counterexample> object +Please enter new object: "Caramel Chew Chew" +counterexample> attributes +Please enter the attributes the new object should have: "Choco Pieces" +counterexample> q +Do you want to give another counterexample? no +Does the implication (#{Choco Pieces Brownie} ⟶ #{Vanilla}) hold? yes +Does the implication (#{Choco Pieces Brownie Dough Vanilla} ⟶ #{Choco Ice}) hold? yes +Does the implication (#{Choco Ice} ⟶ #{Brownie}) hold? no +Then please provide a counterexample +counterexample> object +Please enter new object: "Caramel Sutra" +counterexample> attributes +Please enter the attributes the new object should have: "Choco Ice" "Choco Pieces" +counterexample> q +Do you want to give another counterexample? no +Does the implication (#{Choco Ice Choco Pieces Vanilla} ⟶ #{Brownie Dough}) hold? yes +{:implications #{(#{"Choco Pieces" "Brownie"} ⟶ #{"Vanilla"}) + (#{"Vanilla"} ⟶ #{"Choco Pieces"}) + (#{"Choco Ice" "Choco Pieces" "Vanilla"} ⟶ #{"Brownie" "Dough"}) + (#{"Choco Pieces" "Brownie" "Dough" "Vanilla"} ⟶ #{"Choco Ice"}) + (#{"Dough"} ⟶ #{"Choco Pieces" "Vanilla"})}, + :context |Brownie Choco Ice Choco Pieces Dough Vanilla +-----------------------+--------------------------------------------- +Caramel Chew Chew |. . x . . +Salted Caramel Brownie |x . x . x +Caramel Sutra |. x x . . +Cookie Dough |. . x x x +Fudge Brownie |x x . . . +Half Baked |x x x x x +} +#+end_src diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-support.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-support.png new file mode 100644 index 0000000000000000000000000000000000000000..803717d9afde8e8bf36d39590af3199920b4353d GIT binary patch literal 130576 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

uKJ|Nn@ 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-manual_valuations.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-manual_valuations.png new file mode 100644 index 0000000000000000000000000000000000000000..6b90de6875516c13d9954f8cf5df3bbd5f6cc37d GIT binary patch literal 129301 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 z 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

U3YJ2l&s^9N@_@q)P3K2p%QHGQu4WfZV$h#zz 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice.png new file mode 100644 index 0000000000000000000000000000000000000000..567b01e60048dd1b3221c56ef3ec5de9c30f701b GIT binary patch literal 87165 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 OaOKi9-^&);2mBjZ;pqPW literal 0 HcmV?d00001 From 56e8071f4f4d039853b762fcd841506533d7f8e8 Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 10 Jul 2023 10:51:00 +0200 Subject: [PATCH 082/112] fix error for drawing lattices with valuations --- src/main/clojure/conexp/gui/draw.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/gui/draw.clj b/src/main/clojure/conexp/gui/draw.clj index 773a1f5e0..1ef806755 100644 --- a/src/main/clojure/conexp/gui/draw.clj +++ b/src/main/clojure/conexp/gui/draw.clj @@ -150,7 +150,8 @@ (defn draw-poset "Draws poset with given layout. Passes all other parameters to draw-layout." [poset & args] - (let [map (apply hash-map 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))) From a5c6b5f89aace1a59041103071d54296c4de04ae Mon Sep 17 00:00:00 2001 From: Jana Date: Wed, 12 Jul 2023 10:47:26 +0200 Subject: [PATCH 083/112] update tutorial --- .../icfca-2023/icfca-2023-tutorial.org | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org index 23d72c314..856ffd3fa 100644 --- a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org +++ b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org @@ -33,6 +33,16 @@ Ben-and-Jerrys ice cream context. It is possible to read formal contexts in several formats, e.g., Burmeister and csv. A more detailed overview of the context formats can be found in [[../../IO.org][Input/Output of Formal Contexts]]. +In addition, you can show all input formats with + +#+begin_src clojure :exports both +(list-context-input-formats) +#+end_src + +#+begin_src text +(:burmeister :csv :conexp :named-binary-csv :anonymous-burmeister :graphml :simple :binary-csv :fcalgs :colibri :json :galicia) +#+end_src + When reading a context, the format will be automatically determined: #+begin_src clojure :results silent @@ -187,7 +197,7 @@ The extents and intents of a formal context can be computed via: #+end_src #+begin_src clojure :export :both -(extents ben-and-jerrys-ctx) +(intents ben-and-jerrys-ctx) #+end_src #+RESULTS @@ -260,7 +270,7 @@ explain how to draw a concept lattice. To be able to draw concept lattices, first use this command once: #+begin_src clojure :results silent (use 'conexp.gui.draw) - #+end_src +#+end_src You can either draw the lattice from the initial context. @@ -306,8 +316,6 @@ After enabeling the labels, the concept lattice looks like this: *** Outputs -** Scaling data and scale-measures - ** Computing implications *** Canonical base @@ -338,6 +346,8 @@ The canonical base of a context can be computed with: #+end_src +** Scaling data and scale-measures + ** Attribute exploration ~conexp-clj~ offers a function for attribute exploration. From 9b824ac62c6ce9d7f4b9eb359e0539f29c9ee961 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:08:40 +0200 Subject: [PATCH 084/112] fix error for drawing lattices with valuations (#117) --- src/main/clojure/conexp/gui/draw.clj | 3 ++- src/test/clojure/conexp/gui/draw_test.clj | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/conexp/gui/draw.clj b/src/main/clojure/conexp/gui/draw.clj index 773a1f5e0..1ef806755 100644 --- a/src/main/clojure/conexp/gui/draw.clj +++ b/src/main/clojure/conexp/gui/draw.clj @@ -150,7 +150,8 @@ (defn draw-poset "Draws poset with given layout. Passes all other parameters to draw-layout." [poset & args] - (let [map (apply hash-map 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))) diff --git a/src/test/clojure/conexp/gui/draw_test.clj b/src/test/clojure/conexp/gui/draw_test.clj index 021ab3577..9ec2d13a1 100644 --- a/src/test/clojure/conexp/gui/draw_test.clj +++ b/src/test/clojure/conexp/gui/draw_test.clj @@ -29,6 +29,12 @@ (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] From 1529724c5d1ec143d335ea20ac6b3e498e65de39 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:13:00 +0200 Subject: [PATCH 085/112] 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 --- AUTHORS.md | 2 +- 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 | 27 +- src/main/clojure/conexp/fca/fast.clj | 73 +- src/main/clojure/conexp/fca/pqcores.clj | 67 -- src/main/clojure/conexp/fca/smeasure.clj | 705 ++++++++++++++++++ src/main/clojure/conexp/io/smeasure.clj | 323 ++++++++ .../conexp/fca/concept_transform_test.clj | 25 + src/test/clojure/conexp/fca/contexts_test.clj | 2 +- src/test/clojure/conexp/fca/pqcores_test.clj | 13 - src/test/clojure/conexp/fca/smeasure_test.clj | 516 +++++++++++++ src/test/clojure/conexp/io/smeasure_test.clj | 50 ++ 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-smeasure1-2.tex | 48 ++ testing-data/latex/tikz-smeasure1-3.tex | 60 ++ testing-data/latex/tikz-smeasure3-1.tex | 65 ++ 20 files changed, 2310 insertions(+), 98 deletions(-) create mode 100644 src/main/clojure/conexp/fca/concept_transform.clj create mode 100644 src/main/clojure/conexp/fca/smeasure.clj create mode 100644 src/main/clojure/conexp/io/smeasure.clj create mode 100644 src/test/clojure/conexp/fca/concept_transform_test.clj create mode 100644 src/test/clojure/conexp/fca/smeasure_test.clj create mode 100644 src/test/clojure/conexp/io/smeasure_test.clj 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 diff --git a/AUTHORS.md b/AUTHORS.md index 84edbb8ae..332175d75 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -13,7 +13,7 @@ Additional Contributors are * 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) 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 0226190d1..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 > 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/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/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/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/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/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/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-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} From c34671ebc44c043b489bf11232abc3bcd0172c7e Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 17 Jul 2023 07:53:38 +0200 Subject: [PATCH 086/112] tutorial finished --- .../icfca-2023/icfca-2023-tutorial.org | 473 ++++++++++++------ testing-data/ben-and-jerrys-allergens.ctx | 26 + testing-data/ben-and-jerrys-flavors-small.ctx | 25 + ...-jerrys.ctx => ben-and-jerrys-flavors.ctx} | 0 4 files changed, 363 insertions(+), 161 deletions(-) create mode 100644 testing-data/ben-and-jerrys-allergens.ctx create mode 100644 testing-data/ben-and-jerrys-flavors-small.ctx rename testing-data/{ben-and-jerrys.ctx => ben-and-jerrys-flavors.ctx} (100%) diff --git a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org index 856ffd3fa..3fc29edbf 100644 --- a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org +++ b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org @@ -28,7 +28,7 @@ conexp.main=> *** Read a context During the workshop, you can use your own context or, for example, the -[[../../../testing-data/Living-Beings-and-Water.ctx][Living-Beings-and-Water]] or [[../../../testing-data/ben-and-jerrys.ctx][Ben-and-Jerrys]] context. The examples in this tutorial use the +[[../../../testing-data/Living-Beings-and-Water.ctx][Living-Beings-and-Water]] or [[../../../testing-data/ben-and-jerrys-flavors.ctx][Ben-and-Jerrys]] context. The examples in this tutorial use the Ben-and-Jerrys ice cream context. It is possible to read formal contexts in several formats, e.g., Burmeister and csv. @@ -46,7 +46,7 @@ In addition, you can show all input formats with When reading a context, the format will be automatically determined: #+begin_src clojure :results silent -(def ben-and-jerrys-ctx (read-context "path-to-file/ben-and-jerrys.ctx")) +(def ben-and-jerrys-ctx (read-context "path-to-file/ben-and-jerrys-flavors.ctx")) #+end_src To see the formal context, evaluate the ~ben-and-jerrys-ctx~ variable explicitly. @@ -71,70 +71,6 @@ Peanut Butter Cup |. . . . x . Salted Caramel Brownie |x x . . x . . . x #+end_src -*** Remove objects and attributes - -In case you want to make your formal context smaller (to decrease computation time -for some of the later analysis steps), there are several options. You can create a new -formal context that only contains the objects and attributes that you want to keep. The -new incidence relation is derived from the old one by only considering the object-attribute -pairs of objects and attributes that are contained in the new context. - -#+begin_src clojure :results silent -(def small-ben-and-jerrys-ctx - (make-context - ["Cookie Dough" "Half Baked" "Fudge Brownie"] - ["Brownie" "Choco Ice" "Choco Pieces" "Dough" "Vanilla"] - (fn [A B] - (contains? (incidence-relation ben-and-jerrys-ctx) [A B])))) -#+end_src - -The resulting context of this example is: - -#+begin_src clojure :exports both -small-ben-and-jerrys-ctx -#+end_src - -#+RESULTS -#+begin_src text - |Brownie Choco Ice Choco Pieces Dough Vanilla ---------------+--------------------------------------------- -Cookie Dough |. . x x x -Fudge Brownie |x x . . . -Half Baked |x x x x x -#+end_src - -With the function ~rename-attributes~, it is also possible to rename attributes and -combine several attributes in one, e.g., ~Choco Ice~ and ~Choco Pieces~ in the new -attribute ~Choco~ and ~Peanut Butter~ and ~Peanut Ice~ in ~Peanut~. - -#+begin_src clojure :results silent -(rename-attributes ben-and-jerrys-ctx - {"Brownie" "Brownie" - "Caramel" "Caramel" - "Caramel Ice" "Caramel" - "Choco Ice" "Choco" - "Choco Pieces" "Choco" - "Dough" "Dough" - "Peanut Butter" "Peanut" - "Peanut Ice" "Peanut" - "Vanilla" "Vanilla"}) -#+end_src - -#+RESULTS -#+begin_src text - |Brownie Caramel Choco Dough Peanut Vanilla ------------------------+------------------------------------------- -Caramel Chew Chew |. x x . . . -Caramel Sutra |. x x . . . -Cookie Dough |. . x x . x -Fudge Brownie |x . x . . . -Half Baked |x . x x . x -Peanut Butter Cup |. . x . x . -Salted Caramel Brownie |x x x . . x -#+end_src - -The same can be applied to objects with ~rename-objects~. - *** Reduce, clarify With ~reduced?~ and ~clarified?~, you can check if a context is reduced or clarified. @@ -168,59 +104,39 @@ Salted Caramel Brownie |x x . . x . As the attributes ~Peanut Butter~ and ~Peanut Ice~ have the same derivation, one of them (in this case ~Peanut Butter~ is removed. -*** Compute the concept lattice +*** Compute derivations -The extents and intents of a formal context can be computed via: +~conexp-clj~ provides functions to compute the attribute and object derivation. +In the following example, the object derivation of two types of ice cream is +computed (to see what they have in common): #+begin_src clojure :export :both -(extents ben-and-jerrys-ctx) +(object-derivation ben-and-jerrys-ctx #{"Cookie Dough" "Half Baked"}) #+end_src #+RESULTS #+begin_src text -(#{} - #{"Half Baked"} - #{"Half Baked" "Cookie Dough"} - #{"Salted Caramel Brownie"} - #{"Salted Caramel Brownie" "Half Baked"} - #{"Salted Caramel Brownie" "Half Baked" "Cookie Dough"} - #{"Caramel Sutra"} - #{"Caramel Sutra" "Half Baked"} - #{"Caramel Sutra" "Caramel Chew Chew"} - #{"Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew"} - #{"Fudge Brownie" "Half Baked"} - #{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked"} - #{"Fudge Brownie" "Caramel Sutra" "Half Baked"} - #{"Peanut Butter Cup"} - #{"Peanut Butter Cup" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} - #{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"}) +#{"Choco Pieces" "Dough" "Vanilla"} #+end_src +The same can be done for a set of attributes with ~attribute-derivation~. + +To compute the closure of a set of objects, you can use + #+begin_src clojure :export :both -(intents ben-and-jerrys-ctx) +(context-object-closure ben-and-jerrys-ctx #{"Caramel Chew Chew"}) #+end_src #+RESULTS #+begin_src text -(#{} - #{"Brownie"} - #{"Choco Pieces"} - #{"Choco Pieces" "Caramel"} - #{"Choco Pieces" "Vanilla"} - #{"Choco Pieces" "Brownie" "Vanilla"} - #{"Choco Pieces" "Brownie" "Vanilla" "Caramel"} - #{"Choco Pieces" "Caramel Ice" "Caramel"} - #{"Choco Pieces" "Dough" "Vanilla"} - #{"Peanut Ice" "Choco Pieces" "Peanut Butter"} - #{"Choco Ice"} - #{"Choco Ice" "Brownie"} - #{"Choco Ice" "Choco Pieces"} - #{"Choco Ice" "Choco Pieces" "Caramel Ice" "Caramel"} - #{"Choco Ice" "Choco Pieces" "Brownie" "Dough" "Vanilla"} - #{"Choco Ice" "Peanut Ice" "Choco Pieces" "Brownie" "Dough" "Peanut Butter" "Caramel Ice" "Vanilla" "Caramel"}) +#{"Caramel Sutra" "Caramel Chew Chew"} #+end_src -In combination, the extents and intents form the formal concepts: +The closure of a set of attributes can be computed with ~context-attribute-closure~. + +*** Compute the concept lattice + +The formal concepts of the context can be computed as #+begin_src clojure :export :both (concepts ben-and-jerrys-ctx) @@ -246,7 +162,7 @@ In combination, the extents and intents form the formal concepts: [#{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked"} #{"Brownie"}]) #+end_src -The concept lattice can be computed via +The concept lattice, consisting of the concepts and their order, can be computed via #+begin_src clojure :result silent (def ben-and-jerrys-lattice (concept-lattice ben-and-jerrys-ctx) @@ -262,7 +178,7 @@ Lattice on 16 elements. #+end_src The lattice consists of a ~base-set~ (~(base-set ben-and-jerrys-lattice)~ contains the -concepts from the previous example) and an ~order~ function. The next section will +concepts from the previous output) and an ~order~ function. The next section will explain how to draw a concept lattice. *** Draw the concept lattice @@ -313,17 +229,16 @@ After enabeling the labels, the concept lattice looks like this: #+caption: Concept lattice of ben-and-jerrys context with manually set valuations [[./images/ben-and-jerrys-lattice-manual_valuations.png]] -*** Outputs - - -** Computing implications - -*** Canonical base +*** Computing implications - Canonical base The canonical base of a context can be computed with: +#+begin_src clojure :result silent +(def ben-and-jerrys-implications (canonical-base ben-and-jerrys-ctx)) +#+end_src + #+begin_src clojure :exports both -(canonical-base ben-and-jerrys-ctx) +ben-and-jerrys-implications #+end_src #+RESULTS @@ -345,39 +260,165 @@ The canonical base of a context can be computed with: (#{"Choco Ice" "Peanut Ice" "Choco Pieces" "Peanut Butter"} ⟶ #{"Brownie" "Dough" "Caramel Ice" "Vanilla" "Caramel"})) #+end_src +*** Outputs + +Depending on the size of the contexts, the computation of the concept can take a long time. +Therefore, the results can be saved so that the computation does not need to be repeated. +For the output, the format needs to be specified. The formats to save a concept lattice are + +#+begin_src clojure :export both +(list-lattice-output-formats) +#+end_src + +#+RESULTS +#+begin_src text +(:simple :json) +#+end_src + +A concept lattice can be saved in the ~:json~ format with the following command + +#+begin_src clojure :result silent +(write-lattice :json ben-and-jerrys-lattice "path/ben-and-jerrys-lattice.json") +#+end_src + +(It can be loaded again with ~(read-lattice "path/ben-and-jerrys-lattice.json")~.) + +For implications, there is only the ~:json~ output format. Implications can be saved via + +#+begin_src clojure :result silent +(write-implication :json ben-and-jerrys-implications "path/ben-and-jerrys-implications.json") +#+end_src + +~conexp-clj~ also provides the option to save a whole Formal Concept Analysis in one +file (in ~:json~) format. This FCA needs to contain a formal context. The ~:lattice~ +and ~:implication-sets~ in the following map are optional. + +#+begin_src clojure :result silent +(def ben-and-jerrys-fca {:context ben-and-jerrys-context + :lattice ben-and-jerrys-lattice + :implication-sets [ben-and-jerrys-implications]}) +#+end_src + +Note that such an FCA object can contain several implication sets. The +~ben-and-jerrys-fca~ can be saved with + +#+begin_src clojure :result silent +(write-fca :json ben-and-jerrys-fca "path/ben-and-jerrys-fca.json") +#+end_src + ** Scaling data and scale-measures +~conexp-clj~ provides the functionality for conceptual scaling. For an example, +load the smaller ~ben-and-jerrys-flavors-small.ctx~: + +#+begin_src clojure :results silent +(def ben-and-jerrys-small-ctx (read-context "path-to-file/ben-and-jerrys-flavors-small.ctx")) +#+end_src + +#+begin_src clojure :exports both +ben-and-jerrys-small-ctx +#+end_src + +The ben-and-jerrys context contains the same ice cream types as objects, but a smaller +set of flavors as attributes: + +#+RESULTS +#+begin_src text + |Brownie Caramel Choco Dough Peanut Vanilla +-----------------------+------------------------------------------- +Caramel Chew Chew |. x x . . . +Caramel Sutra |. x x . . . +Cookie Dough |. . x x . x +Fudge Brownie |x . x . . . +Half Baked |x . x x . x +Peanut Butter Cup |. . x . x . +Salted Caramel Brownie |x x x . . x +#+end_src + +To check if this smaller context is a scale of the ~ben-and-jerrys-ctx~, the conceptual +scaling error is computed: + +#+begin_src clojure :exports both +(use 'conexp.fca.smeasure) +(conceptual-scaling-error (make-smeasure-nc ben-and-jerrys-ctx ben-and-jerrys-small-ctx identity)) +#+end_src + +#+RESULTS +#+begin_src text +0 +#+end_src + +As the error is 0, the ~ben-and-jerrys-small-ctx~ is a scale of the ~ben-and-jerrys-ctx~. + +Another context uses the same ice cream types, but allergens instead of flavors. + +#+begin_src clojure :results silent +(def ben-and-jerrys-allergens-ctx (read-context "path-to-file/ben-and-jerrys-allergens.ctx")) +#+end_src + +#+begin_src clojure :exports both +ben-and-jerrys-allergens-ctx +#+end_src + +#+RESULTS +#+begin_src text + |almond barley egg milk peanuts soy wheat +-----------------------+----------------------------------------- +Caramel Chew Chew |. . x x . x . +Caramel Sutra |x . x x . x . +Cookie Dough |. . x x . x x +Fudge Brownie |. x x x . . x +Half Baked |. x x x . x x +Peanut Butter Cup |. . x x x x . +Salted Caramel Brownie |. . x x . x x +#+end_src + +You can compute the conceptual scaling error in the same way as for the ~ben-and-jerrys-small-ctx~. + +#+begin_src clojure :exports both +(conceptual-scaling-error (make-smeasure-nc ben-and-jerrys-ctx ben-and-jerrys-allergens-ctx identity)) +#+end_src + +#+RESULTS +#+begin_src text +1 +#+end_src + +In this case, the error is 1 and therefore the allergens context is not a scale of the +original ~ben-and-jerrys-ctx~. + + ** Attribute exploration -~conexp-clj~ offers a function for attribute exploration. +~conexp-clj~ provides a function for attribute exploration. #+begin_src clojure :results silent -(attribute-exploration :context small-ben-and-jerrys-ctx) +(attribute-exploration :context ben-and-jerrys-small-ctx) #+end_src The following attribute exploration is interactive. For a suggested implication, the user accepts or rejects it with ~yes~ or ~no~: #+begin_src text -Does the implication (#{Vanilla} ⟶ #{Choco Pieces Dough}) hold? no +Does the implication (#{} ⟶ #{Choco}) hold? no #+end_src If an implication is rejected, a counterexample needs to be provided. First, the object -of the counterexample needs to be given. In this case, we use the "Salted Caramel Brownie" -ice cream type from the original ben-and-jerrys-ctx. +of the counterexample needs to be given. In this case, we give an additional "Peanut" +ice cream. #+begin_src text Then please provide a counterexample counterexample> object -Please enter new object: "Salted Caramel Brownie" +Please enter new object: "Peanut" #+end_src After that, the attributes of the counterexample are given in the following input format. #+begin_src text counterexample> attributes -Please enter new attributes: "Brownie" "Choco Pieces" "Vanilla" +Please enter new attributes: "Peanut" "Vanilla" #+end_src The process of providing a counterexample is finished with the input ~q~. It is possible @@ -388,54 +429,164 @@ counterexample> q Do you want to give another counterexample? no #+end_src -The following example shows the attribute exploration of the small-ben-and-jerrys-ctx -with knowledge from the original ben-and-jerrys-ctx. In the end, the attribute exploration -returns the list of learned implications and the new context, which in this case is a -subcontext of the original ben-and-jerrys context. +The following example shows an attribute exploration of the ~ben-and-jerrys-small-ctx~. +In the end, the attribute exploration returns the list of learned implications and the +new context. #+begin_src text -conexp.main=> (explore-attributes :context small-ben-and-jerrys-ctx) -Does the implication (#{Vanilla} ⟶ #{Choco Pieces Dough}) hold? no -Then please provide a counterexample -counterexample> object -Please enter new object: "Salted Caramel Brownie" -counterexample> attributes -Please enter new attributes: "Brownie" "Choco Pieces" "Vanilla" -counterexample> q -Do you want to give another counterexample? no -Does the implication (#{Vanilla} ⟶ #{Choco Pieces}) hold? yes -Does the implication (#{Dough} ⟶ #{Choco Pieces Vanilla}) hold? yes -Does the implication (#{Choco Pieces} ⟶ #{Vanilla}) hold? no -Then please provide a counterexample -counterexample> object -Please enter new object: "Caramel Chew Chew" -counterexample> attributes -Please enter the attributes the new object should have: "Choco Pieces" -counterexample> q -Do you want to give another counterexample? no -Does the implication (#{Choco Pieces Brownie} ⟶ #{Vanilla}) hold? yes -Does the implication (#{Choco Pieces Brownie Dough Vanilla} ⟶ #{Choco Ice}) hold? yes -Does the implication (#{Choco Ice} ⟶ #{Brownie}) hold? no +conexp.main=> (explore-attributes :context ben-and-jerrys-small-ctx) +Does the implication (#{} ⟶ #{Choco}) hold? no Then please provide a counterexample counterexample> object -Please enter new object: "Caramel Sutra" +Please enter new object: "Peanut" counterexample> attributes -Please enter the attributes the new object should have: "Choco Ice" "Choco Pieces" +Please enter the attributes the new object should have: "Peanut" "Vanilla" counterexample> q Do you want to give another counterexample? no -Does the implication (#{Choco Ice Choco Pieces Vanilla} ⟶ #{Brownie Dough}) hold? yes -{:implications #{(#{"Choco Pieces" "Brownie"} ⟶ #{"Vanilla"}) - (#{"Vanilla"} ⟶ #{"Choco Pieces"}) - (#{"Choco Ice" "Choco Pieces" "Vanilla"} ⟶ #{"Brownie" "Dough"}) - (#{"Choco Pieces" "Brownie" "Dough" "Vanilla"} ⟶ #{"Choco Ice"}) - (#{"Dough"} ⟶ #{"Choco Pieces" "Vanilla"})}, - :context |Brownie Choco Ice Choco Pieces Dough Vanilla ------------------------+--------------------------------------------- -Caramel Chew Chew |. . x . . -Salted Caramel Brownie |x . x . x -Caramel Sutra |. x x . . -Cookie Dough |. . x x x -Fudge Brownie |x x . . . -Half Baked |x x x x x +Does the implication (#{Caramel} ⟶ #{Choco}) hold? yes +Does the implication (#{Dough} ⟶ #{Choco Vanilla}) hold? yes +Does the implication (#{Brownie} ⟶ #{Choco}) hold? yes +Does the implication (#{Choco Vanilla Caramel} ⟶ #{Brownie}) hold? yes +Does the implication (#{Choco Peanut Caramel} ⟶ #{Brownie Dough Vanilla}) hold? yes +Does the implication (#{Choco Peanut Vanilla} ⟶ #{Brownie Dough Caramel}) hold? yes +Does the implication (#{Choco Brownie Caramel} ⟶ #{Vanilla}) hold? yes +Does the implication (#{Choco Brownie Peanut} ⟶ #{Dough Vanilla Caramel}) hold? yes +Does the implication (#{Choco Brownie Dough Vanilla Caramel} ⟶ #{Peanut}) hold? yes +{:implications #{(#{"Choco" "Peanut" "Caramel"} ⟶ #{"Brownie" "Dough" "Vanilla"}) + (#{"Brownie"} ⟶ #{"Choco"}) + (#{"Choco" "Brownie" "Caramel"} ⟶ #{"Vanilla"}) + (#{"Caramel"} ⟶ #{"Choco"}) + (#{"Choco" "Vanilla" "Caramel"} ⟶ #{"Brownie"}) + (#{"Choco" "Brownie" "Dough" "Vanilla" "Caramel"} ⟶ #{"Peanut"}) + (#{"Choco" "Peanut" "Vanilla"} ⟶ #{"Brownie" "Dough" "Caramel"}) + (#{"Dough"} ⟶ #{"Choco" "Vanilla"}) + (#{"Choco" "Brownie" "Peanut"} ⟶ #{"Dough" "Vanilla" "Caramel"})}, +:context |Brownie Caramel Choco Dough Peanut Vanilla +-----------------------+------------------------------------------- +Caramel Chew Chew |. x x . . . +Caramel Sutra |. x x . . . +Cookie Dough |. . x x . x +Fudge Brownie |x . x . . . +Half Baked |x . x x . x +Peanut Butter Cup |. . x . x . +Peanut |. . . . x x +Salted Caramel Brownie |x x x . . x } #+end_src + + +** optional +*** Remove objects and attributes + +In case you want to make your formal context smaller (to decrease computation time +for some of the later analysis steps), there are several options. You can create a new +formal context that only contains the objects and attributes that you want to keep. The +new incidence relation is derived from the old one by only considering the object-attribute +pairs of objects and attributes that are contained in the new context. + +#+begin_src clojure :results silent +(def small-ben-and-jerrys-ctx + (make-context + ["Cookie Dough" "Half Baked" "Fudge Brownie"] + ["Brownie" "Choco Ice" "Choco Pieces" "Dough" "Vanilla"] + (fn [A B] + (contains? (incidence-relation ben-and-jerrys-ctx) [A B])))) +#+end_src + +The resulting context of this example is: + +#+begin_src clojure :exports both +small-ben-and-jerrys-ctx +#+end_src + +#+RESULTS +#+begin_src text + |Brownie Choco Ice Choco Pieces Dough Vanilla +--------------+--------------------------------------------- +Cookie Dough |. . x x x +Fudge Brownie |x x . . . +Half Baked |x x x x x +#+end_src + +With the function ~rename-attributes~, it is also possible to rename attributes and +combine several attributes in one, e.g., ~Choco Ice~ and ~Choco Pieces~ in the new +attribute ~Choco~ and ~Peanut Butter~ and ~Peanut Ice~ in ~Peanut~. + +#+begin_src clojure :results silent +(rename-attributes ben-and-jerrys-ctx + {"Brownie" "Brownie" + "Caramel" "Caramel" + "Caramel Ice" "Caramel" + "Choco Ice" "Choco" + "Choco Pieces" "Choco" + "Dough" "Dough" + "Peanut Butter" "Peanut" + "Peanut Ice" "Peanut" + "Vanilla" "Vanilla"}) +#+end_src + +#+RESULTS +#+begin_src text + |Brownie Caramel Choco Dough Peanut Vanilla +-----------------------+------------------------------------------- +Caramel Chew Chew |. x x . . . +Caramel Sutra |. x x . . . +Cookie Dough |. . x x . x +Fudge Brownie |x . x . . . +Half Baked |x . x x . x +Peanut Butter Cup |. . x . x . +Salted Caramel Brownie |x x x . . x +#+end_src + +The same can be applied to objects with ~rename-objects~. + +*** Extents and intents +The extents and intents of a formal context can be computed via: + +#+begin_src clojure :export :both +(extents ben-and-jerrys-ctx) +#+end_src + +#+RESULTS +#+begin_src text +(#{} + #{"Half Baked"} + #{"Half Baked" "Cookie Dough"} + #{"Salted Caramel Brownie"} + #{"Salted Caramel Brownie" "Half Baked"} + #{"Salted Caramel Brownie" "Half Baked" "Cookie Dough"} + #{"Caramel Sutra"} + #{"Caramel Sutra" "Half Baked"} + #{"Caramel Sutra" "Caramel Chew Chew"} + #{"Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew"} + #{"Fudge Brownie" "Half Baked"} + #{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked"} + #{"Fudge Brownie" "Caramel Sutra" "Half Baked"} + #{"Peanut Butter Cup"} + #{"Peanut Butter Cup" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} + #{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"}) +#+end_src + +#+begin_src clojure :export :both +(intents ben-and-jerrys-ctx) +#+end_src + +#+RESULTS +#+begin_src text +(#{} + #{"Brownie"} + #{"Choco Pieces"} + #{"Choco Pieces" "Caramel"} + #{"Choco Pieces" "Vanilla"} + #{"Choco Pieces" "Brownie" "Vanilla"} + #{"Choco Pieces" "Brownie" "Vanilla" "Caramel"} + #{"Choco Pieces" "Caramel Ice" "Caramel"} + #{"Choco Pieces" "Dough" "Vanilla"} + #{"Peanut Ice" "Choco Pieces" "Peanut Butter"} + #{"Choco Ice"} + #{"Choco Ice" "Brownie"} + #{"Choco Ice" "Choco Pieces"} + #{"Choco Ice" "Choco Pieces" "Caramel Ice" "Caramel"} + #{"Choco Ice" "Choco Pieces" "Brownie" "Dough" "Vanilla"} + #{"Choco Ice" "Peanut Ice" "Choco Pieces" "Brownie" "Dough" "Peanut Butter" "Caramel Ice" "Vanilla" "Caramel"}) +#+end_src diff --git a/testing-data/ben-and-jerrys-allergens.ctx b/testing-data/ben-and-jerrys-allergens.ctx new file mode 100644 index 000000000..87bb90c42 --- /dev/null +++ b/testing-data/ben-and-jerrys-allergens.ctx @@ -0,0 +1,26 @@ +B + +7 +7 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +barley +milk +peanuts +almond +wheat +egg +soy +.XX..XX +XX..XX. +.X.X.XX +.X..XXX +.X...XX +XX..XXX +.X..XXX diff --git a/testing-data/ben-and-jerrys-flavors-small.ctx b/testing-data/ben-and-jerrys-flavors-small.ctx new file mode 100644 index 000000000..42125ebc9 --- /dev/null +++ b/testing-data/ben-and-jerrys-flavors-small.ctx @@ -0,0 +1,25 @@ +B + +7 +6 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +Choco +Brownie +Dough +Peanut +Vanilla +Caramel +X..X.. +XX.... +X....X +XX..XX +X....X +XXX.X. +X.X.X. diff --git a/testing-data/ben-and-jerrys.ctx b/testing-data/ben-and-jerrys-flavors.ctx similarity index 100% rename from testing-data/ben-and-jerrys.ctx rename to testing-data/ben-and-jerrys-flavors.ctx From 5e80265d1734777327d4200dab1d3412be27728a Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 17 Jul 2023 07:54:04 +0200 Subject: [PATCH 087/112] tutorial cleanup --- .../icfca-2023/icfca-2023-tutorial.org | 117 ------------------ 1 file changed, 117 deletions(-) diff --git a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org index 3fc29edbf..a3f6cd1d9 100644 --- a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org +++ b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org @@ -473,120 +473,3 @@ Peanut |. . . . x x Salted Caramel Brownie |x x x . . x } #+end_src - - -** optional -*** Remove objects and attributes - -In case you want to make your formal context smaller (to decrease computation time -for some of the later analysis steps), there are several options. You can create a new -formal context that only contains the objects and attributes that you want to keep. The -new incidence relation is derived from the old one by only considering the object-attribute -pairs of objects and attributes that are contained in the new context. - -#+begin_src clojure :results silent -(def small-ben-and-jerrys-ctx - (make-context - ["Cookie Dough" "Half Baked" "Fudge Brownie"] - ["Brownie" "Choco Ice" "Choco Pieces" "Dough" "Vanilla"] - (fn [A B] - (contains? (incidence-relation ben-and-jerrys-ctx) [A B])))) -#+end_src - -The resulting context of this example is: - -#+begin_src clojure :exports both -small-ben-and-jerrys-ctx -#+end_src - -#+RESULTS -#+begin_src text - |Brownie Choco Ice Choco Pieces Dough Vanilla ---------------+--------------------------------------------- -Cookie Dough |. . x x x -Fudge Brownie |x x . . . -Half Baked |x x x x x -#+end_src - -With the function ~rename-attributes~, it is also possible to rename attributes and -combine several attributes in one, e.g., ~Choco Ice~ and ~Choco Pieces~ in the new -attribute ~Choco~ and ~Peanut Butter~ and ~Peanut Ice~ in ~Peanut~. - -#+begin_src clojure :results silent -(rename-attributes ben-and-jerrys-ctx - {"Brownie" "Brownie" - "Caramel" "Caramel" - "Caramel Ice" "Caramel" - "Choco Ice" "Choco" - "Choco Pieces" "Choco" - "Dough" "Dough" - "Peanut Butter" "Peanut" - "Peanut Ice" "Peanut" - "Vanilla" "Vanilla"}) -#+end_src - -#+RESULTS -#+begin_src text - |Brownie Caramel Choco Dough Peanut Vanilla ------------------------+------------------------------------------- -Caramel Chew Chew |. x x . . . -Caramel Sutra |. x x . . . -Cookie Dough |. . x x . x -Fudge Brownie |x . x . . . -Half Baked |x . x x . x -Peanut Butter Cup |. . x . x . -Salted Caramel Brownie |x x x . . x -#+end_src - -The same can be applied to objects with ~rename-objects~. - -*** Extents and intents -The extents and intents of a formal context can be computed via: - -#+begin_src clojure :export :both -(extents ben-and-jerrys-ctx) -#+end_src - -#+RESULTS -#+begin_src text -(#{} - #{"Half Baked"} - #{"Half Baked" "Cookie Dough"} - #{"Salted Caramel Brownie"} - #{"Salted Caramel Brownie" "Half Baked"} - #{"Salted Caramel Brownie" "Half Baked" "Cookie Dough"} - #{"Caramel Sutra"} - #{"Caramel Sutra" "Half Baked"} - #{"Caramel Sutra" "Caramel Chew Chew"} - #{"Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew"} - #{"Fudge Brownie" "Half Baked"} - #{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked"} - #{"Fudge Brownie" "Caramel Sutra" "Half Baked"} - #{"Peanut Butter Cup"} - #{"Peanut Butter Cup" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} - #{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"}) -#+end_src - -#+begin_src clojure :export :both -(intents ben-and-jerrys-ctx) -#+end_src - -#+RESULTS -#+begin_src text -(#{} - #{"Brownie"} - #{"Choco Pieces"} - #{"Choco Pieces" "Caramel"} - #{"Choco Pieces" "Vanilla"} - #{"Choco Pieces" "Brownie" "Vanilla"} - #{"Choco Pieces" "Brownie" "Vanilla" "Caramel"} - #{"Choco Pieces" "Caramel Ice" "Caramel"} - #{"Choco Pieces" "Dough" "Vanilla"} - #{"Peanut Ice" "Choco Pieces" "Peanut Butter"} - #{"Choco Ice"} - #{"Choco Ice" "Brownie"} - #{"Choco Ice" "Choco Pieces"} - #{"Choco Ice" "Choco Pieces" "Caramel Ice" "Caramel"} - #{"Choco Ice" "Choco Pieces" "Brownie" "Dough" "Vanilla"} - #{"Choco Ice" "Peanut Ice" "Choco Pieces" "Brownie" "Dough" "Peanut Butter" "Caramel Ice" "Vanilla" "Caramel"}) -#+end_src From 7e1e20acd0b6c2eef7047038df8c7b7d1f0ff5e0 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:39:16 +0200 Subject: [PATCH 088/112] Update icfca-2023-tutorial.org --- .../icfca-2023/icfca-2023-tutorial.org | 174 ++++++++++-------- 1 file changed, 95 insertions(+), 79 deletions(-) diff --git a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org index a3f6cd1d9..6f932a840 100644 --- a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org +++ b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org @@ -14,13 +14,13 @@ To run ~conexp-clj~, a Java Runtime Environment with version 1.8 or higher is ne A pre-compiled version of ~conexp-clj~ is available [[https://algebra20.de/conexp/][here]]. The jar file can be used like this: -#+begin_src sh :eval never +#+begin_src sh :exports both java -jar conexp-clj-2.3.0-SNAPSHOT-standalone.jar #+end_src A prompt for ~conexp-clj~ like this will appear: -#+RESULTS +#+RESULTS: #+begin_src text conexp.main=> #+end_src @@ -32,21 +32,27 @@ During the workshop, you can use your own context or, for example, the Ben-and-Jerrys ice cream context. It is possible to read formal contexts in several formats, e.g., Burmeister and csv. -A more detailed overview of the context formats can be found in [[../../IO.org][Input/Output of Formal Contexts]]. -In addition, you can show all input formats with +A more detailed overview of the context formats can be found in [[../../IO.org][Input/Output of Formal Contexts]]. When reading a context, in most cases the format will be automatically determined: + +#+begin_src clojure :results silent +(def ben-and-jerrys-ctx (read-context "path-to-file/ben-and-jerrys-flavors.ctx")) +#+end_src + +If this does not work, you can specify the input format. A list of all input formats can be shown with #+begin_src clojure :exports both (list-context-input-formats) #+end_src +#+RESULTS: #+begin_src text (:burmeister :csv :conexp :named-binary-csv :anonymous-burmeister :graphml :simple :binary-csv :fcalgs :colibri :json :galicia) #+end_src -When reading a context, the format will be automatically determined: +You can read a context, e.g., in ~:burmeister~ format, by writing the format after the file path: #+begin_src clojure :results silent -(def ben-and-jerrys-ctx (read-context "path-to-file/ben-and-jerrys-flavors.ctx")) +(def ben-and-jerrys-ctx (read-context "path-to-file/ben-and-jerrys-flavors.ctx" :burmeister)) #+end_src To see the formal context, evaluate the ~ben-and-jerrys-ctx~ variable explicitly. @@ -58,7 +64,7 @@ ben-and-jerrys-ctx The ben-and-jerrys context contains ice cream types as objects and ingredients as attributes: -#+RESULTS +#+RESULTS: #+begin_src text |Brownie Caramel Caramel Ice Choco Ice Choco Pieces Dough Peanut Butter Peanut Ice Vanilla -----------------------+------------------------------------------------------------------------------------------ @@ -79,16 +85,13 @@ The attributes, objects and the whole context can be reduced with ~reduce-attrib The whole example context can be clarified as follows: -#+begin_src clojure :results silent +#+begin_src clojure :exports both (def ben-and-jerrys-clarified (clarify-context ben-and-jerrys-ctx) -#+end_src - -#+begin_src clojure :exports both ben-and-jerrys-clarified #+end_src -#+RESULTS +#+RESULTS: #+begin_src text |Brownie Caramel Caramel Ice Choco Ice Choco Pieces Dough Peanut Ice Vanilla -----------------------+---------------------------------------------------------------------------- @@ -102,7 +105,7 @@ Salted Caramel Brownie |x x . . x . #+end_src As the attributes ~Peanut Butter~ and ~Peanut Ice~ have the same derivation, one of them (in this -case ~Peanut Butter~ is removed. +case ~Peanut Butter~) is removed. *** Compute derivations @@ -110,39 +113,56 @@ case ~Peanut Butter~ is removed. In the following example, the object derivation of two types of ice cream is computed (to see what they have in common): -#+begin_src clojure :export :both +#+begin_src clojure :exports both (object-derivation ben-and-jerrys-ctx #{"Cookie Dough" "Half Baked"}) #+end_src -#+RESULTS +#+RESULTS: #+begin_src text #{"Choco Pieces" "Dough" "Vanilla"} #+end_src -The same can be done for a set of attributes with ~attribute-derivation~. +The same can be done for a set of attributes with ~attribute-derivation~: -To compute the closure of a set of objects, you can use +#+begin_src clojure :exports both +(attribute-derivation ben-and-jerrys-ctx #{"Choco Pieces" "Dough" "Vanilla"}) +#+end_src -#+begin_src clojure :export :both -(context-object-closure ben-and-jerrys-ctx #{"Caramel Chew Chew"}) +#+RESULTS: +#+begin_src text +#{"Half Baked" "Cookie Dough"} +#+end_src + +This example shows that ~#{"Half Baked" "Cookie Dough"}~ is a closed set. +To directly compute the closure of a set of objects, you can use + +#+begin_src clojure :exports both +(context-object-closure ben-and-jerrys-ctx #{"Half Baked" "Cookie Dough"}) #+end_src -#+RESULTS +#+RESULTS: #+begin_src text -#{"Caramel Sutra" "Caramel Chew Chew"} +#{"Half Baked" "Cookie Dough"} #+end_src The closure of a set of attributes can be computed with ~context-attribute-closure~. -*** Compute the concept lattice +#+begin_src clojure :exports both +(context-attribute-closure ben-and-jerrys-ctx #{"Choco Pieces" "Dough" "Vanilla"}) +#+end_src + +#+RESULTS: +#+begin_src text +#{"Choco Pieces" "Dough" "Vanilla"} +#+end_src -The formal concepts of the context can be computed as +All formal concepts of the context can be computed as -#+begin_src clojure :export :both +#+begin_src clojure :exports both (concepts ben-and-jerrys-ctx) #+end_src -#+RESULTS +#+RESULTS: #+begin_src text ([#{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" "Cookie Dough"} #{}] [#{"Fudge Brownie" "Caramel Sutra" "Half Baked"} #{"Choco Ice"}] @@ -162,44 +182,14 @@ The formal concepts of the context can be computed as [#{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked"} #{"Brownie"}]) #+end_src -The concept lattice, consisting of the concepts and their order, can be computed via - -#+begin_src clojure :result silent -(def ben-and-jerrys-lattice (concept-lattice ben-and-jerrys-ctx) -#+end_src - -#+begin_src clojure :export :both -ben-and-jerrys-lattice -#+end_src - -#+RESULTS -#+begin_src text -Lattice on 16 elements. -#+end_src - -The lattice consists of a ~base-set~ (~(base-set ben-and-jerrys-lattice)~ contains the -concepts from the previous output) and an ~order~ function. The next section will -explain how to draw a concept lattice. - *** Draw the concept lattice -To be able to draw concept lattices, first use this command once: +To draw the concept lattice of a formal context, use these commands: #+begin_src clojure :results silent (use 'conexp.gui.draw) -#+end_src - -You can either draw the lattice from the initial context. - -#+begin_src clojure :results silent (draw-concept-lattice ben-and-jerrys-ctx) #+end_src -You can also draw the already computed ~ben-and-jerrys-lattice~. - -#+begin_src clojure :result silent -(draw-lattice ben-and-jerrys-lattice) -#+end_src - The lattice will appear in a new window. #+caption: Concept lattice of ben-and-jerrys context @@ -207,13 +197,13 @@ The lattice will appear in a new window. In left bar of the ~Lattice~ window, you have several options, e.g., you can change the layout and turn on the labels. In addition, you have the option to show several -valuations, like probability, distributivity and support. +valuations, like probability, distributivity and stability. -The ~ben-and-jerrys-lattice~ with DimDraw layout, labels and support looks like this: +The ~ben-and-jerrys-lattice~ with DimDraw layout, labels and stability looks like this: #+caption: Concept lattice of ben-and-jerrys context with DimDraw layout, labels and -support -[[./images/ben-and-jerrys-lattice-dimdraw-labels-support.png]] +stability +[[./images/ben-and-jerrys-lattice-dimdraw-labels-stability.png]] You can also create your own valuations, e.g., the extent and intent size of each formal concept. @@ -229,19 +219,24 @@ After enabeling the labels, the concept lattice looks like this: #+caption: Concept lattice of ben-and-jerrys context with manually set valuations [[./images/ben-and-jerrys-lattice-manual_valuations.png]] +You can save the lattice in several formats. To do so, click ~Export to File~ (the second +last button on the left). A new window opens, in which you can specify the path and format. +In the example, the lattice is saved in ~tikz~ format. It is important to write the file +ending in the file name that matches the file type. Otherwise an error occurs. + +#+caption: Window to save concept lattice in tikz format +[[./images/ben-an-jerrys-export-tikz.png]] + *** Computing implications - Canonical base The canonical base of a context can be computed with: -#+begin_src clojure :result silent -(def ben-and-jerrys-implications (canonical-base ben-and-jerrys-ctx)) -#+end_src - #+begin_src clojure :exports both +(def ben-and-jerrys-implications (canonical-base ben-and-jerrys-ctx)) ben-and-jerrys-implications #+end_src -#+RESULTS +#+RESULTS: #+begin_src text ((#{"Caramel"} ⟶ #{"Choco Pieces"}) (#{"Vanilla"} ⟶ #{"Choco Pieces"}) @@ -266,11 +261,11 @@ Depending on the size of the contexts, the computation of the concept can take a Therefore, the results can be saved so that the computation does not need to be repeated. For the output, the format needs to be specified. The formats to save a concept lattice are -#+begin_src clojure :export both +#+begin_src clojure :exports both (list-lattice-output-formats) #+end_src -#+RESULTS +#+RESULTS: #+begin_src text (:simple :json) #+end_src @@ -278,6 +273,7 @@ For the output, the format needs to be specified. The formats to save a concept A concept lattice can be saved in the ~:json~ format with the following command #+begin_src clojure :result silent +(def ben-and-jerrys-lattice (concept-lattice ben-and-jerrys-ctx)) (write-lattice :json ben-and-jerrys-lattice "path/ben-and-jerrys-lattice.json") #+end_src @@ -312,18 +308,15 @@ Note that such an FCA object can contain several implication sets. The ~conexp-clj~ provides the functionality for conceptual scaling. For an example, load the smaller ~ben-and-jerrys-flavors-small.ctx~: -#+begin_src clojure :results silent -(def ben-and-jerrys-small-ctx (read-context "path-to-file/ben-and-jerrys-flavors-small.ctx")) -#+end_src - #+begin_src clojure :exports both +(def ben-and-jerrys-small-ctx (read-context "path-to-file/ben-and-jerrys-flavors-small.ctx")) ben-and-jerrys-small-ctx #+end_src The ben-and-jerrys context contains the same ice cream types as objects, but a smaller set of flavors as attributes: -#+RESULTS +#+RESULTS: #+begin_src text |Brownie Caramel Choco Dough Peanut Vanilla -----------------------+------------------------------------------- @@ -344,7 +337,7 @@ scaling error is computed: (conceptual-scaling-error (make-smeasure-nc ben-and-jerrys-ctx ben-and-jerrys-small-ctx identity)) #+end_src -#+RESULTS +#+RESULTS: #+begin_src text 0 #+end_src @@ -353,15 +346,12 @@ As the error is 0, the ~ben-and-jerrys-small-ctx~ is a scale of the ~ben-and-jer Another context uses the same ice cream types, but allergens instead of flavors. -#+begin_src clojure :results silent -(def ben-and-jerrys-allergens-ctx (read-context "path-to-file/ben-and-jerrys-allergens.ctx")) -#+end_src - #+begin_src clojure :exports both +(def ben-and-jerrys-allergens-ctx (read-context "path-to-file/ben-and-jerrys-allergens.ctx")) ben-and-jerrys-allergens-ctx #+end_src -#+RESULTS +#+RESULTS: #+begin_src text |almond barley egg milk peanuts soy wheat -----------------------+----------------------------------------- @@ -380,7 +370,7 @@ You can compute the conceptual scaling error in the same way as for the ~ben-and (conceptual-scaling-error (make-smeasure-nc ben-and-jerrys-ctx ben-and-jerrys-allergens-ctx identity)) #+end_src -#+RESULTS +#+RESULTS: #+begin_src text 1 #+end_src @@ -388,6 +378,32 @@ You can compute the conceptual scaling error in the same way as for the ~ben-and In this case, the error is 1 and therefore the allergens context is not a scale of the original ~ben-and-jerrys-ctx~. +To get more information about the scaling error, you can use + +#+begin_src clojure :exports both +(error-in-smeasure (make-smeasure-nc ben-and-jerrys-ctx ben-and-jerrys-allergens-ctx identity)) +#+end_src + +#+RESULTS: +#+begin_src text +(#{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked" "Cookie Dough"}) +#+end_src + +This set of objects is closed in ~ben-and-jerrys-allergens-ctx~, but not in ~ben-and-jerrys-ctx~, +as can be seen with + +#+begin_src clojure :exports both +(context-object-closure ben-and-jerrys-ctx + #{"Fudge Brownie" "Salted Caramel Brownie" "Half Baked" "Cookie Dough"}) +#+end_src + +#+RESULTS: +#+begin_src text +#{"Peanut Butter Cup" "Fudge Brownie" "Caramel Sutra" + "Salted Caramel Brownie" "Caramel Chew Chew" "Half Baked" + "Cookie Dough"} +#+end_src + ** Attribute exploration From 31eb2d8c699ea712cf49e96efe4474ae959244c4 Mon Sep 17 00:00:00 2001 From: Jana Date: Mon, 17 Jul 2023 10:40:07 +0200 Subject: [PATCH 089/112] update images for tutorial --- .../images/ben-and-jerrys-export-tikz.png | Bin 0 -> 16787 bytes ...jerrys-lattice-dimdraw-labels-stability.png | Bin 0 -> 136297 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/tutorials/icfca-2023/images/ben-and-jerrys-export-tikz.png create mode 100644 doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-stability.png diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-export-tikz.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-export-tikz.png new file mode 100644 index 0000000000000000000000000000000000000000..9076e6e607aeac5e994487ad914bc86d54f42e80 GIT binary patch literal 16787 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

xY{z%?=g|-1-1Fmm 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-stability.png b/doc/tutorials/icfca-2023/images/ben-and-jerrys-lattice-dimdraw-labels-stability.png new file mode 100644 index 0000000000000000000000000000000000000000..a8de54f1b4eb650730d5f8584f479b59884dc48c GIT binary patch literal 136297 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ɪR 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 literal 0 HcmV?d00001 From 59718f99277b366c7b17287e303bd27a1f7616ef Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:42:16 +0200 Subject: [PATCH 090/112] fix typo --- doc/tutorials/icfca-2023/icfca-2023-tutorial.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org index 6f932a840..27c50d0fa 100644 --- a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org +++ b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org @@ -225,7 +225,7 @@ In the example, the lattice is saved in ~tikz~ format. It is important to write ending in the file name that matches the file type. Otherwise an error occurs. #+caption: Window to save concept lattice in tikz format -[[./images/ben-an-jerrys-export-tikz.png]] +[[./images/ben-and-jerrys-export-tikz.png]] *** Computing implications - Canonical base From 3a7b9cfcb42de25931be701facbdfe664fba93f5 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:09:54 +0200 Subject: [PATCH 091/112] fix typos --- doc/tutorials/icfca-2023/icfca-2023-tutorial.org | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org index 27c50d0fa..a45a7a01b 100644 --- a/doc/tutorials/icfca-2023/icfca-2023-tutorial.org +++ b/doc/tutorials/icfca-2023/icfca-2023-tutorial.org @@ -79,9 +79,9 @@ Salted Caramel Brownie |x x . . x . *** Reduce, clarify -With ~reduced?~ and ~clarified?~, you can check if a context is reduced or clarified. +With ~context-reduced?~ and ~context-clarified?~, you can check if a context is reduced or clarified. The attributes, objects and the whole context can be reduced with ~reduce-attributes~, -~reduce-objects~ and ~reduce-context~. The same applies to ~clarify~. +~reduce-objects~ and ~reduce-context~. The same applies to clarify. The whole example context can be clarified as follows: From 539196beb23dea9e7163291bac8c15d1771dc76c Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Mon, 17 Jul 2023 22:22:17 +0200 Subject: [PATCH 092/112] 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 5ad769692082634a2dd84f2971fb52b6657b4884 Mon Sep 17 00:00:00 2001 From: "Tom Hanika (sys:companion)" Date: Mon, 17 Jul 2023 22:31:58 +0200 Subject: [PATCH 093/112] Updated version in flake -- for real --- flake.nix | 2 +- project.clj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 3767de07e..63ef00002 100644 --- a/flake.nix +++ b/flake.nix @@ -40,7 +40,7 @@ conexp = let pname = "conexp-clj"; - version = "2.4.0"; + version = "2.4.1-SNAPSHOT"; in mkCljBin rec { name = "conexp/${pname}"; inherit version; diff --git a/project.clj b/project.clj index 4dddc5da1..3ef6089c5 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.1-SNAPSHOT" :min-lein-version "2.0.0" :description "A ConExp rewrite in clojure -- and so much more ..." From 544d4a9841b6da011dd4000f25bd00b198f6325d Mon Sep 17 00:00:00 2001 From: Jana Date: Tue, 18 Jul 2023 08:11:26 +0200 Subject: [PATCH 094/112] add contexts to tutorial --- .../contexts/ben-and-jerrys-allergens.ctx | 26 +++++++++++++++++ .../contexts/ben-and-jerrys-flavors-small.ctx | 25 +++++++++++++++++ .../contexts/ben-and-jerrys-flavors.ctx | 28 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 doc/tutorials/icfca-2023/contexts/ben-and-jerrys-allergens.ctx create mode 100644 doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors-small.ctx create mode 100644 doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors.ctx diff --git a/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-allergens.ctx b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-allergens.ctx new file mode 100644 index 000000000..87bb90c42 --- /dev/null +++ b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-allergens.ctx @@ -0,0 +1,26 @@ +B + +7 +7 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +barley +milk +peanuts +almond +wheat +egg +soy +.XX..XX +XX..XX. +.X.X.XX +.X..XXX +.X...XX +XX..XXX +.X..XXX diff --git a/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors-small.ctx b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors-small.ctx new file mode 100644 index 000000000..42125ebc9 --- /dev/null +++ b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors-small.ctx @@ -0,0 +1,25 @@ +B + +7 +6 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +Choco +Brownie +Dough +Peanut +Vanilla +Caramel +X..X.. +XX.... +X....X +XX..XX +X....X +XXX.X. +X.X.X. diff --git a/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors.ctx b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors.ctx new file mode 100644 index 000000000..9a1466fd7 --- /dev/null +++ b/doc/tutorials/icfca-2023/contexts/ben-and-jerrys-flavors.ctx @@ -0,0 +1,28 @@ +B + +7 +9 + +Peanut Butter Cup +Fudge Brownie +Caramel Sutra +Salted Caramel Brownie +Caramel Chew Chew +Half Baked +Cookie Dough +Choco Ice +Peanut Ice +Choco Pieces +Brownie +Dough +Peanut Butter +Caramel Ice +Vanilla +Caramel +.XX..X... +X..X..... +X.X...X.X +..XX...XX +..X...X.X +X.XXX..X. +..X.X..X. From 3ce23a8ac54a9b002ed650d68579966d9ac1567f Mon Sep 17 00:00:00 2001 From: Maximilian Marx Date: Wed, 19 Jul 2023 23:31:12 +0200 Subject: [PATCH 095/112] Avoid duplicating the version number in `flake.nix` (#124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Read version from project.clj * flake.lock: Update Flake lock file updates: • Updated input 'clj-nix': 'github:jlesquembre/clj-nix/7d9e244ea96988524ba3bd6c2bbafdf0a5340b96' (2023-02-25) → 'github:jlesquembre/clj-nix/6a017fb2bc7b60c9e67b1c6f0b04bbefcf8dc698' (2023-07-19) • Updated input 'clj-nix/devshell': 'github:numtide/devshell/0ffc7937bb5e8141af03d462b468bd071eb18e1b' (2022-07-25) → 'github:numtide/devshell/3864857b2754ab0e16c7c7c626f0e5a1d4e42f38' (2023-06-28) • Removed input 'clj-nix/devshell/flake-utils' • Added input 'clj-nix/devshell/systems': 'github:nix-systems/default/da67096a3b9bf56a91d16901293e51ba5b49a27e' (2023-04-09) • Removed input 'clj-nix/flake-utils' • Added input 'clj-nix/nix-fetcher-data': 'github:jlesquembre/nix-fetcher-data/f14967db6c92c79b77419f52c22a698518c91120' (2023-05-31) • Added input 'clj-nix/nix-fetcher-data/flake-part': 'github:hercules-ci/flake-parts/6ef2707776c6379bc727faf3f83c0dd60b06e0c6' (2023-05-31) • Added input 'clj-nix/nix-fetcher-data/flake-part/nixpkgs-lib': 'github:NixOS/nixpkgs/da45bf6ec7bbcc5d1e14d3795c025199f28e0de0?dir=lib' (2023-04-30) • Added input 'clj-nix/nix-fetcher-data/flake-parts': 'github:hercules-ci/flake-parts/6ef2707776c6379bc727faf3f83c0dd60b06e0c6' (2023-05-31) • Added input 'clj-nix/nix-fetcher-data/flake-parts/nixpkgs-lib': 'github:NixOS/nixpkgs/da45bf6ec7bbcc5d1e14d3795c025199f28e0de0?dir=lib' (2023-04-30) • Added input 'clj-nix/nix-fetcher-data/nixpkgs': follows 'clj-nix/nixpkgs' • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/6b0edc9c690c1d8a729f055e0d73439045cfda55' (2023-05-30) → 'github:NixOS/nixpkgs/53657afe29748b3e462f1f892287b7e254c26d77' (2023-07-17) * Clean up flake structure * Add metadata to conexp-clj package * Fix formatting * Build package (and thus run tests) as flake check --- .github/workflows/run-tests.yaml | 5 +- .github/workflows/update-deps-lock.yaml | 5 +- .gitignore | 3 +- flake.lock | 139 +++++++++++++++++++++--- flake.nix | 113 +++++++++++-------- 5 files changed, 198 insertions(+), 67 deletions(-) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 0ab0b6c91..1453d8992 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -13,5 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v17 - - run: nix run .#test + - uses: DeterminateSystems/nix-installer-action@v4 + - uses: DeterminateSystems/magic-nix-cache-action@v2 + - run: nix flake check diff --git a/.github/workflows/update-deps-lock.yaml b/.github/workflows/update-deps-lock.yaml index abe9eb791..5a0dd72d9 100644 --- a/.github/workflows/update-deps-lock.yaml +++ b/.github/workflows/update-deps-lock.yaml @@ -10,9 +10,10 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v17 + - uses: DeterminateSystems/nix-installer-action@v4 + - uses: DeterminateSystems/magic-nix-cache-action@v2 - name: Update deps-lock - run: "nix run .#deps-lock" + run: nix run .#deps-lock - name: Create Pull Request uses: peter-evans/create-pull-request@v4.0.3 diff --git a/.gitignore b/.gitignore index d38ac958b..00d4b3d49 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ /doc/icfca-2013-tutorial/icfca2013-tutorial-talk.vrb /doc/tutorials/icfca-2013/icfca2013-tutorial-live.html /.lsp/ -/.direnv/ \ No newline at end of file +/.direnv/ +/.clj-kondo/ diff --git a/flake.lock b/flake.lock index d197ce887..1b39c426f 100644 --- a/flake.lock +++ b/flake.lock @@ -3,20 +3,17 @@ "clj-nix": { "inputs": { "devshell": "devshell", - "flake-utils": [ - "utils", - "flake-utils" - ], + "nix-fetcher-data": "nix-fetcher-data", "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1677342613, - "narHash": "sha256-BqhKj7jQahSVThEwLHt164kJHGx9LXzBARFZaFNLPW8=", + "lastModified": 1689754411, + "narHash": "sha256-DBaZAmBR5EA03Zjf4k7XHqvOovJP+pFhzl+BJ4V+lFw=", "owner": "jlesquembre", "repo": "clj-nix", - "rev": "7d9e244ea96988524ba3bd6c2bbafdf0a5340b96", + "rev": "6a017fb2bc7b60c9e67b1c6f0b04bbefcf8dc698", "type": "github" }, "original": { @@ -27,21 +24,18 @@ }, "devshell": { "inputs": { - "flake-utils": [ - "clj-nix", - "flake-utils" - ], "nixpkgs": [ "clj-nix", "nixpkgs" - ] + ], + "systems": "systems" }, "locked": { - "lastModified": 1658746384, - "narHash": "sha256-CCJcoMOcXyZFrV1ag4XMTpAPjLWb4Anbv+ktXFI1ry0=", + "lastModified": 1687944744, + "narHash": "sha256-4ZtRVG/5yWHPZpkit1Ak5Mo1DDnkx1AG1HpNu/P+n5U=", "owner": "numtide", "repo": "devshell", - "rev": "0ffc7937bb5e8141af03d462b468bd071eb18e1b", + "rev": "3864857b2754ab0e16c7c7c626f0e5a1d4e42f38", "type": "github" }, "original": { @@ -50,6 +44,41 @@ "type": "github" } }, + "flake-part": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1685546676, + "narHash": "sha256-XDbjJyAg6odX5Vj0Q22iI/gQuFvEkv9kamsSbQ+npaI=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "6ef2707776c6379bc727faf3f83c0dd60b06e0c6", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1685546676, + "narHash": "sha256-XDbjJyAg6odX5Vj0Q22iI/gQuFvEkv9kamsSbQ+npaI=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "6ef2707776c6379bc727faf3f83c0dd60b06e0c6", + "type": "github" + }, + "original": { + "id": "flake-parts", + "type": "indirect" + } + }, "flake-utils": { "locked": { "lastModified": 1644229661, @@ -85,13 +114,36 @@ "type": "github" } }, + "nix-fetcher-data": { + "inputs": { + "flake-part": "flake-part", + "flake-parts": "flake-parts", + "nixpkgs": [ + "clj-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1685572850, + "narHash": "sha256-lYKEqFG9F84xu51H1rM1u+Ip88cINL0+W26sT+vFEZc=", + "owner": "jlesquembre", + "repo": "nix-fetcher-data", + "rev": "f14967db6c92c79b77419f52c22a698518c91120", + "type": "github" + }, + "original": { + "owner": "jlesquembre", + "repo": "nix-fetcher-data", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1685451684, - "narHash": "sha256-Y5iqtWkO82gHAnrBvNu/yLQsiVNJRCad4wWGz2a1urk=", + "lastModified": 1689605451, + "narHash": "sha256-u2qp2k9V1smCfk6rdUcgMKvBj3G9jVvaPHyeXinjN9E=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6b0edc9c690c1d8a729f055e0d73439045cfda55", + "rev": "53657afe29748b3e462f1f892287b7e254c26d77", "type": "github" }, "original": { @@ -101,6 +153,42 @@ "type": "github" } }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1682879489, + "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib_2": { + "locked": { + "dir": "lib", + "lastModified": 1682879489, + "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "clj-nix": "clj-nix", @@ -109,6 +197,21 @@ "utils": "utils" } }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "utils": { "inputs": { "flake-utils": "flake-utils" diff --git a/flake.nix b/flake.nix index 63ef00002..5c553224f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,5 @@ { - description = - "conexp-clj, a general purpose software tool for Formal Concept Analysis"; + description = "conexp-clj, a general purpose software tool for Formal Concept Analysis"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; @@ -8,10 +7,7 @@ clj-nix = { url = "github:jlesquembre/clj-nix"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "utils/flake-utils"; - }; + inputs.nixpkgs.follows = "nixpkgs"; }; gitignore = { @@ -20,30 +16,55 @@ }; }; - outputs = { self, nixpkgs, utils, ... }@inputs: - let inherit (utils.lib) mkApp mkFlake; - in mkFlake { + outputs = { + self, + nixpkgs, + utils, + ... + } @ inputs: let + inherit (utils.lib) mkApp mkFlake; + in + mkFlake { inherit self inputs; - channels.nixpkgs.overlaysBuilder = channels: - [ inputs.clj-nix.overlays.default ]; + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + "aarch64-darwin" + "x86_64-darwin" + ]; + + channels.nixpkgs.overlaysBuilder = channels: [inputs.clj-nix.overlays.default]; overlays.default = final: prev: { inherit (self.packages."${final.system}") conexp-clj; }; - outputsBuilder = channels: - let - inherit (inputs.gitignore.lib) gitignoreSource; - inherit (inputs.clj-nix.lib) mk-deps-cache; - inherit (channels.nixpkgs) mkCljBin mkShell writeShellScriptBin; - - conexp = let - pname = "conexp-clj"; - version = "2.4.1-SNAPSHOT"; - in mkCljBin rec { + outputsBuilder = channels: let + inherit (inputs.gitignore.lib) gitignoreSource; + inherit (inputs.clj-nix.lib) mk-deps-cache; + inherit (channels.nixpkgs) mkCljBin mkShell writeShellScriptBin; + inherit (channels.nixpkgs.lib) pipe; + + conexp = let + versionFromDefproject = name: + pipe ./project.clj [ + builtins.readFile + (builtins.match '' + .*\([[:SPACE:]]*defproject[[:SPACE:]]+${name}[[:SPACE:]]+"([^"]+)".*'') + builtins.head + ]; + pname = "conexp-clj"; + in + mkCljBin rec { name = "conexp/${pname}"; - inherit version; + version = versionFromDefproject pname; + + meta = { + description = "A General-Purpose Tool for Formal Concept Analysis"; + homepage = "https://github.com/tomhanika/conexp-clj"; + license = channels.nixpkgs.lib.licenses.epl10; + }; projectSrc = gitignoreSource ./.; main-ns = "conexp"; @@ -56,38 +77,42 @@ doCheck = true; checkPhase = "lein test"; }; + in rec { + packages = { + conexp-clj = conexp; + default = conexp; + }; - in { - packages = rec { - conexp-clj = conexp; - default = conexp-clj; + apps = rec { + deps-lock = mkApp { + drv = writeShellScriptBin "deps-lock" '' + ${channels.nixpkgs.deps-lock}/bin/deps-lock --lein $@ + ''; }; - apps = rec { - conexp-clj = mkApp { drv = conexp; }; - default = conexp-clj; - - deps-lock = mkApp { - drv = writeShellScriptBin "deps-lock" '' - ${channels.nixpkgs.deps-lock}/bin/deps-lock --lein $@ - ''; - }; - - test = let deps = mk-deps-cache { lock-file = ./deps-lock.json; }; - in mkApp { + test = let + deps = mk-deps-cache {lock-file = ./deps-lock.json;}; + in + mkApp { drv = writeShellScriptBin "conexp-clj-tests" '' lein test $@ ''; }; - }; - - devShells.default = mkShell { - buildInputs = with channels.nixpkgs; [ clojure-lsp leiningen ]; - }; + }; - formatter = channels.nixpkgs.alejandra; + checks = { + inherit (packages) conexp-clj; + devShell = devShells.default; + }; + devShells.default = mkShell { + buildInputs = with channels.nixpkgs; [clojure-lsp leiningen]; }; + formatter = channels.nixpkgs.alejandra; + }; }; } +# Local Variables: +# apheleia-formatter: alejandra +# End: From cdd5bb7cd510ae10e68847a4f4ab58ea9ba1f097 Mon Sep 17 00:00:00 2001 From: Jana Fischer <74052109+jana-fischer@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:57:46 +0200 Subject: [PATCH 096/112] bind json schema resources such that they are also correctly opened in standalone jar (#126) --- src/main/clojure/conexp/io/contexts.clj | 2 +- src/main/clojure/conexp/io/fcas.clj | 2 +- src/main/clojure/conexp/io/implications.clj | 2 +- src/main/clojure/conexp/io/json.clj | 5 +++-- src/main/clojure/conexp/io/lattices.clj | 2 +- src/main/clojure/conexp/io/layouts.clj | 2 +- src/main/clojure/conexp/io/many_valued_contexts.clj | 2 +- 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/clojure/conexp/io/contexts.clj b/src/main/clojure/conexp/io/contexts.clj index 6b237e070..4f44212ec 100644 --- a/src/main/clojure/conexp/io/contexts.clj +++ b/src/main/clojure/conexp/io/contexts.clj @@ -667,7 +667,7 @@ [file] (with-in-reader file (let [file-content (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/context_schema_v1.1.json"] + schema-file "schemas/context_schema_v1.1.json"] (assert (matches-schema? file-content schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->ctx file-content)))) diff --git a/src/main/clojure/conexp/io/fcas.clj b/src/main/clojure/conexp/io/fcas.clj index 024748c73..c51bfc3b4 100644 --- a/src/main/clojure/conexp/io/fcas.clj +++ b/src/main/clojure/conexp/io/fcas.clj @@ -65,7 +65,7 @@ [file] (with-in-reader file (let [json-fca (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/fca_schema_v1.0.json"] + schema-file "schemas/fca_schema_v1.0.json"] (assert (matches-schema? json-fca schema-file) (str "The input file does not match the schema given at " schema-file ".")) (create-fca-input-map json-fca)))) diff --git a/src/main/clojure/conexp/io/implications.clj b/src/main/clojure/conexp/io/implications.clj index 3531ce854..7bfdef23a 100644 --- a/src/main/clojure/conexp/io/implications.clj +++ b/src/main/clojure/conexp/io/implications.clj @@ -62,7 +62,7 @@ [file] (with-in-reader file (let [impl (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/implications_schema_v1.0.json"] + schema-file "schemas/implications_schema_v1.0.json"] (assert (matches-schema? impl schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->implications impl)))) diff --git a/src/main/clojure/conexp/io/json.clj b/src/main/clojure/conexp/io/json.clj index fae1f57a3..f5885bc8f 100644 --- a/src/main/clojure/conexp/io/json.clj +++ b/src/main/clojure/conexp/io/json.clj @@ -9,13 +9,14 @@ (ns conexp.io.json "Provides functionality to read and process json files" (:require [json-schema.core :as json-schema] - [clojure.data.json :as json])) + [clojure.data.json :as json] + [clojure.java.io :as io])) (defn- read-schema "Returns the file content as Json object." [file] (json-schema/prepare-schema - (-> file slurp + (-> (io/resource file) slurp (cheshire.core/parse-string true)) ;; referencing inside of schemas with relative references {:classpath-aware? true diff --git a/src/main/clojure/conexp/io/lattices.clj b/src/main/clojure/conexp/io/lattices.clj index 5a83501f0..b64d7af39 100644 --- a/src/main/clojure/conexp/io/lattices.clj +++ b/src/main/clojure/conexp/io/lattices.clj @@ -102,7 +102,7 @@ [file] (with-in-reader file (let [json-lattice (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/lattice_schema_v1.1.json"] + schema-file "schemas/lattice_schema_v1.1.json"] (assert (matches-schema? json-lattice schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->lattice json-lattice)))) diff --git a/src/main/clojure/conexp/io/layouts.clj b/src/main/clojure/conexp/io/layouts.clj index 87f30778e..521d6a549 100644 --- a/src/main/clojure/conexp/io/layouts.clj +++ b/src/main/clojure/conexp/io/layouts.clj @@ -275,7 +275,7 @@ [file] (with-in-reader file (let [json-layout (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/layout_schema_v1.0.json"] + schema-file "schemas/layout_schema_v1.0.json"] (assert (matches-schema? json-layout schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->layout json-layout)))) diff --git a/src/main/clojure/conexp/io/many_valued_contexts.clj b/src/main/clojure/conexp/io/many_valued_contexts.clj index 9a6d782c4..d4a6869fc 100644 --- a/src/main/clojure/conexp/io/many_valued_contexts.clj +++ b/src/main/clojure/conexp/io/many_valued_contexts.clj @@ -165,7 +165,7 @@ [file] (with-in-reader file (let [json-mv-context (json/read *in* :key-fn keyword) - schema-file "src/main/resources/schemas/mv-context_schema_v1.0.json"] + schema-file "schemas/mv-context_schema_v1.0.json"] (assert (matches-schema? json-mv-context schema-file) (str "The input file does not match the schema given at " schema-file ".")) (json->mv-context json-mv-context)))) 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 097/112] 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 098/112] 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 099/112] 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 100/112] 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 101/112] 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 102/112] 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 103/112] 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 104/112] 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 105/112] 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 106/112] 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 107/112] 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 108/112] 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 109/112] 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 110/112] 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 111/112] 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 112/112] 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",