From 7d690c598595c94ad97cbb14146ced23f254bc03 Mon Sep 17 00:00:00 2001 From: Alexander Ignatov Date: Sat, 18 Dec 2021 01:46:36 +0200 Subject: [PATCH] Add alpha-cut --- Sources/FuzzyKit/ContinuousFuzzySet.swift | 6 +++- .../FuzzyKit/DiscreteMutableFuzzySet.swift | 16 +++++++++- Sources/FuzzyKit/FuzzySet.swift | 2 ++ Sources/FuzzyKit/IterableFuzzySet.swift | 11 +++++++ Sources/FuzzyKit/MembershipFunction.swift | 6 ++-- .../ContinuousFuzzySetTests.swift | 17 +++++++++- .../DiscreteMutableFuzzySetTests.swift | 31 +++++++++++++++++++ .../FuzzyKitTests/IterableFuzzySetTests.swift | 26 +++++++++++----- 8 files changed, 103 insertions(+), 12 deletions(-) diff --git a/Sources/FuzzyKit/ContinuousFuzzySet.swift b/Sources/FuzzyKit/ContinuousFuzzySet.swift index e0e5362..3e1120d 100644 --- a/Sources/FuzzyKit/ContinuousFuzzySet.swift +++ b/Sources/FuzzyKit/ContinuousFuzzySet.swift @@ -7,13 +7,17 @@ public struct ContinuousFuzzySet: FuzzySet { self.membershipFunction = membershipFunction } - public init(membershipFunction: @escaping (Universe) -> Grade) { + public init(membershipFunction: @escaping MembershipFunction.FunctionType) { self.membershipFunction = MembershipFunction(membershipFunction) } public func grade(forElement element: Universe) -> Grade { membershipFunction(element) } + + public func alphaCut(_ alpha: Grade) -> Self { + .init { min(membershipFunction($0), alpha) } + } } public extension ContinuousFuzzySet { diff --git a/Sources/FuzzyKit/DiscreteMutableFuzzySet.swift b/Sources/FuzzyKit/DiscreteMutableFuzzySet.swift index 357647c..a37100b 100644 --- a/Sources/FuzzyKit/DiscreteMutableFuzzySet.swift +++ b/Sources/FuzzyKit/DiscreteMutableFuzzySet.swift @@ -1,6 +1,6 @@ public struct DiscreteMutableFuzzySet: FuzzySet { - private var grades: [Universe: Grade] + public private(set) var grades: [Universe: Grade] public init(elementToGradeMap: [Universe: Grade] = [:]) { self.grades = elementToGradeMap @@ -24,6 +24,20 @@ public struct DiscreteMutableFuzzySet: FuzzySet { setGrade(newValue, forElement: element) } } + + public func alphaCut(_ alpha: Grade) -> Self { + var newSet = Self(elementToGradeMap: grades) + newSet.applyAlphaCut(alpha) + return newSet + } + + public mutating func applyAlphaCut(_ alpha: Grade) { + let newGradeTuples = grades.map { + ($0.key, min($0.value, alpha)) + } + let newMap = Dictionary(uniqueKeysWithValues: newGradeTuples) + grades = newMap + } } public extension DiscreteMutableFuzzySet { diff --git a/Sources/FuzzyKit/FuzzySet.swift b/Sources/FuzzyKit/FuzzySet.swift index e4f4d7c..40e6927 100644 --- a/Sources/FuzzyKit/FuzzySet.swift +++ b/Sources/FuzzyKit/FuzzySet.swift @@ -7,6 +7,8 @@ public protocol FuzzySet { func grade(forElement element: Universe) -> Grade subscript(_ element: Universe) -> Grade { get } + + func alphaCut(_ alpha: Grade) -> Self } public extension FuzzySet { diff --git a/Sources/FuzzyKit/IterableFuzzySet.swift b/Sources/FuzzyKit/IterableFuzzySet.swift index 89f0157..89b3795 100644 --- a/Sources/FuzzyKit/IterableFuzzySet.swift +++ b/Sources/FuzzyKit/IterableFuzzySet.swift @@ -19,6 +19,17 @@ public struct IterableFuzzySet { self.range = range self.function = membershipFunction } + + public init(range: StrideThrough, membershipFunction: @escaping MembershipFunction.FunctionType) { + self.range = range + self.function = .init(membershipFunction) + } + + public func alphaCut(_ alpha: Grade) -> Self { + .init(range: range) { + Swift.min(function($0), alpha) + } + } } extension IterableFuzzySet: FuzzySet { diff --git a/Sources/FuzzyKit/MembershipFunction.swift b/Sources/FuzzyKit/MembershipFunction.swift index 01a2783..f784bb9 100644 --- a/Sources/FuzzyKit/MembershipFunction.swift +++ b/Sources/FuzzyKit/MembershipFunction.swift @@ -1,8 +1,10 @@ public struct MembershipFunction { - private let function: (U) -> Grade + public typealias FunctionType = (U) -> Grade - public init(_ function: @escaping (U) -> Grade) { + private let function: FunctionType + + public init(_ function: @escaping FunctionType) { self.function = function } diff --git a/Tests/FuzzyKitTests/ContinuousFuzzySetTests.swift b/Tests/FuzzyKitTests/ContinuousFuzzySetTests.swift index bf375e0..d1bbb9b 100644 --- a/Tests/FuzzyKitTests/ContinuousFuzzySetTests.swift +++ b/Tests/FuzzyKitTests/ContinuousFuzzySetTests.swift @@ -2,5 +2,20 @@ import XCTest import FuzzyKit final class ContinuousFuzzySetTests: XCTestCase { - // TODO + func test_triangular_alphaCut() { + let a = 3.0 + let b = 5.0 + let c = 8.0 + let alpha = 0.5 + let set = ContinuousFuzzySet(membershipFunction: .triangular(minimum: a, peak: b, maximum: c)) + + let sut = set.alphaCut(alpha) + let minResult = sut[a] + let peakResult = sut[b] + let maxResult = sut[c] + + XCTAssertEqual(0, minResult) + XCTAssertEqual(alpha, peakResult) + XCTAssertEqual(0, maxResult) + } } diff --git a/Tests/FuzzyKitTests/DiscreteMutableFuzzySetTests.swift b/Tests/FuzzyKitTests/DiscreteMutableFuzzySetTests.swift index 19c19cc..f9e9a0e 100644 --- a/Tests/FuzzyKitTests/DiscreteMutableFuzzySetTests.swift +++ b/Tests/FuzzyKitTests/DiscreteMutableFuzzySetTests.swift @@ -80,4 +80,35 @@ final class DiscreteMutableFuzzySetTests: XCTestCase { assertExpectedGrade(element: element, expectedGrade: grade, sut: fs2) } } + + func test_alphaCut_gradesAreCorrect() { + let alpha = 0.5 + let initial = [ + "a": 1.0, + "b": 0.88, + "c": 0.69, + "d": 0.42, + "e": 0.001, + "f": 0.0, + ] + let expected = [ + "a": 0.5, + "b": 0.5, + "c": 0.5, + "d": 0.42, + "e": 0.001, + "f": 0.0, + ] + + let set = DiscreteMutableFuzzySet(elementToGradeMap: initial) + var sut2 = DiscreteMutableFuzzySet(elementToGradeMap: initial) + + let sut1 = set.alphaCut(alpha) + sut2.applyAlphaCut(alpha) + + for (element, grade) in expected { + assertExpectedGrade(element: element, expectedGrade: grade, sut: sut1) + assertExpectedGrade(element: element, expectedGrade: grade, sut: sut2) + } + } } diff --git a/Tests/FuzzyKitTests/IterableFuzzySetTests.swift b/Tests/FuzzyKitTests/IterableFuzzySetTests.swift index cadf0be..3564017 100644 --- a/Tests/FuzzyKitTests/IterableFuzzySetTests.swift +++ b/Tests/FuzzyKitTests/IterableFuzzySetTests.swift @@ -18,20 +18,18 @@ class IterableFuzzySetTests: XCTestCase { func test_initFromIntRangeAndDiscreteFunction() { let expected = near4Support4 + let range = stride(from: 0, through: 10, by: 1) - let sut = IterableFuzzySet( - range: stride(from: 0, through: 10, by: 1), - membershipFunction: .init { - expected[$0].grade - } - ) + let sut = IterableFuzzySet(range: range) { + expected[$0].grade + } let result = Array(sut) XCTAssertEqual(result, expected) } - func test_initFromIntRangeAndContinuousFunction() { + func test_initFromDoubleRangeAndContinuousFunction() { let expected = near4Support4.map { IterableFuzzySet.Element(element: Double($0.element), grade: $0.grade) } @@ -45,4 +43,18 @@ class IterableFuzzySetTests: XCTestCase { XCTAssertEqual(result, expected) } + + func test_alphaCut_allValuesAreBelowAlpha() { + let alpha = 0.5 + let set = IterableFuzzySet( + range: stride(from: 0.0, through: 100.0, by: 0.5), + membershipFunction: .triangular(minimum: 42.0, peak: 69.0, maximum: 88.88) + ) + + let sut = set.alphaCut(alpha) + + let grades = Array(sut).map { $0.grade } + let allAreBelowAlpha = grades.allSatisfy { $0 <= alpha } + XCTAssertTrue(allAreBelowAlpha) + } }