diff --git a/src/main/clojure/conexp/fca/fuzzy/fca.clj b/src/main/clojure/conexp/fca/fuzzy/fca.clj new file mode 100644 index 00000000..72c02c14 --- /dev/null +++ b/src/main/clojure/conexp/fca/fuzzy/fca.clj @@ -0,0 +1,154 @@ +;; 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.fuzzy.fca + "Basic definitions for Fuzzy FCA" + (:use conexp.base + conexp.fca.contexts + conexp.fca.many-valued-contexts + [conexp.fca.fuzzy sets])) + + +(deftype Fuzzy-Context [objects attributes incidence] + Object + (equals [this other] + (generic-equals [this other] Fuzzy-Context [objects attributes incidence])) + (hashCode [this] + (hash-combine-hash Fuzzy-Context objects attributes incidence)) + + conexp.fca.contexts/Context + (objects [this] objects) + (attributes [this] attributes) + (incidence [this] incidence)) + +(defn mv->fuzzy-context-nc + "Converts a many-valued-context to a fuzzy context, without checking." + [mv-ctx] + (Fuzzy-Context. (objects mv-ctx) (attributes mv-ctx) (make-fuzzy-set (incidence mv-ctx))) + ) + +(defmethod print-method Fuzzy-Context + [ctx out] + (.write ^java.io.Writer out + ^String (mv-context-to-string (make-mv-context (objects ctx) + (attributes ctx) + (fn [a b] ((incidence ctx) [a b]))))) + ) + +(defmulti make-fuzzy-context + "Creates a fuzzy context from the given attributes. A fuzzy context + is nothing else than a Many-Valued Context with real entries between + 0 and 1." + {:arglists '([objects attributes incidence])} + (fn [& args] + (vec (map clojure-type args)))) + +(defmethod make-fuzzy-context [clojure-coll clojure-coll clojure-fn] + [objects attributes truth-function] + (let [mv-ctx (make-mv-context (set objects) + (set attributes) + truth-function)] + (when-not (forall [v (vals (incidence mv-ctx))] (and (number? v) (<= 0 v 1))) + (illegal-argument "Given function does not return real values between 0 and 1.")) + (mv->fuzzy-context-nc mv-ctx))) + +(defmethod make-fuzzy-context [clojure-coll clojure-coll clojure-coll] + [objects attributes values] + (let [mv-ctx (make-mv-context-from-matrix objects attributes values)] + (when-not (forall [v (vals (incidence mv-ctx))] (and (number? v) (<= 0 v 1))) + (illegal-argument "Given value table does not contain of real values between 0 and 1.")) + (mv->fuzzy-context-nc mv-ctx))) + +(defn make-fuzzy-context-from-matrix + "Creates a fuzzy context from the given (number of) objects, (number + of) attributes and the value table, which must contain only real + values between 0 and 1." + [objects attributes values] + (make-fuzzy-context objects attributes values)) + +(defn- fuzzy-operators [norm] + "Returns fuzzy operations based on the supplied norm, that are required for more complex fuzzy operations." + (let [t-norm (first norm) + residuum (second norm) + f-and #(t-norm %1 (residuum %1 %2)) + f-or #(f-and (residuum (residuum %1 %2) %2) + (residuum (residuum %2 %1) %1)) + f-neg #(residuum % 0)] + [t-norm residuum f-and f-or f-neg]) + ) + +(defn fuzzy-object-derivation + "Accepts a fuzzy context and a fuzzy set of objects. + Computes the fuzzy object derivation of the supplied objects in the supplied fuzzy context." + ([fctx fobjs norm] + (fuzzy-object-derivation fctx fobjs norm identity)) + + ([fctx fobjs norm hedge] + (let [[t-norm residuum f-and f-or f-neg] (fuzzy-operators norm) + inz (incidence fctx)] + (make-fuzzy-set (into {} (for [attr (attributes fctx)] + [attr + (reduce f-and 1 (for [obj (keys fobjs)] (residuum (hedge (fobjs obj)) (inz [obj attr]))))]))))) + ) + +(defn fuzzy-attribute-derivation + "Accepts a fuzzy context and a fuzzy set of attributes. + Computes the fuzzy object derivation of the supplied attributes in the supplied fuzzy context." + ([fctx fattrs norm] + (fuzzy-attribute-derivation fctx fattrs norm identity)) + + ([fctx fattrs norm hedge] + (let [[t-norm residuum f-and f-or f-neg] (fuzzy-operators norm) + inz (incidence fctx)] + (make-fuzzy-set (into {} (for [obj (attributes fctx)] + [obj + (reduce f-and 1 (for [attr (keys fattrs)] (residuum (hedge (fattrs attr)) (inz [obj attr]))))]))))) + ) + +(defn globalization-hedge [x] + "Globalization hedge function." + (if (= x 1) 1 0) + ) + +(defn fuzzy-subset-degree + "Returns the degree to which fset1 is a subset of fset2. Applies hedge to the truth + value of an element being in fset1, if given." + ([fset1 fset2 norm] + (fuzzy-subset-degree fset1 fset2 norm identity)) + + ([fset1 fset2 norm hedge] + (let [[t-norm residuum f-and f-or f-neg] (fuzzy-operators norm)] + (reduce #(f-and %1 (residuum (hedge (fset1 %2)) + (fset2 %2))) + 1 + (keys fset1)))) + ) + +(defn validity + "Returns the degree to which the implication *fset1* ==> *fset2* is true in the + supplied fuzzy context. *fset1* and *fset2* are fuzzy subsets of the attributes of + the supplied fuzzy context." + ([fctx fset1 fset2 norm] + (validity fctx fset1 fset2 norm identity)) + ([fctx fset1 fset2 norm hedge] + (fuzzy-subset-degree (make-fuzzy-set fset2) + (fuzzy-object-derivation fctx + (fuzzy-attribute-derivation fctx (make-fuzzy-set fset1) norm) + norm + hedge) + norm))) + +;Pairs of t-norms and residuum +(def lukasiewicz-norm [#(max 0 (+ %1 %2 -1)) + #(min 1 (+ 1 (- %1) %2))]) + +(def goedel-norm [#(min %1 %2) + #(if (<= %1 %2) 1 %2)]) + +(def product-norm [#(* %1 %2) + #(if (<= %1 %2) 1 (/ %2 %1))]) diff --git a/src/main/clojure/conexp/fca/fuzzy/sets.clj b/src/main/clojure/conexp/fca/fuzzy/sets.clj new file mode 100644 index 00000000..bcb6e508 --- /dev/null +++ b/src/main/clojure/conexp/fca/fuzzy/sets.clj @@ -0,0 +1,160 @@ +;; 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.fuzzy.sets + "Basic definitions for fuzzy sets" + (:use conexp.base) + (:require [clojure.math.combinatorics :as comb] + [clojure.set :as set])) + + +(deftype Fuzzy-Set [^clojure.lang.APersistentMap hashmap] + + Object + (equals [this other] + (generic-equals [this other] Fuzzy-Set [hashmap])) + (hashCode [this] + (hash-combine-hash Fuzzy-Set hashmap)) + (toString [this] + (str hashmap)) + + clojure.lang.ISeq + (first [this] + (first hashmap)) + (next [this] + (next hashmap)) + (more [this] + (if-let [n (next hashmap)] + n + ())) + (cons [this [k v]] + (when-not (and (number? v) (<= 0 v 1)) + (illegal-argument "Fuzzy sets only support real values in [0,1]")) + (if (zero? v) + (Fuzzy-Set. (dissoc hashmap k)) + (Fuzzy-Set. (assoc hashmap k v)))) + (seq [this] + (seq hashmap)) + (count [this] + (count hashmap)) + (empty [this] + (Fuzzy-Set. {})) + (equiv [this other] + (.equals this other)) + + clojure.lang.IFn + (invoke [this thing] + (let [result (hashmap thing)] + (or result 0))) + (applyTo [this ^clojure.lang.ISeq seq] + (if (= 1 (count seq)) + (.applyTo hashmap seq) + (illegal-argument "Cannot apply fuzzy sets to non-singleton sequences."))) + + clojure.lang.Associative + (containsKey [this o] + true) + (entryAt [this o] + (.entryAt hashmap o)) + (assoc [this k v] + (.cons this [k v])) + + clojure.lang.ILookup + (valAt [this o] + (get hashmap o 0)) + (valAt [this o not-found] + (get hashmap o not-found))) + +(defmethod print-method Fuzzy-Set [set out] + (.write ^java.io.Writer out + ^String (str "#F" set))) + +(defmulti make-fuzzy-set + "Constructs a fuzzy set from a given collection." + clojure-type) + +(defmethod make-fuzzy-set :default + [thing] + (illegal-argument "Don't know how to create a fuzzy set from " thing ".")) + +(defmethod make-fuzzy-set clojure-map + [hashmap] + (assert (forall [v (vals hashmap)] + (and (number? v) (<= 0 v 1)))) + (Fuzzy-Set. (select-keys hashmap (remove #(zero? (hashmap %)) (keys hashmap))))) + +(defmethod make-fuzzy-set Fuzzy-Set + [fuzzy-set] + fuzzy-set) + +(defmethod make-fuzzy-set clojure-set + [set] + (Fuzzy-Set. (map-by-fn (constantly 1) set))) + +(defmethod make-fuzzy-set clojure-coll + [coll] + (make-fuzzy-set (set coll))) + +(defmethod make-fuzzy-set clojure-vec + [vec] + (make-fuzzy-set (set vec))) + +(defn fuzzy-set-to-hashmap [fuzzy-set] + "Returns a Hashmap Mapping each Element in the Fzuuy Set to its Membership Degree." + (.hashmap ^Fuzzy-Set fuzzy-set)) + +(defn fuzzy-set? [thing] + "Tests whether the argument is a fuzzy set." + (instance? Fuzzy-Set thing)) + +;Set Operations +(defn fuzzy-intersection [a b] + "Computes the intersection of fuzzy sets." + (let [a-entries (set (map first a)) + b-entries (set (map first b)) + entries (set/union a-entries b-entries)] + (make-fuzzy-set (into {} (for [x entries] [x (min (a x) (b x))])))) +) + +(defn fuzzy-union [a b] + "Computes the union of fuzzy sets." + (let [a-entries (set (map first a)) + b-entries (set (map first b)) + entries (set/union a-entries b-entries)] + (make-fuzzy-set (into {} (for [x entries] [x (max (a x) (b x))])))) +) + +(defn fuzzy-difference [a b] + "Computes the difference of fuzzy sets." + (let [a-entries (set (map first a)) + b-entries (set (map first b)) + entries (set/union a-entries b-entries)] + (make-fuzzy-set (into {} (for [x entries] [x (max 0 (- (a x) (b x)))])))) +) + +(defn fuzzy-subsets [mvalues fset] + "Receives a collection of membership degrees and a fuzzy set as arguments. + Returns all fuzzy subsets of the supplies fuzzy sets that are generated by + combining each element with each membership degree lesser or equal to its + original membership degree in the supplied fuzzy set." + (let [mvalues (sort mvalues), + fset (seq (make-fuzzy-set fset)), + max-values (vec (map second fset)), + crisp-base (map first fset)] + (map #(make-fuzzy-set (zipmap crisp-base %)) + (apply comb/cartesian-product + (for [i (range (count fset))] + (take-while #(<= % (nth max-values i)) mvalues))))) +) + +(defn fuzzy-subset? [fset1 fset2] + "Verifies whether *fset1* is a fuzzy subset of *fset2*." + (forall [k (keys (fuzzy-set-to-hashmap fset1))] + (<= (fset1 k) + (fset2 k))) +) diff --git a/src/test/clojure/conexp/fca/fuzzy/fca_test.clj b/src/test/clojure/conexp/fca/fuzzy/fca_test.clj new file mode 100644 index 00000000..c6e5b619 --- /dev/null +++ b/src/test/clojure/conexp/fca/fuzzy/fca_test.clj @@ -0,0 +1,85 @@ +;; 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.fuzzy.fca-test + (:use [conexp.base] + conexp.fca.contexts + conexp.fca.many-valued-contexts + conexp.fca.fuzzy.sets + conexp.fca.fuzzy.fca) + (:use clojure.test)) + +;;; +(def- mvctx (make-mv-context [1 2 3 4] + [1 2 3 4 5 6] + #{[1 1 1.0] + [1 2 1.0] + [1 3 0.0] + [1 4 1.0] + [1 5 1.0] + [1 6 0.2] + [2 1 1.0] + [2 2 0.4] + [2 3 0.3] + [2 4 0.8] + [2 5 0.5] + [2 6 1.0] + [3 1 0.2] + [3 2 0.9] + [3 3 0.7] + [3 4 0.5] + [3 5 1.0] + [3 6 0.6] + [4 1 1.0] + [4 2 1.0] + [4 3 0.8] + [4 4 1.0] + [4 5 1.0] + [4 6 0.5]})) + +(def- fctx (make-fuzzy-context [1 2 3 4] + [1 2 3 4 5 6] + [1.0 1.0 0.0 1.0 1.0 0.2, + 1.0 0.4 0.3 0.8 0.5 1.0, + 0.2 0.9 0.7 0.5 1.0 0.6, + 1.0 1.0 0.8 1.0 1.0 0.5])) + +(println fctx) + +(deftest test-mv-to-fuzzy + + (is (= (mv->fuzzy-context-nc mvctx) fctx)) +) + +(deftest test-fuzzy-derivation + (let [fset1 (make-fuzzy-set #{1 2}) + fset2 (make-fuzzy-set {3 0.6 4 0.4}) + fset3 (make-fuzzy-set [5 6])] + + (is (= (fuzzy-object-derivation fctx fset1 lukasiewicz-norm) + (make-fuzzy-set {1 1.0 4 0.8 6 0.19999999999999996 2 0.3999999999999999 5 0.5}))) + (is (= (fuzzy-object-derivation fctx fset2 goedel-norm) + (make-fuzzy-set {1 0.2 4 0.5 6 1 3 1 2 1 5 1}))) + (is (= (fuzzy-attribute-derivation fctx fset3 product-norm) + (make-fuzzy-set {1 0.2 4 0.5 3 0.6 2 0.5})))) +) + + +(deftest test-fuzzy-implications + (let [fset1 (make-fuzzy-set {1 1}) + fset2 (make-fuzzy-set {2 1}) + fset3 (make-fuzzy-set {1 1 2 1}) + fset4 (make-fuzzy-set {4 1}) + fset5 (make-fuzzy-set {1 0.2 2 0.9}) + fset6 (make-fuzzy-set {3 1})] + + (is (= (validity fctx fset1 fset2 product-norm) 0.4)) + (is (= (validity fctx fset3 fset4 product-norm) 1)) + (is (= (validity fctx fset5 fset6 product-norm) 0)) + ) + ) diff --git a/src/test/clojure/conexp/fca/fuzzy/sets_test.clj b/src/test/clojure/conexp/fca/fuzzy/sets_test.clj new file mode 100644 index 00000000..c51aa5f5 --- /dev/null +++ b/src/test/clojure/conexp/fca/fuzzy/sets_test.clj @@ -0,0 +1,53 @@ +;; 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.fuzzy.sets-test + (:use conexp.fca.fuzzy.sets) + (:use clojure.test)) + +(def fset1 (make-fuzzy-set {1 0.8 2 1.0 3 0.6 4 0.4})) +(def fset2 (make-fuzzy-set {2 0.6 4 0.4})) +(def fset3 (make-fuzzy-set {3 0.5 4 0.9 5 0.7})) + + +(deftest test-fuzzy-subsets + (is (= (set (fuzzy-subsets [0 1/2 1] (make-fuzzy-set {1 1/2 2 1 3 0}))) + (set (map make-fuzzy-set + (list {} {1 1/2} {2 1/2} {2 1/2, 1 1/2} {2 1} {2 1, 1 1/2}))))) + + (is (fuzzy-subset? (make-fuzzy-set {1 0.5 2 0.3}) (make-fuzzy-set {1 1.0 2 0.5}))) + (is (fuzzy-subset? (make-fuzzy-set {1 0.3 2 0.7}) (make-fuzzy-set {1 0.3 2 1.0}))) + + (is (not (fuzzy-subset? (make-fuzzy-set {1 0.5 2 0.3}) (make-fuzzy-set {1 1.0 2 0.2})))) + (is (not (fuzzy-subset? (make-fuzzy-set {1 0.3 2 0.7}) (make-fuzzy-set {1 0.5})))) + + ) + +(deftest fuzzy-set-operations + + (is (= (fuzzy-union fset1 fset2) + (make-fuzzy-set {1 0.8 2 1.0 3 0.6 4 0.4}))) + (is (= (fuzzy-union fset2 fset3) + (make-fuzzy-set {2 0.6 3 0.5 4 0.9 5 0.7}))) + (is (= (fuzzy-union fset1 fset3) + (make-fuzzy-set {1 0.8 2 1.0 3 0.6 4 0.9 5 0.7}))) + + (is (= (fuzzy-intersection fset1 fset2) + (make-fuzzy-set {2 0.6 4 0.4}))) + (is (= (fuzzy-intersection fset2 fset3) + (make-fuzzy-set {4 0.4}))) + (is (= (fuzzy-intersection fset1 fset3) + (make-fuzzy-set {3 0.5 4 0.4}))) + + (is (= (fuzzy-difference fset1 fset2) + (make-fuzzy-set {1 0.8 2 0.4 3 0.6}))) + (is (= (fuzzy-difference fset2 fset3) + (make-fuzzy-set {2 0.6}))) + (is (= (fuzzy-difference fset1 fset3) + (make-fuzzy-set {1 0.8 2 1.0 3 0.09999999999999998}))) +)