From 968df3b5de9152f61ac25d14e3e0128783dbe79a Mon Sep 17 00:00:00 2001 From: Alexander Ignatov Date: Tue, 28 Dec 2021 00:07:53 +0200 Subject: [PATCH] Add basic fuzzy realtions --- Package.swift | 14 +++ .../BinaryCartesianProduct.swift | 39 +++++++++ .../FuzzyRelations/BinaryFuzzyRelation.swift | 30 +++++++ .../HomogenousFuzzyRelation.swift | 30 +++++++ .../FuzzyRelations/TernaryFuzzyRelation.swift | 30 +++++++ .../FuzzyRelationsTests.swift | 86 +++++++++++++++++++ .../FuzzyRelationsTests/Helpers/Helpers.swift | 17 ++++ 7 files changed, 246 insertions(+) create mode 100644 Sources/FuzzyRelations/BinaryCartesianProduct.swift create mode 100644 Sources/FuzzyRelations/BinaryFuzzyRelation.swift create mode 100644 Sources/FuzzyRelations/HomogenousFuzzyRelation.swift create mode 100644 Sources/FuzzyRelations/TernaryFuzzyRelation.swift create mode 100644 Tests/FuzzyRelationsTests/FuzzyRelationsTests.swift create mode 100644 Tests/FuzzyRelationsTests/Helpers/Helpers.swift diff --git a/Package.swift b/Package.swift index b60d3f1..91b712d 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,7 @@ enum SubmoduleName: String { case fuzzyKit = "FuzzyKit" case fuzzySets = "FuzzySets" case fuzzyNumbers = "FuzzyNumbers" + case fuzzyRelations = "FuzzyRelations" var name: String { rawValue } var target: String { rawValue } @@ -64,5 +65,18 @@ let package = Package( .target(name: SubmoduleName.fuzzyNumbers.target), ] ), + .target( + name: SubmoduleName.fuzzyRelations.target, + dependencies: [ + .target(name: SubmoduleName.fuzzySets.target), + ] + ), + .testTarget( + name: SubmoduleName.fuzzyRelations.testTarget, + dependencies: [ + .target(name: SubmoduleName.fuzzyRelations.target), + .target(name: SubmoduleName.fuzzySets.target), + ] + ), ] ) diff --git a/Sources/FuzzyRelations/BinaryCartesianProduct.swift b/Sources/FuzzyRelations/BinaryCartesianProduct.swift new file mode 100644 index 0000000..88d2df1 --- /dev/null +++ b/Sources/FuzzyRelations/BinaryCartesianProduct.swift @@ -0,0 +1,39 @@ +import FuzzySets + +public class BinaryCartesianProduct +where A.Universe == U, B.Universe == V { + + let first: A + let second: B + let function: MembershipFunction<(U, V)> + + public init(_ a: A, _ b: B) { + self.first = a + self.second = b + self.function = .init { min(a[$0.0], b[$0.1]) } + } + + public init(_ a: A, _ b: B, membershipFunction: MembershipFunction<(U, V)>) { + self.first = a + self.second = b + self.function = membershipFunction + } + + public init(_ a: A, _ b: B, membershipFunction: @escaping MembershipFunction<(U, V)>.FunctionType) { + self.first = a + self.second = b + self.function = .init(membershipFunction) + } + + public func grade(forElements elements: (U, V)) -> Grade { + function(elements) + } + + public subscript(_ u: U, _ v: V) -> Grade { + grade(forElements: (u, v)) + } + + public func asFuzzySet() -> AnyFuzzySet<(U, V)> { + .init(membershipFunction: function) + } +} diff --git a/Sources/FuzzyRelations/BinaryFuzzyRelation.swift b/Sources/FuzzyRelations/BinaryFuzzyRelation.swift new file mode 100644 index 0000000..b15c602 --- /dev/null +++ b/Sources/FuzzyRelations/BinaryFuzzyRelation.swift @@ -0,0 +1,30 @@ +import FuzzySets + +public class BinaryFuzzyRelation { + + let function: MembershipFunction<(U, V)> + + public init(_ membershipFunction: MembershipFunction<(U, V)>) { + self.function = membershipFunction + } + + public init(_ membershipFunction: @escaping MembershipFunction<(U, V)>.FunctionType) { + self.function = .init(membershipFunction) + } + + public func grade(forElements elements: (U, V)) -> Grade { + function(elements) + } + + public subscript(_ u: U, _ v: V) -> Grade { + grade(forElements: (u, v)) + } + + public subscript(_ x: (U, V)) -> Grade { + grade(forElements: x) + } + + public func asFuzzySet() -> AnyFuzzySet<(U, V)> { + .init(membershipFunction: function) + } +} diff --git a/Sources/FuzzyRelations/HomogenousFuzzyRelation.swift b/Sources/FuzzyRelations/HomogenousFuzzyRelation.swift new file mode 100644 index 0000000..69a5c30 --- /dev/null +++ b/Sources/FuzzyRelations/HomogenousFuzzyRelation.swift @@ -0,0 +1,30 @@ +import FuzzySets + +class HomogenousFuzzyRelation { + + let function: MembershipFunction<[U]> + + public init(_ membershipFunction: MembershipFunction<[U]>) { + self.function = membershipFunction + } + + public init(_ membershipFunction: @escaping MembershipFunction<[U]>.FunctionType) { + self.function = .init(membershipFunction) + } + + public func grade(forElements elements: [U]) -> Grade { + function(elements) + } + + public subscript(_ u: U...) -> Grade { + grade(forElements: u) + } + + public subscript(_ u: [U]) -> Grade { + grade(forElements: u) + } + + public func asFuzzySet() -> AnyFuzzySet<[U]> { + .init(membershipFunction: function) + } +} diff --git a/Sources/FuzzyRelations/TernaryFuzzyRelation.swift b/Sources/FuzzyRelations/TernaryFuzzyRelation.swift new file mode 100644 index 0000000..fe5ca24 --- /dev/null +++ b/Sources/FuzzyRelations/TernaryFuzzyRelation.swift @@ -0,0 +1,30 @@ +import FuzzySets + +public class TernaryFuzzyRelation { + + let function: MembershipFunction<(U, V, W)> + + public init(_ membershipFunction: MembershipFunction<(U, V, W)>) { + self.function = membershipFunction + } + + public init(_ membershipFunction: @escaping MembershipFunction<(U, V, W)>.FunctionType) { + self.function = .init(membershipFunction) + } + + public func grade(forElements elements: (U, V, W)) -> Grade { + function(elements) + } + + public subscript(_ u: U, _ v: V, _ w: W) -> Grade { + grade(forElements: (u, v, w)) + } + + public subscript(_ x: (U, V, W)) -> Grade { + grade(forElements: x) + } + + public func asFuzzySet() -> AnyFuzzySet<(U, V, W)> { + .init(membershipFunction: function) + } +} diff --git a/Tests/FuzzyRelationsTests/FuzzyRelationsTests.swift b/Tests/FuzzyRelationsTests/FuzzyRelationsTests.swift new file mode 100644 index 0000000..64f560f --- /dev/null +++ b/Tests/FuzzyRelationsTests/FuzzyRelationsTests.swift @@ -0,0 +1,86 @@ +import XCTest +import FuzzyRelations +import FuzzySets + +final class FuzzyRelationsTests: XCTestCase { + func test_cartesianProduct_discreteSets() { + let fs1 = DiscreteMutableFuzzySet(elementToGradeMap: [ + "a1": 1, + "a2": 0.6, + "a3": 0.3, + ]) + let fs2 = DiscreteMutableFuzzySet(elementToGradeMap: [ + "b1": 0.6, + "b2": 0.9, + "b3": 0.1, + ]) + let expectedTuples = [ + ("a1", "b1"), + ("a1", "b2"), + ("a1", "b3"), + ("a2", "b1"), + ("a2", "b2"), + ("a2", "b3"), + ("a3", "b1"), + ("a3", "b2"), + ("a3", "b3"), + ] + let expectedGrades = [ + 0.6, + 0.9, + 0.1, + 0.6, + 0.6, + 0.1, + 0.3, + 0.3, + 0.1, + ] + + let sut = BinaryCartesianProduct(fs1, fs2) + + for (elements, grade) in zip(expectedTuples, expectedGrades) { + XCTAssertApproximatelyEqual(grade, sut[elements.0, elements.1]) + XCTAssertApproximatelyEqual(grade, sut.grade(forElements: elements)) + } + } + + func test_customRelation_discreteSets() { + let sut = BinaryFuzzyRelation { (a: Int, b: Int) -> Grade in + switch abs(a - b) { + case 0: return 1 + case 1: return 0.8 + case 2: return 0.3 + default: return 0 + } + } + + let expectedTuples = [ + (1, 1), + (1, 2), + (1, 3), + (2, 1), + (2, 2), + (2, 3), + (3, 1), + (3, 2), + (3, 3), + ] + let expectedGrades = [ + 1, + 0.8, + 0.3, + 0.8, + 1.0, + 0.8, + 0.3, + 0.8, + 1.00, + ] + + for (elements, grade) in zip(expectedTuples, expectedGrades) { + XCTAssertApproximatelyEqual(grade, sut[elements.0, elements.1]) + XCTAssertApproximatelyEqual(grade, sut.grade(forElements: elements)) + } + } +} diff --git a/Tests/FuzzyRelationsTests/Helpers/Helpers.swift b/Tests/FuzzyRelationsTests/Helpers/Helpers.swift new file mode 100644 index 0000000..e566ce1 --- /dev/null +++ b/Tests/FuzzyRelationsTests/Helpers/Helpers.swift @@ -0,0 +1,17 @@ +import XCTest +import FuzzySets + +public func XCTAssertApproximatelyEqual(_ v1: Double, _ v2: Double, tolerance: Double = 0.0001) { + let diff = v1 - v2 + XCTAssert(-tolerance <= diff && diff <= +tolerance) +} + +public func assertExpectedGrade(element: E, expectedGrade: Grade, sut: S) +where S.Universe == E { + let grade1 = sut[element] + let grade2 = sut.grade(forElement: element) + + XCTAssertApproximatelyEqual(grade1, grade2) + XCTAssertApproximatelyEqual(expectedGrade, grade1) + XCTAssertApproximatelyEqual(expectedGrade, grade2) +}