diff --git a/AUTHORS.md b/AUTHORS.md index 74dc413c..bf553a58 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -11,7 +11,7 @@ Additional Contributors are * Immanuel Albrecht (Context Editor Plugin for the GUI) * Sebastian Benner (API) * Stefan Borgwardt (Shared Intents) -* Jana Fischer (json format,Ordinal Motifs, scale-measures) +* Jana Fischer (json format, Simplicial Complex, Ordinal Motifs, scale-measures) * Tom Hanika (Concept Probability) * Johannes Hirth (pq-cores, Ordinal Motifs, scale-measures) * Gleb Kanterov (interval-scale) diff --git a/README.md b/README.md index 47011d74..e8e6cc9c 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ would appreciate if you use the following reference. ## License -Copyright ⓒ 2009—2018 Daniel Borchmann, 2018–2023 Tom Hanika +Copyright ⓒ 2009—2018 Daniel Borchmann, 2018–2024 Tom Hanika Distributed under the Eclipse Public License. diff --git a/deps-lock.json b/deps-lock.json index 38f535a6..cdade2b3 100644 --- a/deps-lock.json +++ b/deps-lock.json @@ -562,6 +562,26 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-KBRAgRJo5l2eJms8yJgpfiFOBPCXQNA4bO60qJI9Y78=" }, + { + "mvn-path": "net/mikera/clojure-pom/0.6.0/clojure-pom-0.6.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-ApT9U7aW/29SAU5XUeJXiHii5kkP/SiwPHvX5A9/HVw=" + }, + { + "mvn-path": "net/mikera/core.matrix/0.63.0/core.matrix-0.63.0.jar", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-UCxlj7Q0EDgwxgDNCMAfPJdQPuwS2W6tklB4UwQAnuI=" + }, + { + "mvn-path": "net/mikera/core.matrix/0.63.0/core.matrix-0.63.0.pom", + "mvn-repo": "https://repo.clojars.org/", + "hash": "sha256-T3MYieJGpsINFq3W3PElUPj8pLEeW9Vjefa28UHeMcA=" + }, + { + "mvn-path": "net/mikera/mikera-pom/0.6.0/mikera-pom-0.6.0.pom", + "mvn-repo": "https://repo1.maven.org/maven2/", + "hash": "sha256-AzYO7a8+mxOHcZrgCf2V7mFF1nEO1P5QQ47IJxM5XTI=" + }, { "mvn-path": "nrepl/drawbridge/0.2.1/drawbridge-0.2.1.jar", "mvn-repo": "https://repo.clojars.org/", @@ -992,6 +1012,11 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-IA0uyoZlJAy8uu5IwAcyS3tE02YIDpWN8dIujd4kg4w=" }, + { + "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/", @@ -1357,6 +1382,16 @@ "mvn-repo": "https://repo1.maven.org/maven2/", "hash": "sha256-v7Yh5LAaW4vOEWpgcIQNzdWUnomceEaNgRtuiqqf0cc=" }, + { + "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/", diff --git a/doc/Getting-Started.org b/doc/Getting-Started.org index 22756430..e9a1398f 100644 --- a/doc/Getting-Started.org +++ b/doc/Getting-Started.org @@ -5,13 +5,13 @@ ** Running conexp-clj from pre-compiled binaries -To run ~conexp-clj~, a Java Runtime Environment with version ≥ 1.8 is necessary. +To run ~conexp-clj~, a Java Runtime Environment with version ≥ 1.11 is necessary. The recommended way to use ~conexp-clj~ outside of development is to download a -[[https://algebra20.de/conexp/][pre-compiled version]]. These are just usual Java jar files and can be used like +[[https://github.com/tomhanika/conexp-clj/releases#:~:text=Assets][pre-compiled version]]. These are just usual Java jar files and can be used like this: #+begin_src sh :eval never -java -jar conexp-clj-2.1.1-SNAPSHOT-standalone.jar +java -jar conexp-clj-2.5.0-standalone-openjdk-11.jar #+end_src This will get you a prompt for ~conexp-clj~ much like @@ -32,6 +32,13 @@ conexp.analysis=> (+ 1 1) (where you do not type the ~conexp.main=>~ and the 2 is the result of the evaluation. +You may also start the (rudimentary) graphical user interface (GUI) by appending `-g` + +#+begin_src sh :eval never +java -jar conexp-clj-2.5.0-standalone-openjdk-11.jar -g +#+end_src + + ** Running conexp-clj directly from source It is also possible to get command line access for ~conexp-clj~ directly from @@ -59,6 +66,12 @@ 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]]. +You can start the graphical user interface (GUI) via + +#+begin_src sh :eval never +lein run -g +#+end_src + ** Running conexp-clj via nix Using the [[https://nixos.org/manual/nix/stable/][nix]] package manager, you can directly run ~conexp-clj~ without @@ -125,15 +138,9 @@ type into the prompt can also be written into a file, say ~file.clj~. To run (load-file "file.clj") #+end_src -** Graphical User Interface - -It is also possible to use ~conexp-clj~'s incomplete GUI fragment. You can start it running the following code in you favourite shell - -#+begin_src sh :eval never -java -jar conexp-clj-2.1.1-SNAPSHOT-standalone.jar -g -#+end_src +** Concerning the Graphical User Interface -But please note that the GUI is not only (inherently) limited in its +As indicated above, tt is also possible to use ~conexp-clj~'s incomplete GUI fragment. But please note that the GUI is not only (inherently) limited in its functionality, but also quite (not to say: really) buggy. ** Online Documentation (No Network Required!) diff --git a/project.clj b/project.clj index a6f00cde..03217349 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.5.0" +(defproject conexp-clj "2.5.1-SNAPSHOT" :min-lein-version "2.0.0" :description "A ConExp rewrite in clojure -- and so much more ..." @@ -17,6 +17,7 @@ :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.11.3"] [org.clojure/core.async "1.6.681"] + [org.clojure/data.csv "1.1.0"] [org.clojure/data.int-map "1.3.0"] [org.clojure/data.json "2.5.0"] [org.clojure/data.xml "0.0.8"] @@ -42,7 +43,7 @@ [http-kit "2.8.0"] [org.apache.commons/commons-math3 "3.6.1"] [luposlip/json-schema "0.4.5"] - [org.clojure/data.csv "1.1.0"]] + [net.mikera/core.matrix "0.63.0"]] :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/analysis.clj b/src/main/clojure/conexp/analysis.clj index 6489cd05..e1f0c3c5 100644 --- a/src/main/clojure/conexp/analysis.clj +++ b/src/main/clojure/conexp/analysis.clj @@ -23,6 +23,7 @@ [pqcores :refer :all] [protoconcepts :refer :all] [random-contexts :refer :all] + [simplicial-complexes :refer :all] [triadic-exploration :refer :all]] [conexp.math [algebra :refer :all] diff --git a/src/main/clojure/conexp/base.clj b/src/main/clojure/conexp/base.clj index f4e700d9..c13f85a2 100644 --- a/src/main/clojure/conexp/base.clj +++ b/src/main/clojure/conexp/base.clj @@ -805,263 +805,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 - "Implements lectic < at position i. The basic order is given by the ordering - of base which is interpreted as increasing order. - - A and B have to be sets." - [base i A B] - (and (contains? B i) (not (contains? A i)) ; A and B will always be sets - (loop [elements base] - (let [j (first elements)] - (if (= j i) - true - (if (identical? (contains? B j) (contains? A j)) - (recur (rest elements)) - false)))))) - -(defn lectic-< - "Implements lectic ordering. The basic order is given by the ordering of base - which is interpreted as increasing order." - [base A B] - (exists [i base] (lectic-<_i base i A B))) - -(defn next-closed-set-in-family - "Computes next closed set as with next-closed-set, which is in the - family F of all closed sets satisfing predicate. predicate has to - satisfy the condition - - A in F and i in base ==> clop(A union {1, ..., i-1}) in F. - " - [predicate base clop A] - (loop [i-s (reverse base), - A (set A)] - (if (empty? i-s) - nil - (let [i (first i-s)] - (if (contains? A i) - (recur (rest i-s) (disj A i)) - (let [clop-A (clop (conj A i))] - (if (and (lectic-<_i base i A clop-A) - (predicate clop-A)) - clop-A - (recur (rest i-s) A)))))))) - -(defn improve-basic-order - "Improves basic order on the sequence base, where the closure operator - clop operates on." - [base clop] - (let [base (seq base), - clop (memoize clop)] - (sort (fn [x y] - (if (= (clop #{y}) (clop #{x})) - 0 - (if (lectic-< base (clop #{y}) (clop #{x})) - -1 - 1))) - base))) - -(defn all-closed-sets-in-family - "Computes all closed sets of a given closure operator on a given set - base contained in the family described by predicate. See - documentation of next-closed-set-in-family for more details. Uses - initial as first closed set if supplied." - ([predicate base clop] - (all-closed-sets-in-family predicate base clop #{})) - ([predicate base clop initial] - (lazy-seq - (let [base (if (set? base) (improve-basic-order base clop) base), - initial (clop initial), - start (if (predicate initial) - initial - (next-closed-set-in-family predicate base clop initial)), - runner (fn runner [X] - (lazy-seq - (if (nil? X) - nil - (cons X (runner (next-closed-set-in-family predicate base clop X))))))] - (runner start))))) - -(defn next-closed-set - "Computes next closed set of the closure operator clop after A with - the Next Closure algorithm. The order of elements in base, - interpreted as increasing, is taken to be the basic order of the - elements." - [base clop A] - (next-closed-set-in-family (constantly true) base clop A)) - -(defn all-closed-sets - "Computes all closed sets of a given closure operator on a given - set. Uses initial as first closed set if supplied." - ([base clop] - (all-closed-sets base clop #{})) - ([base clop initial] - (all-closed-sets-in-family (constantly true) base clop initial))) - -(defn parallel-closures - "Returns the set of all closures of the closure operator on the given base set. - Computes the closures in parallel, to the extent possible." - [base clop] - (loop [n 0 - closures #{(clop #{})} - current #{(clop #{})}] - (if (< (count base) n) - closures - (let [next-current (atom current)] - (dopar [C current] - (when (not= (count C) n) - (swap! next-current #(disj % C)) - (doseq [x base :when (not (contains? C x))] - (swap! next-current #(conj % (clop (conj C x))))))) - (recur (inc n) (into closures @next-current) @next-current))))) - -;;; Extension - -(defn non-closed-elements - "Given a closure operator (c) and a set (X) returns the subset - {x in X | c({x}) != {x}}." - [base clop] - (set (filter #(not= #{%} (clop #{%})) base))) - -(defn exclusive-closure - "Given a closure operator (c) and a set (s) returns - c(s)/s" - [set clop] - (difference (clop set) set)) - -(defn- extendable-set - "Given a closure system and a element, return the subset of the closure - system whom the element closure is not a subset of." - [closure clop element] - (let [x-closure (exclusive-closure #{element} clop)] - (filter #(not (subset? x-closure %)) closure))) - -(defn extension-set - "Adds the given element to each element of a closure system." - [closure clop element] - (for [F (extendable-set closure clop element)] - (conj F element))) - -(defn extend-closure - "Extents the closure system by an additional given element." - [closure clop element] - (union closure (extension-set closure clop element))) - -;;; Common Math Algorithms - -(defn transitive-closure - "Computes transitive closure of a given set of pairs." - ;; Inspired by the corresponding code from the graph library by - ;; Jeffrey Straszheim - [pairs] - (let [pairs-as-map (loop [pairs pairs - map {}] - (if (not (seq pairs)) - map - (let [[x y] (first pairs)] - (recur (rest pairs) - (update map x conj y))))) - runner (fn runner [to-be-visited already-visited] - (lazy-seq - (let [not-yet-visited (seq (drop-while #(contains? already-visited %) - to-be-visited)) - unseen-node (first not-yet-visited)] - (when (seq not-yet-visited) - (cons unseen-node - (runner (concat (get pairs-as-map unseen-node) - (rest not-yet-visited)) - (conj already-visited - unseen-node)))))))] - (set-of [x y] [x (keys pairs-as-map) - y (runner (get pairs-as-map x) #{})]))) - -(defn reflexive-transitive-closure - "Computes the reflexive, transitive closure of a given set of pairs - on base-set." - [base-set pairs] - (transitive-closure (union (set pairs) - (set-of [x x] [x base-set])))) - -(defn transitive-reduction - "Returns for a set of pairs its transitive reduction. Alternatively, - the relation can be given as a base set and a predicate p which - returns true in (p x y) iff [x y] is in the relation in question. - - Note that if the relation given is not acyclic, the transitive - closure of the reduction may not yield the transitive closure of the - original relation anymore, since the reduction itself can be empty." - ([pairs] - (let [result (atom (transient #{}))] - (doseq [[x y] pairs] - (when (not (exists [[a b] pairs, - [c d] pairs] - (and (= a x) - (= b c) - (= d y)))) - (swap! result conj! [x y]))) - (persistent! @result))) - ([base pred] - (let [result (atom (transient #{}))] - (doseq [x base, y base] - (when (and (pred x y) - (not (exists [z base] - (and (not= x z) - (not= z y) - (pred x z) - (pred z y))))) - (swap! result conj! [x y]))) - (persistent! @result)))) - -(defn graph-of-function? - "Returns true iff relation is the graph of a function from source to target." - [relation source target] - (and (= (set-of x [[x y] relation]) source) - (subset? (set-of y [[x y] relation]) target) - (= (count source) (count relation)))) - -(defn minimal-generating-subsets - "Given a set A and a closure operator clop returns all subsets B of - A such that (= (clop A) (clop B))." - [clop A] - (let [clop-A (clop A)] - (loop [left [A], ;yet to consider - minimals []] ;minimal elements already found - (if (empty? left) - (distinct minimals) - (let [next (first left)] - (if (empty? next) - (recur (rest left) (conj minimals next)) - (let [generating-subsets (set-of X [x next, - :let [X (disj next x)] - :when (= clop-A (clop X))])] - (if (empty? generating-subsets) - (recur (rest left) (conj minimals next)) - (recur (into (rest left) generating-subsets) minimals))))))))) - -(defn partial-min - "For a given partial order <= and given elements returns the minimal - among them." - [<= xs] - (let [runner (fn runner [left minimals] - (if (empty? left) - minimals - (let [next (first left), - new-minimals (remove #(<= next %) minimals)] - (if (not= (count minimals) (count new-minimals)) - (recur (rest left) (conj new-minimals next)) - (if (some #(<= % next) minimals) - (recur (rest left) minimals) - (recur (rest left) (conj minimals next)))))))] - (runner xs ()))) - -(defn partial-max - "For a given partial order <= and given elements returns the maximal - among them." - [<= xs] - (partial-min #(<= %2 %1) xs)) - ;;; Hypergraph Transversals (defn- intersection-set? diff --git a/src/main/clojure/conexp/fca/closure_systems.clj b/src/main/clojure/conexp/fca/closure_systems.clj new file mode 100644 index 00000000..40473963 --- /dev/null +++ b/src/main/clojure/conexp/fca/closure_systems.clj @@ -0,0 +1,269 @@ +;; 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.closure-systems + "Next closure algorithm." + (:require [conexp.base :refer :all] + [clojure.set :refer [difference intersection union subset?]])) + +;;; Next Closure + +(defn lectic-<_i + "Implements lectic < at position i. The basic order is given by the ordering + of base which is interpreted as increasing order. + + A and B have to be sets." + [base i A B] + (and (contains? B i) (not (contains? A i)) ; A and B will always be sets + (loop [elements base] + (let [j (first elements)] + (if (= j i) + true + (if (identical? (contains? B j) (contains? A j)) + (recur (rest elements)) + false)))))) + +(defn lectic-< + "Implements lectic ordering. The basic order is given by the ordering of base + which is interpreted as increasing order." + [base A B] + (exists [i base] (lectic-<_i base i A B))) + +(defn next-closed-set-in-family + "Computes next closed set as with next-closed-set, which is in the + family F of all closed sets satisfing predicate. predicate has to + satisfy the condition + + A in F and i in base ==> clop(A union {1, ..., i-1}) in F. + " + [predicate base clop A] + (loop [i-s (reverse base), + A (set A)] + (if (empty? i-s) + nil + (let [i (first i-s)] + (if (contains? A i) + (recur (rest i-s) (disj A i)) + (let [clop-A (clop (conj A i))] + (if (and (lectic-<_i base i A clop-A) + (predicate clop-A)) + clop-A + (recur (rest i-s) A)))))))) + +(defn improve-basic-order + "Improves basic order on the sequence base, where the closure operator + clop operates on." + [base clop] + (let [base (seq base), + clop (memoize clop)] + (sort (fn [x y] + (if (= (clop #{y}) (clop #{x})) + 0 + (if (lectic-< base (clop #{y}) (clop #{x})) + -1 + 1))) + base))) + +(defn all-closed-sets-in-family + "Computes all closed sets of a given closure operator on a given set + base contained in the family described by predicate. See + documentation of next-closed-set-in-family for more details. Uses + initial as first closed set if supplied." + ([predicate base clop] + (all-closed-sets-in-family predicate base clop #{})) + ([predicate base clop initial] + (lazy-seq + (let [base (if (set? base) (improve-basic-order base clop) base), + initial (clop initial), + start (if (predicate initial) + initial + (next-closed-set-in-family predicate base clop initial)), + runner (fn runner [X] + (lazy-seq + (if (nil? X) + nil + (cons X (runner (next-closed-set-in-family predicate base clop X))))))] + (runner start))))) + +(defn next-closed-set + "Computes next closed set of the closure operator clop after A with + the Next Closure algorithm. The order of elements in base, + interpreted as increasing, is taken to be the basic order of the + elements." + [base clop A] + (next-closed-set-in-family (constantly true) base clop A)) + +(defn all-closed-sets + "Computes all closed sets of a given closure operator on a given + set. Uses initial as first closed set if supplied." + ([base clop] + (all-closed-sets base clop #{})) + ([base clop initial] + (all-closed-sets-in-family (constantly true) base clop initial))) + +(defn parallel-closures + "Returns the set of all closures of the closure operator on the given base set. + Computes the closures in parallel, to the extent possible." + [base clop] + (loop [n 0 + closures #{(clop #{})} + current #{(clop #{})}] + (if (< (count base) n) + closures + (let [next-current (atom current)] + (dopar [C current] + (when (not= (count C) n) + (swap! next-current #(disj % C)) + (doseq [x base :when (not (contains? C x))] + (swap! next-current #(conj % (clop (conj C x))))))) + (recur (inc n) (into closures @next-current) @next-current))))) + +;;; Extension + +(defn non-closed-elements + "Given a closure operator (c) and a set (X) returns the subset + {x in X | c({x}) != {x}}." + [base clop] + (set (filter #(not= #{%} (clop #{%})) base))) + +(defn exclusive-closure + "Given a closure operator (c) and a set (s) returns + c(s)/s" + [set clop] + (difference (clop set) set)) + +(defn- extendable-set + "Given a closure system and a element, return the subset of the closure + system whom the element closure is not a subset of." + [closure clop element] + (let [x-closure (exclusive-closure #{element} clop)] + (filter #(not (subset? x-closure %)) closure))) + +(defn extension-set + "Adds the given element to each element of a closure system." + [closure clop element] + (for [F (extendable-set closure clop element)] + (conj F element))) + +(defn extend-closure + "Extents the closure system by an additional given element." + [closure clop element] + (union closure (extension-set closure clop element))) + +;;; Common Math Algorithms + +(defn transitive-closure + "Computes transitive closure of a given set of pairs." + ;; Inspired by the corresponding code from the graph library by + ;; Jeffrey Straszheim + [pairs] + (let [pairs-as-map (loop [pairs pairs + map {}] + (if (not (seq pairs)) + map + (let [[x y] (first pairs)] + (recur (rest pairs) + (update map x conj y))))) + runner (fn runner [to-be-visited already-visited] + (lazy-seq + (let [not-yet-visited (seq (drop-while #(contains? already-visited %) + to-be-visited)) + unseen-node (first not-yet-visited)] + (when (seq not-yet-visited) + (cons unseen-node + (runner (concat (get pairs-as-map unseen-node) + (rest not-yet-visited)) + (conj already-visited + unseen-node)))))))] + (set-of [x y] [x (keys pairs-as-map) + y (runner (get pairs-as-map x) #{})]))) + +(defn reflexive-transitive-closure + "Computes the reflexive, transitive closure of a given set of pairs + on base-set." + [base-set pairs] + (transitive-closure (union (set pairs) + (set-of [x x] [x base-set])))) + +(defn transitive-reduction + "Returns for a set of pairs its transitive reduction. Alternatively, + the relation can be given as a base set and a predicate p which + returns true in (p x y) iff [x y] is in the relation in question. + + Note that if the relation given is not acyclic, the transitive + closure of the reduction may not yield the transitive closure of the + original relation anymore, since the reduction itself can be empty." + ([pairs] + (let [result (atom (transient #{}))] + (doseq [[x y] pairs] + (when (not (exists [[a b] pairs, + [c d] pairs] + (and (= a x) + (= b c) + (= d y)))) + (swap! result conj! [x y]))) + (persistent! @result))) + ([base pred] + (let [result (atom (transient #{}))] + (doseq [x base, y base] + (when (and (pred x y) + (not (exists [z base] + (and (not= x z) + (not= z y) + (pred x z) + (pred z y))))) + (swap! result conj! [x y]))) + (persistent! @result)))) + +(defn graph-of-function? + "Returns true iff relation is the graph of a function from source to target." + [relation source target] + (and (= (set-of x [[x y] relation]) source) + (subset? (set-of y [[x y] relation]) target) + (= (count source) (count relation)))) + +(defn minimal-generating-subsets + "Given a set A and a closure operator clop returns all subsets B of + A such that (= (clop A) (clop B))." + [clop A] + (let [clop-A (clop A)] + (loop [left [A], ;yet to consider + minimals []] ;minimal elements already found + (if (empty? left) + (distinct minimals) + (let [next (first left)] + (if (empty? next) + (recur (rest left) (conj minimals next)) + (let [generating-subsets (set-of X [x next, + :let [X (disj next x)] + :when (= clop-A (clop X))])] + (if (empty? generating-subsets) + (recur (rest left) (conj minimals next)) + (recur (into (rest left) generating-subsets) minimals))))))))) + +(defn partial-min + "For a given partial order <= and given elements returns the minimal + among them." + [<= xs] + (let [runner (fn runner [left minimals] + (if (empty? left) + minimals + (let [next (first left), + new-minimals (remove #(<= next %) minimals)] + (if (not= (count minimals) (count new-minimals)) + (recur (rest left) (conj new-minimals next)) + (if (some #(<= % next) minimals) + (recur (rest left) minimals) + (recur (rest left) (conj minimals next)))))))] + (runner xs ()))) + +(defn partial-max + "For a given partial order <= and given elements returns the maximal + among them." + [<= xs] + (partial-min #(<= %2 %1) xs)) diff --git a/src/main/clojure/conexp/fca/contexts.clj b/src/main/clojure/conexp/fca/contexts.clj index 1bb7a4f9..805fc8d2 100644 --- a/src/main/clojure/conexp/fca/contexts.clj +++ b/src/main/clojure/conexp/fca/contexts.clj @@ -11,7 +11,7 @@ (:require [clojure.core.reducers :as r] [clojure.set :refer [difference intersection union subset?]] [conexp.base :refer :all] - )) + [conexp.fca.closure-systems :refer :all])) ;;; diff --git a/src/main/clojure/conexp/fca/exploration.clj b/src/main/clojure/conexp/fca/exploration.clj index 7e9d85d3..63eb197c 100644 --- a/src/main/clojure/conexp/fca/exploration.clj +++ b/src/main/clojure/conexp/fca/exploration.clj @@ -10,7 +10,8 @@ "Provides function for exploration and computing proper premises." (:use conexp.base conexp.fca.contexts - conexp.fca.implications) + conexp.fca.implications + conexp.fca.closure-systems) (:require [clojure.core.reducers :as r] [clojure.set :refer [difference union intersection subset? ]])) diff --git a/src/main/clojure/conexp/fca/fast.clj b/src/main/clojure/conexp/fca/fast.clj index 763ec4e8..90fd5b19 100644 --- a/src/main/clojure/conexp/fca/fast.clj +++ b/src/main/clojure/conexp/fca/fast.clj @@ -9,7 +9,8 @@ (ns conexp.fca.fast "Provides some optimized versions of the standard algorithms of conexp-clj" (:require [clojure.core.async :refer [!! chan close! thread]] - [conexp.base :refer [illegal-argument improve-basic-order set-of]] + [conexp.base :refer [illegal-argument set-of]] + [conexp.fca.closure-systems :refer [improve-basic-order]] [conexp.fca.contexts :refer [attribute-derivation diff --git a/src/main/clojure/conexp/fca/implications.clj b/src/main/clojure/conexp/fca/implications.clj index 8b2e9625..9311036d 100644 --- a/src/main/clojure/conexp/fca/implications.clj +++ b/src/main/clojure/conexp/fca/implications.clj @@ -11,10 +11,12 @@ (:require [clojure.core.reducers :as r] [conexp.base :refer :all] [conexp.math.algebra :refer :all] + [conexp.fca.closure-systems :refer [next-closed-set-in-family + all-closed-sets-in-family + extension-set]] [conexp.fca.contexts :refer :all] [clojure.set :refer [difference union subset? intersection]] - [clojure.math.numeric-tower :as nt] - )) + [clojure.math.numeric-tower :as nt])) ;;; 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 index 245a0eec..11210b62 100644 --- a/src/main/clojure/conexp/fca/incomplete_contexts/exploration_shared_implications.clj +++ b/src/main/clojure/conexp/fca/incomplete_contexts/exploration_shared_implications.clj @@ -5,6 +5,7 @@ [clojure.math.combinatorics :refer [subsets]] [clojure.algo.generic.functor :refer [fmap]] [clojure.string :as s] + [conexp.fca.closure-systems :refer [next-closed-set]] [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] @@ -24,7 +25,7 @@ (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 + (next-closed-set M (clop-by-implications L) A)) 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 index fa4530af..1e73f92d 100644 --- a/src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts_exploration.clj +++ b/src/main/clojure/conexp/fca/incomplete_contexts/incomplete_contexts_exploration.clj @@ -2,7 +2,8 @@ "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.base :refer [ask exists]] + [conexp.fca.closure-systems :refer [next-closed-set]] [conexp.fca.implications :refer :all] [conexp.fca.incomplete-contexts.incomplete-contexts :refer :all])) diff --git a/src/main/clojure/conexp/fca/incremental_ganter.clj b/src/main/clojure/conexp/fca/incremental_ganter.clj index 64ead8cd..26a771a8 100644 --- a/src/main/clojure/conexp/fca/incremental_ganter.clj +++ b/src/main/clojure/conexp/fca/incremental_ganter.clj @@ -14,7 +14,8 @@ (:require [conexp.base :refer :all] [conexp.fca.implications :refer :all] [conexp.fca.contexts :refer :all] - [clojure.set :refer [difference union subset? intersection]])) + [conexp.fca.closure-systems :refer :all] + [clojure.set :refer [difference union subset? intersection]] )) ;;; diff --git a/src/main/clojure/conexp/fca/more.clj b/src/main/clojure/conexp/fca/more.clj index da2dd14f..ea04c7b2 100644 --- a/src/main/clojure/conexp/fca/more.clj +++ b/src/main/clojure/conexp/fca/more.clj @@ -14,7 +14,8 @@ [conexp.fca [contexts :refer :all] [exploration :refer :all] - [implications :refer :all]] + [implications :refer :all] + [closure-systems :refer :all]] [conexp.math.util :refer [eval-polynomial]])) ;;; Bonds diff --git a/src/main/clojure/conexp/fca/ordinal_motifs.clj b/src/main/clojure/conexp/fca/ordinal_motifs.clj index 6f38f335..c0580051 100644 --- a/src/main/clojure/conexp/fca/ordinal_motifs.clj +++ b/src/main/clojure/conexp/fca/ordinal_motifs.clj @@ -7,7 +7,7 @@ [clojure.math.combinatorics :as comb] [clojure.algo.generic.functor :refer :all] [clojure.algo.generic.collection :as generic-col] - [clojure.set :refer [difference union subset? intersection]])) + [clojure.set :refer [difference intersection union subset?]])) ;;;;;;;; ordinal motifs diff --git a/src/main/clojure/conexp/fca/simplicial_complexes.clj b/src/main/clojure/conexp/fca/simplicial_complexes.clj new file mode 100644 index 00000000..09fb31e0 --- /dev/null +++ b/src/main/clojure/conexp/fca/simplicial_complexes.clj @@ -0,0 +1,273 @@ +;; 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.simplicial-complexes + "Provides the implementation of simplicial complexes and functions on them." + (:require [conexp.base :refer :all] + [conexp.fca.closure-systems :refer [next-closed-set-in-family]] + [conexp.fca.contexts :refer [extents + objects]] + [conexp.fca.implications :refer [clop-by-implications + close-under-implications + implication? + make-implication]] + [conexp.fca.lattices :refer [concept-lattice + inf + lattice-base-set + lattice-order + sup]] + [conexp.fca.ordinal-motifs :refer [generate-scale + identify-full-scale-measures]] + [clojure.set :refer [difference intersection union subset?]]) + (:import conexp.fca.contexts.Formal-Context + conexp.fca.lattices.Lattice)) + +;;; Data structure + +(defprotocol SimplicialComplex + (base [this] "Returns the base set.") + (simplices [this] "Returns the simplices of the simplicial complex, + which are subsets of its base set.")) + +(deftype FullSimplicialComplex [base simplices] + Object + (equals [this other] + (and (= (class this) (class other)) + (= (.base this) (.base ^FullSimplicialComplex other)) + (= (set (.simplices this)) (set (.simplices ^FullSimplicialComplex other))))) + (hashCode [this] + (hash-combine-hash FullSimplicialComplex base simplices)) + (toString [this] + (str (set (.simplices this)))) + ;; + SimplicialComplex + (base [this] base) + (simplices [this] simplices)) + +(defmethod print-method FullSimplicialComplex [^FullSimplicialComplex simplicial-complex, + ^java.io.Writer out] + (.write out + ^String (str simplicial-complex))) + +;;; Constructors + +(defmulti make-full-simplicial-complex-nc + "Creates a full simplicial complex from the given arguments, without any checks." + {:arglist '([simplices] + [base simplices])} + (fn [& args] (vec (map clojure-type args)))) + +(defmethod make-full-simplicial-complex-nc [clojure-set clojure-coll] [base simplices] + (FullSimplicialComplex. base simplices)) + +(defmethod make-full-simplicial-complex-nc [clojure-coll] [simplices] + (let [base (apply union simplices)] + (make-full-simplicial-complex-nc base simplices))) + +(defmethod make-full-simplicial-complex-nc :default [& args] + (illegal-argument "The arguments " args " are not valid for a Full Simplicial Complex.")) + +(defn is-simplicial-complex? + "Check if given object follows the definition of a simplicial complex." + [simplicial-complex] + (and + (let [simplices-set (set (.simplices simplicial-complex))] + (every? (fn [simplex] + (if (not= simplex #{}) + (let [direct-sub-simplices + (map #(difference (set simplex) #{%}) simplex)] + (every? #(contains? simplices-set %) direct-sub-simplices)) + true)) + simplices-set)) + ;; also check if base set is correct (contains all elements contained in simplices) + (let [base-set-derived-from-simplices (apply union (.simplices simplicial-complex))] + (subset? base-set-derived-from-simplices (.base simplicial-complex))))) + +(defmulti make-full-simplicial-complex + "Creates a full simplicial complex from the given arguments, and checks that it really is a full simplicial complex." + {:arglist '([simplices] + [base simplices])} + (fn [& args] (vec (map clojure-type args)))) + +(defmethod make-full-simplicial-complex [clojure-set clojure-coll] [base simplices] + (let [simplicial-complex (make-full-simplicial-complex-nc base simplices)] + (when-not (is-simplicial-complex? simplicial-complex) + (illegal-argument "Given arguments do not describe a simplicial complex.")) + simplicial-complex)) + +(defmethod make-full-simplicial-complex [clojure-coll] [simplices] + (let [base (apply union simplices)] + (make-full-simplicial-complex base simplices))) + + +;; FCA + +(defn simplicial-complex-from-clop + "Given a closure operator «clop» on the set «base», computes its canonical base, + optionally using the set «background-knowledge» of implications on «base-set» + as background knowledge. The result will be a lazy sequence. If «predicate» + is given as third argument, computes only those implications whose premise + satisfy this predicate. Note that «predicate» has to satisfy the same + conditions as the one of «next-closed-set-in-family»." + ([clop base] + (simplicial-complex-from-clop clop base #{} (constantly true))) + ([clop base background-knowledge] + (simplicial-complex-from-clop clop base background-knowledge (constantly true))) + ([clop base background-knowledge predicate] + (assert (fn? clop) + "Given closure operator must be a function") + (assert (coll? base) + "Base must be a collection") + (assert (fn? predicate) + "Predicate must be a function") + (assert (and (set? background-knowledge) + (forall [x background-knowledge] + (implication? x))) + "Background knowledge must be a set of implications") + (let [next-closure (fn [implications last] + (next-closed-set-in-family predicate + base + (clop-by-implications implications) + last)), + runner (fn runner [implications simplicial-complex candidate] + (when candidate + (if (not (clop candidate)) + (let [impl (make-implication candidate base), + impls (conj implications impl)] + (recur impls simplicial-complex (next-closure impls candidate))) + (let [s-complex (conj simplicial-complex candidate)] + (cons candidate + (lazy-seq (runner implications + s-complex + (next-closure implications candidate))))))))] + (lazy-seq (runner background-knowledge + #{} + (close-under-implications background-knowledge #{})))))) + +;; + +(defn- join-operator + "Join operator of a given lattice for an input set of arbitrary length." + [lattice] + (fn [concept-set] + (if (= 0 (count concept-set)) + ;; bottom element + (reduce (inf lattice) (lattice-base-set lattice)) + (if (= 1 (count concept-set)) + (first concept-set) + (reduce (sup lattice) concept-set))))) + +(defn- not>=t-operator + [lattice t] + (fn [concept] + (not ((lattice-order lattice) t concept)))) + +(defn- t-simplex-operator + [lattice t] + (fn [concept-set] + ((not>=t-operator lattice t) ((join-operator lattice) concept-set)))) + +(defmulti t-simplex-next-closure + "Creates a t-simplex from a given object with next closure algorithm." + (fn [object t] (type object))) + +(defmethod t-simplex-next-closure Lattice + [lattice t] + (let [base (lattice-base-set lattice) + closure-condition (t-simplex-operator lattice t) + simplices (simplicial-complex-from-clop closure-condition base)] + (FullSimplicialComplex. base simplices))) + +(defmethod t-simplex-next-closure Formal-Context + [ctx t] + (let [lattice (concept-lattice ctx)] + (t-simplex-next-closure lattice t))) + +(defmethod t-simplex-next-closure :default + [object & args] + (illegal-argument "Cannot compute a simplicial complex from type " (type object) ".")) + +;; + +(defn- closure-condition-operator + "Closure operator for :nominal, :ordinal, :interordinal and + :contranominal scales." + [context scale-type] + (let [context-extents (extents context)] + (fn [object-set] + (let [subset-size (count object-set)] + (if (< subset-size 2) + true + (let [scale-extents (set (extents (generate-scale scale-type subset-size)))] + (identify-full-scale-measures scale-type + context-extents object-set + scale-extents))))))) + +(defn- compute-ordinal-motifs-next-closure + [ctx scale-type] + (let [base (objects ctx) + closure-condition (closure-condition-operator ctx scale-type) + simplices (simplicial-complex-from-clop closure-condition base)] + (FullSimplicialComplex. base + simplices))) + +(defmulti ordinal-motif-next-closure + "Creates ordinal motifs from a given context and scale-type with next closure algorithm." + (fn [context scale-type] scale-type)) + +(defmethod ordinal-motif-next-closure :ordinal + [ctx scale-type] + (compute-ordinal-motifs-next-closure ctx scale-type)) + +(defmethod ordinal-motif-next-closure :interordinal + [ctx scale-type] + (compute-ordinal-motifs-next-closure ctx scale-type)) + +(defmethod ordinal-motif-next-closure :nominal + [ctx scale-type] + (compute-ordinal-motifs-next-closure ctx scale-type)) + +(defmethod ordinal-motif-next-closure :contranominal + [ctx scale-type] + (compute-ordinal-motifs-next-closure ctx scale-type)) + +(defmethod ordinal-motif-next-closure :default + [ctx scale-type & args] + (illegal-argument "Cannot compute ordinal motifs for scale " scale-type ".")) + + +;; Simplicial Complex Analytics + +(defn face-dimension + [face] + (- (count face) 1)) + +(defn complex-dimension + "The dimension of the complex dim(Δ) is defined as the largest dimension of any of its faces. + Since every face is element of Δ, it suffices to chek all these elements." + [sc] + (let [faces (.simplices sc)] + (reduce max (map face-dimension faces)))) + + +(defn sc-matrix-rep [sc n] + "For a given simplicial complex sc and a face dimension n, compute the boundary map ∂ₙ in matrix representation. The element on line i and column j of ∂ₙ is 1 iff the n-1-simplex at position i is a subset of the n-simplex at position j." + (let [simps (.simplices sc) + n_simps (filter (fn [x] (== (face-dimension x) n)) simps) + n_1_simps (filter (fn [x] (== (face-dimension x) (- n 1))) simps)] + (vec (for [x n_1_simps] + (vec (for [y n_simps] + ((fn [x y] (if (subset? x y) 1 0)) x y))))))) + +(defn sc-chain-complex + "Given a lattice lat and a chain in lat, compute the simplicial complexes along the chain, compute for each complex the boundary map for n and n + 1 in matrix representation. Returns a vector of vectors, each consisting of the chain element t and the two boundary matrices ∂ₙ and ∂_{n+1}." + [lat chain n] + (vec + (for [t chain] + (let [sc (t-simplex-next-closure lat t)] + [t (sc-matrix-rep sc (+ n 1)) (sc-matrix-rep sc n)])))) diff --git a/src/main/clojure/conexp/fca/smeasure.clj b/src/main/clojure/conexp/fca/smeasure.clj index 3cba71fa..022d9755 100644 --- a/src/main/clojure/conexp/fca/smeasure.clj +++ b/src/main/clojure/conexp/fca/smeasure.clj @@ -1,3 +1,4 @@ + ;; 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) @@ -8,6 +9,7 @@ (ns conexp.fca.smeasure (:require [conexp.base :refer :all] + [conexp.fca.closure-systems :refer :all] [conexp.fca.contexts :refer :all] [conexp.fca.concept-transform :refer :all] [conexp.fca.cover :refer [generate-concept-cover]] diff --git a/src/main/clojure/conexp/fca/triadic_exploration.clj b/src/main/clojure/conexp/fca/triadic_exploration.clj index 4ec9124c..38f144ee 100644 --- a/src/main/clojure/conexp/fca/triadic_exploration.clj +++ b/src/main/clojure/conexp/fca/triadic_exploration.clj @@ -1,10 +1,10 @@ (ns conexp.fca.triadic-exploration (:require [clojure.algo.generic.functor :refer [fmap]] + [conexp.fca.closure-systems :refer [next-closed-set]] [conexp.fca.contexts :as cxt] [conexp.fca.implications :as impl] [conexp.fca.exploration :as expl] - [conexp.base :as base] - )) + [conexp.base :as base])) @@ -154,7 +154,7 @@ (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 + (next-closed-set M (impl/clop-by-implications L) A)) diff --git a/src/main/clojure/conexp/layouts/base.clj b/src/main/clojure/conexp/layouts/base.clj index 50d49a1f..8f8e3953 100644 --- a/src/main/clojure/conexp/layouts/base.clj +++ b/src/main/clojure/conexp/layouts/base.clj @@ -12,9 +12,10 @@ conexp.math.algebra conexp.fca.lattices conexp.fca.posets + conexp.fca.closure-systems clojure.pprint) (:require - [clojure.set :refer [difference subset? superset? intersection]])) + [clojure.set :refer [difference subset? superset? intersection]])) ;;; diff --git a/src/main/clojure/conexp/layouts/util.clj b/src/main/clojure/conexp/layouts/util.clj index 9e042138..3e96d9af 100644 --- a/src/main/clojure/conexp/layouts/util.clj +++ b/src/main/clojure/conexp/layouts/util.clj @@ -14,7 +14,8 @@ [conexp.fca.protoconcepts :refer :all] [conexp.fca.posets :refer :all] [conexp.layouts.base :refer :all] - [conexp.util.graph :as graph]) + [conexp.util.graph :as graph] + [conexp.fca.closure-systems :refer :all]) (:import [conexp.fca.posets Poset] [conexp.fca.lattices Lattice] [conexp.fca.protoconcepts Protoconcepts])) diff --git a/src/test/clojure/conexp/base_test.clj b/src/test/clojure/conexp/base_test.clj index 3e8a7e72..9ca16346 100644 --- a/src/test/clojure/conexp/base_test.clj +++ b/src/test/clojure/conexp/base_test.clj @@ -287,206 +287,6 @@ (is (= (to-set ['a 'b]) '#{a b})) (is (thrown? IllegalArgumentException (to-set 'a)))) -(deftest test-lectic-<_i - (is (lectic-<_i [5 7 3 2 1] 2 #{5 3 1} #{5 3 2 1})) - (is (lectic-<_i [5 7 3 2 1] 5 #{3} #{5 7})) - (is (lectic-<_i [5 7 3 2 1] 3 #{2} #{3})) - (is (lectic-<_i [5 7 3 2 1] 1 #{} #{1})) - (is (not (lectic-<_i [5 7 3 2 1] 5 #{5 3 2} #{5 7 3 2}))) - (is (not (lectic-<_i [5 7 3 2 1] 2 #{5 7 3} #{5 7 2}))) - (is (lectic-<_i [1 nil] nil #{1} #{1 nil}))) - -(deftest test-lectic-< - (is (lectic-< [5 7 3 2 1] #{} #{5})) - (is (lectic-< [5 7 3 2 1] #{7 2 1} #{5})) - (is (not (lectic-< [5 7 3 2 1] #{5 7 3 2 1} #{7 3 2 1})))) - -(deftest test-next-closed-set-in-family - (are [set next] (= (next-closed-set-in-family #(< (count %) 3) - [3 2 0 1] - identity - set) - next) - #{} #{1} - #{1} #{0} - #{0} #{0 1} - #{0 1} #{2} - #{2} #{2 1} - #{2 1} #{2 0} - #{2 0} #{3} - #{3} #{3 1} - #{3 1} #{3 0} - #{3 0} #{3 2} - #{3 2} nil)) - -(deftest test-improve-basic-order - (is (= #{1 2 3} (set (improve-basic-order [3 2 1] #(conj % 1))))) - (is (= #{} (set (improve-basic-order [] identity))))) - -(deftest test-all-closed-sets-in-family - (is (= '(#{2} #{1 2} #{0 2} #{3} #{1 3} #{0 3} #{2 3}) - (all-closed-sets-in-family #(< (count %) 3) [3 2 0 1] identity #{2}))) - (is (= '(#{0} #{0 3} #{0 2} #{0 1}) - (all-closed-sets-in-family #(< (count %) 3) [0 1 2 3] #(conj % 0) #{}))) - (is (= '(#{0} #{0 3} #{0 2} #{0 1}) - (all-closed-sets-in-family #(< (count %) 3) [0 1 2 3] #(conj % 0) #{0})))) - -(deftest test-next-closed-set - (are [set next] (= (next-closed-set [3 2 1] identity set) next) - #{} #{1} - #{1} #{2} - #{2} #{2 1} - #{2 1} #{3} - #{3} #{3 1} - #{3 1} #{3 2} - #{3 2} #{3 2 1} - #{3 2 1} nil) - (are [set next] (= (next-closed-set [1 nil] identity set) next) - #{} #{nil} - #{nil} #{1} - #{1} #{1 nil} - #{1 nil} nil)) - -(deftest test-all-closed-sets - (is (= (all-closed-sets [5 7 3 2 1] #(union % #{3 2 1})) - (seq [#{3 2 1} #{7 3 2 1} #{5 3 2 1} #{5 7 3 2 1}]))) - (is (= (all-closed-sets [3 2 1] identity) - (seq [#{} #{1} #{2} #{2 1} #{3} #{3 1} #{3 2} #{3 2 1}]))) - (is (= (all-closed-sets [1 nil] identity) - (seq [#{} #{nil} #{1} #{1 nil}])))) - -(defn clop-by-subsets [base subsets] - (fn [X] - (reduce intersection base (filter #(subset? X %) subsets)))) - -(deftest test-parallel-closures - (dotimes [i 13] - (is (= (expt 2 (+ i 1)) - (count (parallel-closures (set-of-range (+ i 2)) #(conj % 1)))))) - (are [base subsets] (= (set (all-closed-sets base (clop-by-subsets base subsets))) - (set (parallel-closures base (clop-by-subsets base subsets)))) - #{1 2 3 4} [#{1 2} #{3 4}] - #{1 2 3 4} [#{1} #{2} #{3}] - #{1 2 3 5 7} [#{1 2 3} #{1 2 3 5} #{1 2 3 7}])) - -;;; - -(deftest test-non-closed-elements - (is (= (non-closed-elements #{} - (clop-by-subsets #{} #{})) - #{})) - (is (= (non-closed-elements #{1 2 3 4 5 6} - (clop-by-subsets - #{1 2 3 4 5 6} - #{#{1 3} #{2 4} #{5} #{6}})) - #{1 2 3 4})) - (is (= (non-closed-elements #{'a 'b 'c 'd} - (clop-by-subsets - #{'a 'b 'c 'd} - #{#{'a 'b} #{'c} #{'d}})) - #{'a 'b}))) - -(deftest test-exclusive-closure - (is (= (exclusive-closure #{1} - (clop-by-subsets - #{1 2 3 4 5 6} - #{#{1 3} #{2 4} #{5} #{6}})) - #{3})) - (is (= (exclusive-closure #{'a} - (clop-by-subsets - #{'a 'b 'c 'd} - #{#{'a 'b} #{'c} #{'d}})) - #{'b})) - (is (= (exclusive-closure #{3} - (clop-by-subsets - #{1 2 3 4 5 6 7} - #{#{1 7} #{2 4} #{3 4 5} #{5} #{6}})) - #{4 5}))) - -;;; - -(deftest test-subsets - (is (= #{#{} #{1} #{2} #{1 2}} - (set (subsets #{1 2})))) - (are [x y] (= y (count (subsets (set (range x))))) - 0 1 - 1 2 - 2 4 - 8 256 - 10 1024) - (are [my-set] (and (forall [s (subsets my-set)] (subset? s my-set)) - (exists [s (subsets my-set)] (or (not (proper-subset? s my-set)) - (empty? s)))) - #{} - #{1 2} - #{'a 4} - #{+ 3} - #{2 3 4 'r -})) - -(deftest test-transitive-closure - (are [x y] (= (transitive-closure x) y) - #{[1 2] [2 1]} #{[1 1] [2 2] [1 2] [2 1]} - #{} #{} - #{[1 'a]} #{[1 'a]} - #{[+ -] [- *]} #{[+ -] [- *] [+ *]} - #{[1 2] [2 3] [3 4]} #{[1 2] [2 3] [3 4] [1 3] [1 4] [2 4]})) - -(deftest test-reflexive-transitive-closure - (is (= #{[1 2] [1 1] [2 2]} - (reflexive-transitive-closure [1 2] #{[1 2]}))) - (is (= #{[nil nil] ['a nil] ['a 'a]} - (reflexive-transitive-closure [nil 'a] #{['a nil]})))) - -(deftest test-transitive-reduction - (is (= #{[1 2] [2 3]} - (transitive-reduction #{[1 2] [2 3] [1 3]}))) - (is (empty? (transitive-reduction (cross-product [1 2 3] [1 2 3])))) - (is (= #{[1 2] [2 3] [3 4] [4 5]} - (transitive-reduction [1 2 3 4 5] <))) - (is (let [subs (subsets #{1 2 3 4 5}), - reduct (transitive-reduction (subsets #{1 2 3 4 5 6 7}) - proper-subset?)] - (forall [[x y] reduct, z subs] - (not (and (not= x z) - (not= z y) - (proper-subset? x z) - (proper-subset? z y))))))) - -(deftest test-graph-of-function? - (are [rel src trg] (graph-of-function? rel src trg) - #{[1 2] [2 1]} #{1 2} #{1 2} - #{} #{} #{} - #{} #{} #{1 2 3} - #{[1 2] [2 3] [3 1]} #{1 2 3} #{1 2 3 4 5} - #{[1 1] [2 1] [3 1]} #{1 2 3} #{1 7}) - (are [rel src trg] (not (graph-of-function? rel src trg)) - #{[1 1] [1 2]} #{1 2} #{1 2} - #{} #{1} #{} - #{[1 2] [2 3] [3 4]} #{1 2 3} #{1 2 3} - #{[1 2] [2 2]} #{1} #{2})) - -(deftest test-minimal-generating-sets - (are [set clop minimal-generators] (= (set minimal-generators) - (set (minimal-generating-subsets clop set))) - #{1} identity [#{1}], - #{} #(conj % 1) [#{}], - #{1 2 3 4 5} - #(if (< (count %) 3) - % - #{1 2 3 4 5}) - [#{3 4 5} #{2 4 5} #{2 3 5} #{2 3 4} #{1 4 5} #{1 3 5} #{1 3 4} #{1 2 5} #{1 2 4} #{1 2 3}])) - -(deftest test-partial-min - (are [order seq minimals] (= (set minimals) (set (partial-min order seq))) - <= [1 2 3 4] [1], - subset? [#{1 2 3} #{1 2} #{1 3}] [#{1 2} #{1 3}])) - -(deftest test-partial-max - (are [order seq minimals] (= (set minimals) (set (partial-max order seq))) - <= [1 2 3 4] [4], - subset? [#{1 2 3} #{1 2} #{1 3}] [#{1 2 3}], - subset? [#{2 3 4} #{1 2 3} #{1 2} #{1}] [#{2 3 4} #{1 2 3}])) - (deftest test-minimal-hypergraph-transversals (are [sets minimal-sets] (= (set minimal-sets) (set (minimal-hypergraph-transversals (reduce union sets) sets))) diff --git a/src/test/clojure/conexp/fca/closure_systems_test.clj b/src/test/clojure/conexp/fca/closure_systems_test.clj new file mode 100644 index 00000000..00e6747c --- /dev/null +++ b/src/test/clojure/conexp/fca/closure_systems_test.clj @@ -0,0 +1,213 @@ +;; 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.closure-systems-test + (:use clojure.test + conexp.fca.closure-systems + conexp.base) + (:require [clojure.set :refer [difference intersection union subset?]])) + +(deftest test-lectic-<_i + (is (lectic-<_i [5 7 3 2 1] 2 #{5 3 1} #{5 3 2 1})) + (is (lectic-<_i [5 7 3 2 1] 5 #{3} #{5 7})) + (is (lectic-<_i [5 7 3 2 1] 3 #{2} #{3})) + (is (lectic-<_i [5 7 3 2 1] 1 #{} #{1})) + (is (not (lectic-<_i [5 7 3 2 1] 5 #{5 3 2} #{5 7 3 2}))) + (is (not (lectic-<_i [5 7 3 2 1] 2 #{5 7 3} #{5 7 2}))) + (is (lectic-<_i [1 nil] nil #{1} #{1 nil}))) + +(deftest test-lectic-< + (is (lectic-< [5 7 3 2 1] #{} #{5})) + (is (lectic-< [5 7 3 2 1] #{7 2 1} #{5})) + (is (not (lectic-< [5 7 3 2 1] #{5 7 3 2 1} #{7 3 2 1})))) + +(deftest test-next-closed-set-in-family + (are [set next] (= (next-closed-set-in-family #(< (count %) 3) + [3 2 0 1] + identity + set) + next) + #{} #{1} + #{1} #{0} + #{0} #{0 1} + #{0 1} #{2} + #{2} #{2 1} + #{2 1} #{2 0} + #{2 0} #{3} + #{3} #{3 1} + #{3 1} #{3 0} + #{3 0} #{3 2} + #{3 2} nil)) + +(deftest test-improve-basic-order + (is (= #{1 2 3} (set (improve-basic-order [3 2 1] #(conj % 1))))) + (is (= #{} (set (improve-basic-order [] identity))))) + +(deftest test-all-closed-sets-in-family + (is (= '(#{2} #{1 2} #{0 2} #{3} #{1 3} #{0 3} #{2 3}) + (all-closed-sets-in-family #(< (count %) 3) [3 2 0 1] identity #{2}))) + (is (= '(#{0} #{0 3} #{0 2} #{0 1}) + (all-closed-sets-in-family #(< (count %) 3) [0 1 2 3] #(conj % 0) #{}))) + (is (= '(#{0} #{0 3} #{0 2} #{0 1}) + (all-closed-sets-in-family #(< (count %) 3) [0 1 2 3] #(conj % 0) #{0})))) + +(deftest test-next-closed-set + (are [set next] (= (next-closed-set [3 2 1] identity set) next) + #{} #{1} + #{1} #{2} + #{2} #{2 1} + #{2 1} #{3} + #{3} #{3 1} + #{3 1} #{3 2} + #{3 2} #{3 2 1} + #{3 2 1} nil) + (are [set next] (= (next-closed-set [1 nil] identity set) next) + #{} #{nil} + #{nil} #{1} + #{1} #{1 nil} + #{1 nil} nil)) + +(deftest test-all-closed-sets + (is (= (all-closed-sets [5 7 3 2 1] #(union % #{3 2 1})) + (seq [#{3 2 1} #{7 3 2 1} #{5 3 2 1} #{5 7 3 2 1}]))) + (is (= (all-closed-sets [3 2 1] identity) + (seq [#{} #{1} #{2} #{2 1} #{3} #{3 1} #{3 2} #{3 2 1}]))) + (is (= (all-closed-sets [1 nil] identity) + (seq [#{} #{nil} #{1} #{1 nil}])))) + +(defn clop-by-subsets [base subsets] + (fn [X] + (reduce intersection base (filter #(subset? X %) subsets)))) + +(deftest test-parallel-closures + (dotimes [i 13] + (is (= (expt 2 (+ i 1)) + (count (parallel-closures (set-of-range (+ i 2)) #(conj % 1)))))) + (are [base subsets] (= (set (all-closed-sets base (clop-by-subsets base subsets))) + (set (parallel-closures base (clop-by-subsets base subsets)))) + #{1 2 3 4} [#{1 2} #{3 4}] + #{1 2 3 4} [#{1} #{2} #{3}] + #{1 2 3 5 7} [#{1 2 3} #{1 2 3 5} #{1 2 3 7}])) + +;;; + +(deftest test-non-closed-elements + (is (= (non-closed-elements #{} + (clop-by-subsets #{} #{})) + #{})) + (is (= (non-closed-elements #{1 2 3 4 5 6} + (clop-by-subsets + #{1 2 3 4 5 6} + #{#{1 3} #{2 4} #{5} #{6}})) + #{1 2 3 4})) + (is (= (non-closed-elements #{'a 'b 'c 'd} + (clop-by-subsets + #{'a 'b 'c 'd} + #{#{'a 'b} #{'c} #{'d}})) + #{'a 'b}))) + +(deftest test-exclusive-closure + (is (= (exclusive-closure #{1} + (clop-by-subsets + #{1 2 3 4 5 6} + #{#{1 3} #{2 4} #{5} #{6}})) + #{3})) + (is (= (exclusive-closure #{'a} + (clop-by-subsets + #{'a 'b 'c 'd} + #{#{'a 'b} #{'c} #{'d}})) + #{'b})) + (is (= (exclusive-closure #{3} + (clop-by-subsets + #{1 2 3 4 5 6 7} + #{#{1 7} #{2 4} #{3 4 5} #{5} #{6}})) + #{4 5}))) + +;;; + +(deftest test-subsets + (is (= #{#{} #{1} #{2} #{1 2}} + (set (subsets #{1 2})))) + (are [x y] (= y (count (subsets (set (range x))))) + 0 1 + 1 2 + 2 4 + 8 256 + 10 1024) + (are [my-set] (and (forall [s (subsets my-set)] (subset? s my-set)) + (exists [s (subsets my-set)] (or (not (proper-subset? s my-set)) + (empty? s)))) + #{} + #{1 2} + #{'a 4} + #{+ 3} + #{2 3 4 'r -})) + +(deftest test-transitive-closure + (are [x y] (= (transitive-closure x) y) + #{[1 2] [2 1]} #{[1 1] [2 2] [1 2] [2 1]} + #{} #{} + #{[1 'a]} #{[1 'a]} + #{[+ -] [- *]} #{[+ -] [- *] [+ *]} + #{[1 2] [2 3] [3 4]} #{[1 2] [2 3] [3 4] [1 3] [1 4] [2 4]})) + +(deftest test-reflexive-transitive-closure + (is (= #{[1 2] [1 1] [2 2]} + (reflexive-transitive-closure [1 2] #{[1 2]}))) + (is (= #{[nil nil] ['a nil] ['a 'a]} + (reflexive-transitive-closure [nil 'a] #{['a nil]})))) + +(deftest test-transitive-reduction + (is (= #{[1 2] [2 3]} + (transitive-reduction #{[1 2] [2 3] [1 3]}))) + (is (empty? (transitive-reduction (cross-product [1 2 3] [1 2 3])))) + (is (= #{[1 2] [2 3] [3 4] [4 5]} + (transitive-reduction [1 2 3 4 5] <))) + (is (let [subs (subsets #{1 2 3 4 5}), + reduct (transitive-reduction (subsets #{1 2 3 4 5 6 7}) + proper-subset?)] + (forall [[x y] reduct, z subs] + (not (and (not= x z) + (not= z y) + (proper-subset? x z) + (proper-subset? z y))))))) + +(deftest test-graph-of-function? + (are [rel src trg] (graph-of-function? rel src trg) + #{[1 2] [2 1]} #{1 2} #{1 2} + #{} #{} #{} + #{} #{} #{1 2 3} + #{[1 2] [2 3] [3 1]} #{1 2 3} #{1 2 3 4 5} + #{[1 1] [2 1] [3 1]} #{1 2 3} #{1 7}) + (are [rel src trg] (not (graph-of-function? rel src trg)) + #{[1 1] [1 2]} #{1 2} #{1 2} + #{} #{1} #{} + #{[1 2] [2 3] [3 4]} #{1 2 3} #{1 2 3} + #{[1 2] [2 2]} #{1} #{2})) + +(deftest test-minimal-generating-sets + (are [set clop minimal-generators] (= (set minimal-generators) + (set (minimal-generating-subsets clop set))) + #{1} identity [#{1}], + #{} #(conj % 1) [#{}], + #{1 2 3 4 5} + #(if (< (count %) 3) + % + #{1 2 3 4 5}) + [#{3 4 5} #{2 4 5} #{2 3 5} #{2 3 4} #{1 4 5} #{1 3 5} #{1 3 4} #{1 2 5} #{1 2 4} #{1 2 3}])) + +(deftest test-partial-min + (are [order seq minimals] (= (set minimals) (set (partial-min order seq))) + <= [1 2 3 4] [1], + subset? [#{1 2 3} #{1 2} #{1 3}] [#{1 2} #{1 3}])) + +(deftest test-partial-max + (are [order seq minimals] (= (set minimals) (set (partial-max order seq))) + <= [1 2 3 4] [4], + subset? [#{1 2 3} #{1 2} #{1 3}] [#{1 2 3}], + subset? [#{2 3 4} #{1 2 3} #{1 2} #{1}] [#{2 3 4} #{1 2 3}])) diff --git a/src/test/clojure/conexp/fca/contexts_test.clj b/src/test/clojure/conexp/fca/contexts_test.clj index 3fa55e7f..853f1a9b 100644 --- a/src/test/clojure/conexp/fca/contexts_test.clj +++ b/src/test/clojure/conexp/fca/contexts_test.clj @@ -8,8 +8,9 @@ (ns conexp.fca.contexts-test (:use conexp.base - conexp.fca.contexts) - (:use clojure.test) + conexp.fca.contexts + conexp.fca.closure-systems + clojure.test) (:require [clojure.set :refer [difference union subset? intersection]] [clojure.math.numeric-tower :refer [gcd]])) diff --git a/src/test/clojure/conexp/fca/implications_test.clj b/src/test/clojure/conexp/fca/implications_test.clj index 3460e972..e83e9ee5 100644 --- a/src/test/clojure/conexp/fca/implications_test.clj +++ b/src/test/clojure/conexp/fca/implications_test.clj @@ -9,6 +9,7 @@ (ns conexp.fca.implications-test (:use clojure.test) (:use conexp.base + conexp.fca.closure-systems conexp.fca.contexts conexp.io.contexts conexp.math.algebra diff --git a/src/test/clojure/conexp/fca/simplicial_complexes_test.clj b/src/test/clojure/conexp/fca/simplicial_complexes_test.clj new file mode 100644 index 00000000..cc1b4ad0 --- /dev/null +++ b/src/test/clojure/conexp/fca/simplicial_complexes_test.clj @@ -0,0 +1,240 @@ +;; 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.simplicial-complexes-test + (:use clojure.test) + (:require [conexp.base :refer [with-testing-data]] + [conexp.fca.contexts :refer [attributes + incidence + make-context + make-context-from-matrix + objects + random-contexts + object-concept + attribute-concept]] + [conexp.fca.simplicial-complexes :refer :all] + [conexp.fca.lattices :refer [concept-lattice]] + [conexp.fca.ordinal-motifs :refer [generate-scale]] + [conexp.fca.smeasure :refer [make-smeasure-nc + smeasure?]] + [conexp.io.contexts :refer [read-context]]) + (:import conexp.fca.simplicial_complexes.FullSimplicialComplex)) + +(deftest test-FullSimplicialComplex-equals + (is (= (FullSimplicialComplex. #{} #{}) + (FullSimplicialComplex. #{} #{}))) + (is (= (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}) + (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}))) + (is (= (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}) + (FullSimplicialComplex. #{1 2 3} #{#{} #{3} #{2} #{1}}))) + (is (= (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}) + (FullSimplicialComplex. #{1 2 3} '(#{} #{3} #{2} #{1})))) + (is (= (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}) + (FullSimplicialComplex. #{1 2 3} [#{} #{3} #{2} #{1}]))) + (is (not= (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}) + (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3} #{1 2}}))) + (is (not= (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}) + (FullSimplicialComplex. #{1 2 3 4} #{#{} #{1} #{2} #{3}}))) + (is (not= (FullSimplicialComplex. #{} #{}) + (Object.)))) + +(deftest test-FullSimplicialComplex-hashCode + (let [simplicial-complex-1 (FullSimplicialComplex. #{1 2} #{#{} #{1} #{2} #{1 2}}) + simplicial-complex-2 (FullSimplicialComplex. #{1 2} #{#{} #{1} #{2} #{1 2}}) + simplicial-complex-3 (FullSimplicialComplex. #{1 2} #{#{} #{1} #{2}})] + (is (= (hash simplicial-complex-1) (hash simplicial-complex-1))) + (is (= (hash simplicial-complex-1) (hash simplicial-complex-2))) + (is (not= (hash simplicial-complex-1) (hash simplicial-complex-3))))) + +(deftest test-FullSimplicialComplex-toString + (is (= (str (FullSimplicialComplex. #{1} #{#{1}})) + "#{#{1}}")) + (is (= (str (FullSimplicialComplex. #{1} [#{1}])) + "#{#{1}}"))) + +(deftest test-make-full-simplicial-complex-nc + (is (= (make-full-simplicial-complex-nc #{1 2 3} [#{} #{1} #{2} #{3}]) + (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}))) + (is (= (make-full-simplicial-complex-nc [#{} #{1} #{2} #{3}]) + (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}))) + (is (= (make-full-simplicial-complex-nc [#{} #{1} #{2} #{3} #{1 2} #{1 3}]) + (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3} #{1 2} #{1 3}}))) + (is (thrown? IllegalArgumentException (make-full-simplicial-complex-nc 0)))) + +(deftest test-is-simplicial-complex + (is (is-simplicial-complex? + (make-full-simplicial-complex-nc #{#{} #{1} #{2} #{3} #{1 2} #{1 3}}))) + (is (is-simplicial-complex? + (make-full-simplicial-complex-nc #{1 2 3} [#{} #{1} #{2} #{3}]))) + (is (not (is-simplicial-complex? + (make-full-simplicial-complex-nc #{#{} #{2} #{3} #{1 2} #{1 3}})))) + (is (not (is-simplicial-complex? + (make-full-simplicial-complex-nc #{#{1} #{2} #{3} #{1 2} #{1 3}})))) + (is (is-simplicial-complex? + (make-full-simplicial-complex-nc #{1 2 3} #{#{} #{1} #{2} #{3} #{1 2} #{1 3}}))) + (is (is-simplicial-complex? + (make-full-simplicial-complex-nc #{1 2 3 4} #{#{} #{1} #{2} #{3} #{1 2} #{1 3}}))) + (is (not (is-simplicial-complex? + (make-full-simplicial-complex-nc #{1 2} #{#{} #{1} #{2} #{3} #{1 2} #{1 3}}))))) + +(deftest test-make-full-simplicial-complex + (is (= (make-full-simplicial-complex #{1 2 3} [#{} #{1} #{2} #{3}]) + (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}))) + (is (= (make-full-simplicial-complex [#{} #{1} #{2} #{3}]) + (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3}}))) + (is (= (make-full-simplicial-complex [#{} #{1} #{2} #{3} #{1 2} #{1 3}]) + (FullSimplicialComplex. #{1 2 3} #{#{} #{1} #{2} #{3} #{1 2} #{1 3}}))) + (is (thrown? IllegalArgumentException (make-full-simplicial-complex [#{} #{1} #{2} #{1 2} #{1 3}]))) + (is (thrown? IllegalArgumentException (make-full-simplicial-complex #{1 2} [#{} #{1} #{2} #{3} #{1 2} #{1 3}]))) + (is (thrown? IllegalArgumentException (make-full-simplicial-complex-nc 0)))) + +;; FCA + +(def ctx (make-context-from-matrix [0 1 2 3] ['a 'b 'c 'd] + [1 1 1 1 1 0 0 0 0 1 0 1 1 0 1 1])) +(def ctx2 (make-context-from-matrix [0 1 2 3 4] ['a 'b 'c 'd] + [1 1 1 1 1 0 0 0 0 1 0 1 1 0 1 1 1 1 0 0 ])) + +(deftest test-t-simplex-next-closure + (is (= (t-simplex-next-closure ctx [#{0 3} #{'a 'c 'd}]) + (FullSimplicialComplex. #{[#{0} #{'a 'b 'c 'd}] [#{0 2} #{'b 'd}] + [#{0 3} #{'a 'c 'd}] [#{0 2 3} #{'d}] + [#{0 1 3} #{'a}] [#{0 1 2 3} #{}]} + #{#{} #{[#{0} #{'a 'b 'c 'd}]} #{[#{0 2} #{'b 'd}]} + #{[#{0} #{'a 'b 'c 'd}] [#{0 2} #{'b 'd}]}}))) + (is (= (t-simplex-next-closure ctx [#{0 1 3} #{'a}]) + (FullSimplicialComplex. #{[#{0} #{'a 'b 'c 'd}] [#{0 2} #{'b 'd}] + [#{0 3} #{'a 'c 'd}] [#{0 2 3} #{'d}] + [#{0 1 3} #{'a}] [#{0 1 2 3} #{}]} + #{#{} #{[#{0} #{'a 'b 'c 'd}]} #{[#{0 2} #{'b 'd}]} + #{[#{0 3} #{'a 'c 'd}]} #{[#{0 2 3} #{'d}]} + #{[#{0} #{'a 'b 'c 'd}] [#{0 2} #{'b 'd}]} + #{[#{0} #{'a 'b 'c 'd}] [#{0 3} #{'a 'c 'd}]} + #{[#{0} #{'a 'b 'c 'd}] [#{0 2 3} #{'d}]} + #{[#{0 2} #{'b 'd}] [#{0 2 3} #{'d}]} + #{[#{0 3} #{'a 'c 'd}] [#{0 2 3} #{'d}]} + #{[#{0 2} #{'b 'd}] [#{0 3} #{'a 'c 'd}]} + #{[#{0} #{'a 'b 'c 'd}] [#{0 2} #{'b 'd}] [#{0 2 3} #{'d}]} + #{[#{0} #{'a 'b 'c 'd}] [#{0 3} #{'a 'c 'd}] [#{0 2 3} #{'d}]} + #{[#{0 2} #{'b 'd}] [#{0 2 3} #{'d}] [#{0 3} #{'a 'c 'd}]} + #{[#{0} #{'a 'b 'c 'd}] [#{0 2} #{'b 'd}] [#{0 3} #{'a 'c 'd}]} + #{[#{0} #{'a 'b 'c 'd}] [#{0 2} #{'b 'd}] [#{0 2 3} #{'d}] [#{0 3} #{'a 'c 'd}]}}))) + (is (thrown? IllegalArgumentException (t-simplex-next-closure [#{0 3} #{'a 'c 'd}] [#{0 3} #{'a 'c 'd}])))) + +(deftest test-ordinal-motif-sample-cases + (is (= (ordinal-motif-next-closure ctx :ordinal) + (FullSimplicialComplex. #{0 1 2 3} + #{#{} #{0} #{1} #{2} #{3} + #{0 1} #{0 2} #{0 3} #{1 3} #{0 1 3}}))) + (let [ctx1 (make-context-from-matrix [0 1 2 3] ['a 'b 'c 'd] + [1 1 0 0 1 0 1 0 0 1 0 1 1 1 0 1]) + ctx2 (make-context-from-matrix [0 1 2 3] [0 1 2 3] [1 1 1 1 0 1 1 1 0 0 1 1 0 0 0 1])] + (is (= (ordinal-motif-next-closure ctx1 :ordinal) + (FullSimplicialComplex. #{0 1 2 3} + #{#{} #{0} #{1} #{2} #{3} #{0 3} #{2 3}}))) + (is (= (ordinal-motif-next-closure ctx2 :ordinal) + (FullSimplicialComplex. #{0 1 2 3} + #{#{} #{0} #{1} #{2} #{3} + #{0 1} #{0 2} #{0 3} #{1 2} #{1 3} #{2 3} + #{0 1 2} #{0 1 3} #{0 2 3} #{1 2 3} #{0 1 2 3}})))) + (is (= (ordinal-motif-next-closure ctx :interordinal) + (FullSimplicialComplex. #{0 1 2 3} + #{#{} #{0} #{1} #{2} #{3} + #{1 2} #{2 3}}))) + ;; Test case similar to ordinal-motifs branch. + (let [ctx (make-context-from-matrix [0 1 2 3] + ['a 'b 'c 'd] + [1 1 0 0 1 0 1 0 0 1 0 1 1 1 0 1])] + (are [type simplices] + (= (ordinal-motif-next-closure ctx type) + (FullSimplicialComplex. #{0 1 2 3} + simplices)) + :ordinal #{#{} #{0} #{1} #{2} #{3} #{0 3} #{2 3}} + :interordinal #{#{} #{0} #{1} #{2} #{3} #{0 1} #{0 2} #{1 2} #{1 3} #{0 1 2}} + :nominal #{#{} #{0} #{1} #{2} #{3} #{1 3} #{1 2} #{0 2} #{0 1}} + :contranominal #{#{} #{0} #{1} #{2} #{3} #{0 1} #{0 2} #{1 2} #{1 3}}))) + +(defn- has-smeasure? + ;; Check if given context has a scale measure of given scale type. + [ctx scale-type] + (let [smeasures (map + #(make-smeasure-nc + ctx + (generate-scale scale-type %) + (zipmap (objects ctx) (range 1 (inc %)))) + (range (inc (count (objects ctx)))))] + (some #(smeasure? %) smeasures))) + +(deftest test-ordinal-motif-next-closure + "Test the ordinal-motif-next-closure method by testing if for all + subcontexts that contain simplices as objects there is a (local) + scale-measure to the given scale-type." + (let [contexts (random-contexts 10 10)] + (with-testing-data [ctx contexts, + scale-type [:nominal :contranominal :ordinal :interordinal]] + (let [ordinal-motifs (ordinal-motif-next-closure ctx scale-type) + subcontexts (map #(make-context % (attributes ctx) (incidence ctx)) + (simplices ordinal-motifs)) + smeasures (map + #(make-smeasure-nc + % + (generate-scale scale-type (count (objects %))) + (zipmap (objects %) (range 1 (inc (count (objects %)))))) + subcontexts)] + (every? #(has-smeasure? % scale-type) subcontexts)))) + (is (thrown? IllegalArgumentException (ordinal-motif-next-closure ctx :other)))) + +;;; Tests for simplicial complex analytics + +(deftest test-face-dimension + (let [face1 #{[#{0} #{'a 'b 'c 'd}]} + face2 #{[#{0} #{'a 'b 'c 'd}] [#{0 2} #{'b 'd}]}] + (is (= (face-dimension face1) 0)) + (is (= (face-dimension face2) 1)))) + +(deftest test-complex-dimension + (let [ctx (read-context "testing-data/bodiesofwater.cxt") + sc1 (t-simplex-next-closure ctx (object-concept ctx "puddle")) + sc2 (t-simplex-next-closure ctx (object-concept ctx "reservoir")) + sc3 (t-simplex-next-closure ctx (object-concept ctx "lagoon"))] + (is (= (complex-dimension sc1) 7)) + (is (= (complex-dimension sc2) 6)) + (is (= (complex-dimension sc3) 2)))) + +(deftest test-sc-matrix-rep + (let [sc1 (t-simplex-next-closure ctx (object-concept ctx 1)) + sc2 (t-simplex-next-closure ctx (object-concept ctx 2)) + sc3 (t-simplex-next-closure ctx (attribute-concept ctx 'd))] + (is (= (sc-matrix-rep sc1 1) + [[1 1 0 1 0 0] [1 0 1 0 1 0] [0 1 1 0 0 1] [0 0 0 1 1 1]])) + (is (= (sc-matrix-rep sc1 2) + [[1 1 0 0] [1 0 1 0] [1 0 0 1] [0 1 1 0] [0 1 0 1] [0 0 1 1]])) + (is (= (sc-matrix-rep sc1 3) + [[1] [1] [1] [1]])) + (is (= (sc-matrix-rep sc1 4) + [[]])) + (is (= (sc-matrix-rep sc2 1) + [[1 1 0] [1 0 1] [0 1 1]])) + (is (= (sc-matrix-rep sc2 2) + [[1] [1] [1]])) + (is (= (sc-matrix-rep sc2 0) + [[1 1 1]])) + (is (= (sc-matrix-rep sc3 2) + [[0] [1] [1] [1]])) + (is (= (sc-matrix-rep sc3 1) + [[1 0 0 0] [1 1 1 0] [0 1 0 1] [0 0 1 1]])))) + +(deftest test-sc-chain-complex + (let [chain [(object-concept ctx 3) (attribute-concept ctx 'd)] + sc1 (t-simplex-next-closure ctx (nth chain 0)) + sc2 (t-simplex-next-closure ctx (nth chain 1)) + chain2 [(object-concept ctx2 2) (attribute-concept ctx 'd)] ] + (is (= (sc-chain-complex (concept-lattice ctx) chain 2) + [[[#{0 3} #{'a 'c 'd}] [] [[]]] [[#{0 3 2} #{'d}] [[]] [[0] [1] [1] [1]]]])) + (is (= (sc-chain-complex (concept-lattice ctx2) chain2 2) + [[[#{0 2} #{'b 'd}] [[1] [1] [1] [1]] [[1 1 0 0] [1 0 1 0] [1 0 0 1] [0 1 1 0] [0 1 0 1] [0 0 1 1]]] [[#{0 3 2} #{'d}] [[0 1] [1 0] [1 0] [1 0] [1 0] [0 1] [0 1] [0 1]] [[1 0 0 0 0 1 0 0] [1 0 0 0 0 0 1 0] [1 1 1 0 0 0 0 1] [0 1 0 1 0 0 0 0] [0 1 0 0 1 0 0 0] [0 0 1 1 0 0 0 0] [0 0 1 0 1 0 0 0] [0 0 0 1 1 0 0 0] [0 0 0 0 0 1 1 0] [0 0 0 0 0 1 0 1] [0 0 0 0 0 0 1 1]]]]))))