diff --git a/CHANGELOG.md b/CHANGELOG.md index 28326d4..50d9abf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,7 @@ ## 1.0.1 -- 2025-05-01 * Changed constrains for dependencies in .cabal file to allow for more flexibility in package versions. + +## 1.0.2 -- 2025-03-28 +* Added deffuzzification function to the library, allowing users to convert fuzzy sets (Lset Double l) back into numbers. +* This update is prepared for the next major release, which will include fuzzy control systems, if than rules and more. \ No newline at end of file diff --git a/fuzzySets.cabal b/fuzzySets.cabal index 8fbcc08..d1d77b2 100644 --- a/fuzzySets.cabal +++ b/fuzzySets.cabal @@ -1,6 +1,6 @@ cabal-version: 2.4 name: fuzzySets -version: 1.0.1 +version: 1.0.2 category: Math license: BSD-3-Clause author: Lukas Balog @@ -17,7 +17,7 @@ description: This package provide functions for working with fuzzy sets and fuzzy relations. -extra-source-files: CHANGELOG.md +extra-doc-files: CHANGELOG.md source-repository head type: git @@ -33,6 +33,7 @@ library Fuzzy.Sets.MembershipFunctions Fuzzy.Sets.Cardinality Fuzzy.Sets.Properties + Fuzzy.Control.Defuzzification Fuzzy.Relations.LRelation Fuzzy.Relations.MembershipFunctions Fuzzy.Relations.RelationComposition @@ -77,3 +78,4 @@ test-suite test Fuzzy.Sets.PropertiesTest Fuzzy.Relations.LRelationTest Fuzzy.Relations.PropertiesTest + Fuzzy.Control.DefuzzificationTest diff --git a/src/Fuzzy/Control/Defuzzification.hs b/src/Fuzzy/Control/Defuzzification.hs new file mode 100644 index 0000000..7971d83 --- /dev/null +++ b/src/Fuzzy/Control/Defuzzification.hs @@ -0,0 +1,55 @@ +module Fuzzy.Control.Defuzzification where + +import Fuzzy.Sets.LSet (LSet, toList) +import Fuzzy.Sets.Cardinality +import Lattices.ResiduatedLattice (ResiduatedLattice) +import FuzzySet +import Data.List (maximumBy) +import Data.Ord (comparing) + +-- | Defuzzify a fuzzy set using the center of gravity (centroid) method. +-- +-- Returns 0 for an empty universe. +centerOfGravity :: ResiduatedLattice l => LSet Double l -> Double +centerOfGravity = centerOfGravityMod id + +-- | Defuzzify with a membership modifier function 'c'. +-- +-- The modifier is applied to each membership value before computing the weighted mean. +-- Returns 0 when the modified sigma count is 0. +centerOfGravityMod :: ResiduatedLattice l => (l -> l) -> LSet Double l -> Double +centerOfGravityMod c set = if card == 0 then 0 else numer / card + where + pairs = toList set + card = sigmaCountMod c set + numer = sum [x * realToFrac mem | (x, mem) <- pairs] + +-- | Defuzzify by taking the midpoint of the maximum interval. +-- +-- For a non-empty set returns (max + min) / 2 based on the universe support. +centerOfMaxima :: ResiduatedLattice l => LSet Double l -> Double +centerOfMaxima set = (sup + inf) / 2 + where + sup = maximum $ universe set + inf = minimum $ universe set + +-- | Mean of maxima with a modifier function. +-- +-- Uses 'sigmaCountMod' for total modified membership and normalizes by universe size. +-- Returns 0 for empty universes. +meanOfMaximaMod :: ResiduatedLattice l => (l -> l) -> LSet Double l -> Double +meanOfMaximaMod c set = if sizeUniverse == 0 then 0 else card / sizeUniverse + where + card = sigmaCountMod c set + sizeUniverse = fromIntegral $ universeCardinality set + +-- | Return the universe element with maximal membership degree. +-- +-- If multiple elements have the same maximal degree, the first encountered is returned. +-- For an empty set, returns 0. +maxMembership :: ResiduatedLattice l => LSet Double l -> Double +maxMembership set + | null pairs = 0 + | otherwise = fst $ maximumBy (comparing snd) pairs + where + pairs = toList set \ No newline at end of file diff --git a/src/Fuzzy/Sets/Cardinality.hs b/src/Fuzzy/Sets/Cardinality.hs index 15e6e58..15c7dce 100644 --- a/src/Fuzzy/Sets/Cardinality.hs +++ b/src/Fuzzy/Sets/Cardinality.hs @@ -2,7 +2,7 @@ module Fuzzy.Sets.Cardinality( sigmaCount, thresholdSigmaCount, normalizedSigmaCount, - sigmaCountWithModifier, + sigmaCountMod, -- * Modifier functions modifierFunction, sigmoidModifier, @@ -41,11 +41,11 @@ For a fuzzy set A, |A| = Σ c(A(u)) for all u ∈ U >>> let set = fromPairs [(1, 0.2), (2, 0.7), (3, 0.5)] :: LSet Int UILukasiewicz >>> let modifier = sigmoidModifier 2.0 0.5 ->>> sigmaCountWithModifier modifier set +>>> sigmaCountMod modifier set 1.4 -} -sigmaCountWithModifier :: (FuzzySet set a l) => (l -> l) -> set -> Double -sigmaCountWithModifier c set = sum [realToFrac $ (c . f) x | x <- universe set] +sigmaCountMod :: (FuzzySet set a l) => (l -> l) -> set -> Double +sigmaCountMod c set = sum [realToFrac $ (c . f) x | x <- universe set] where f = member set diff --git a/test/Fuzzy/Control/DefuzzificationTest.hs b/test/Fuzzy/Control/DefuzzificationTest.hs new file mode 100644 index 0000000..6c841d6 --- /dev/null +++ b/test/Fuzzy/Control/DefuzzificationTest.hs @@ -0,0 +1,38 @@ +module Fuzzy.Control.DefuzzificationTest ( + defuzzificationTests +) where + +import Test.Tasty +import Test.Tasty.HUnit +import Fuzzy.Control.Defuzzification +import Fuzzy.Sets.LSet +import Fuzzy.Sets.Cardinality +import Lattices.UnitIntervalStructures.Godel + +-- Test data +sampleSet :: LSet Double UIGodel +sampleSet = fromList [(1, 0.2), (2, 0.8)] + +emptySet :: LSet Double UIGodel +emptySet = mkEmptySet + +-- Test group +defuzzificationTests :: TestTree +defuzzificationTests = testGroup "Defuzzification Tests" [ + testCase "centerOfGravity on non-empty set" $ + assertEqual "centroid correct" 1.8 (centerOfGravity sampleSet), + testCase "centerOfGravity on empty set" $ + assertEqual "empty set yields 0" 0.0 (centerOfGravity emptySet), + testCase "centerOfGravityMod with alpha-cut" $ + assertEqual "modifier influences denominator only" 1.8 (centerOfGravityMod (alphaCutModifier 0.5) sampleSet), + testCase "centerOfMaxima uses universe bounds" $ + assertEqual "midpoint between min and max" 1.5 (centerOfMaxima sampleSet), + testCase "meanOfMaximaMod with identity modifier" $ + assertEqual "normalized sigma count" 0.5 (meanOfMaximaMod (\x -> x) sampleSet), + testCase "meanOfMaximaMod with alpha-cut modifier" $ + assertEqual "alpha-cut count over universe size" 0.5 (meanOfMaximaMod (alphaCutModifier 0.5) sampleSet), + testCase "maxMembership on non-empty set" $ + assertEqual "highest membership element" 2.0 (maxMembership sampleSet), + testCase "maxMembership on empty set" $ + assertEqual "empty set returns 0" 0.0 (maxMembership emptySet) + ] diff --git a/test/Tests.hs b/test/Tests.hs index 4255e89..94d85c7 100644 --- a/test/Tests.hs +++ b/test/Tests.hs @@ -10,6 +10,7 @@ import Fuzzy.Sets.PropertiesTest import Fuzzy.Sets.MembershipFunctionsTest import Fuzzy.Relations.LRelationTest import Fuzzy.Relations.PropertiesTest +import Fuzzy.Control.DefuzzificationTest -- Run all tests main :: IO () @@ -21,5 +22,6 @@ main = defaultMain $ testGroup "All Tests" [ membershipFunctionsTests, propertiesTests, lrelationTests, - relPropertiesTests + relPropertiesTests, + defuzzificationTests ] \ No newline at end of file