generated from johnHostetter/pypi-package
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d753ee7
commit 8570491
Showing
39 changed files
with
4,128 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
""" | ||
Demo of working with discrete fuzzy sets for a toy task regarding age. | ||
""" | ||
|
||
from sympy import Symbol, Interval, oo # oo is infinity | ||
|
||
from fuzzy.relations.discrete.snorm import StandardUnion | ||
from fuzzy.relations.discrete.tnorm import StandardIntersection | ||
from fuzzy.relations.discrete.complement import standard_complement | ||
from fuzzy.sets.discrete import DiscreteFuzzySet, FuzzyVariable | ||
|
||
|
||
def a_1(): | ||
""" | ||
A sample construction of a Fuzzy Set called 'A1'. | ||
""" | ||
formulas = [] | ||
element = Symbol("x") | ||
formulas.append((1, Interval.Lopen(-oo, 20))) | ||
formulas.append(((35 - element) / 15, Interval.open(20, 35))) | ||
formulas.append((0, Interval.Ropen(35, oo))) | ||
return DiscreteFuzzySet(formulas, "A1") | ||
|
||
|
||
def a_2(): | ||
""" | ||
A sample construction of a Fuzzy Set called 'A2'. | ||
""" | ||
formulas = [] | ||
element = Symbol("x") | ||
formulas.append((0, Interval.Lopen(-oo, 20))) | ||
formulas.append(((element - 20) / 15, Interval.open(20, 35))) | ||
formulas.append((1, Interval(35, 45))) | ||
formulas.append(((60 - element) / 15, Interval.open(45, 60))) | ||
formulas.append((0, Interval.Ropen(60, oo))) | ||
return DiscreteFuzzySet(formulas, "A2") | ||
|
||
|
||
def a_3(): | ||
""" | ||
A sample construction of a Fuzzy Set called 'A3'. | ||
""" | ||
formulas = [] | ||
element = Symbol("x") | ||
formulas.append((0, Interval.Lopen(-oo, 45))) | ||
formulas.append(((element - 45) / 15, Interval.open(45, 60))) | ||
formulas.append((1, Interval.Ropen(60, oo))) | ||
return DiscreteFuzzySet(formulas, "A3") | ||
|
||
|
||
a1 = a_1() | ||
a2 = a_2() | ||
a3 = a_3() | ||
|
||
FuzzyVariable([a1, a2, a3], "Age").plot(0, 80) | ||
b = StandardIntersection([a1, a2], "B") | ||
b.plot(0, 80) | ||
c = StandardIntersection([a2, a3], "C") | ||
c.plot(0, 80) | ||
StandardUnion([b, c], "B Union C").plot(0, 80) | ||
standard_complement(a1) | ||
a1.plot(0, 80) | ||
standard_complement(a3) | ||
a3.plot(0, 80) | ||
StandardIntersection([a1, a3], "Not(A1) Intersection Not(A3)").plot(0, 80) | ||
standard_complement(a1) | ||
standard_complement(a3) | ||
# doesn't work yet | ||
# StandardComplement(StandardUnion([b, c], 'Not (B Union C)')).graph(0, 80) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
""" | ||
Demo of working with discrete fuzzy sets for a toy task regarding knowledge of material. | ||
""" | ||
|
||
from sympy import Symbol, Interval, oo # oo is infinity | ||
|
||
from fuzzy.relations.discrete.tnorm import StandardIntersection | ||
from fuzzy.relations.discrete.extension import AlphaCut, SpecialFuzzySet | ||
from fuzzy.sets.discrete import DiscreteFuzzySet, FuzzyVariable | ||
|
||
|
||
# https://www-sciencedirect-com.prox.lib.ncsu.edu/science/article/pii/S0957417412008056 | ||
|
||
|
||
def unknown(): | ||
""" | ||
Create a fuzzy set for the linguistic term 'unknown'. | ||
Returns: | ||
OrdinaryDiscreteFuzzySet | ||
""" | ||
formulas = [] | ||
element = Symbol("x") | ||
formulas.append((1, Interval.Lopen(-oo, 55))) | ||
formulas.append((1 - (element - 55) / 5, Interval.open(55, 60))) | ||
formulas.append((0, Interval.Ropen(60, oo))) | ||
return DiscreteFuzzySet(formulas, "Unknown") | ||
|
||
|
||
def known(): | ||
""" | ||
Create a fuzzy set for the linguistic term 'known'. | ||
Returns: | ||
OrdinaryDiscreteFuzzySet | ||
""" | ||
formulas = [] | ||
element = Symbol("x") | ||
formulas.append(((element - 70) / 5, Interval.open(70, 75))) | ||
formulas.append((1, Interval(75, 85))) | ||
formulas.append((1 - (element - 85) / 5, Interval.open(85, 90))) | ||
formulas.append((0, Interval.Lopen(-oo, 70))) | ||
formulas.append((0, Interval.Ropen(90, oo))) | ||
return DiscreteFuzzySet(formulas, "Known") | ||
|
||
|
||
def unsatisfactory_unknown(): | ||
""" | ||
Create a fuzzy set for the linguistic term 'unsatisfactory unknown'. | ||
Returns: | ||
OrdinaryDiscreteFuzzySet | ||
""" | ||
formulas = [] | ||
element = Symbol("x") | ||
formulas.append(((element - 55) / 5, Interval.open(55, 60))) | ||
formulas.append((1, Interval(60, 70))) | ||
formulas.append((1 - (element - 70) / 5, Interval.open(70, 75))) | ||
formulas.append((0, Interval.Lopen(-oo, 55))) | ||
formulas.append((0, Interval.Ropen(75, oo))) | ||
return DiscreteFuzzySet(formulas, "Unsatisfactory Unknown") | ||
|
||
|
||
def learned(): | ||
""" | ||
Create a fuzzy set for the linguistic term 'learned'. | ||
Returns: | ||
OrdinaryDiscreteFuzzySet | ||
""" | ||
formulas = [] | ||
element = Symbol("x") | ||
formulas.append(((element - 85) / 5, Interval.open(85, 90))) | ||
formulas.append((1, Interval(90, 100))) | ||
formulas.append((0, Interval.Lopen(-oo, 85))) | ||
return DiscreteFuzzySet(formulas, "Learned") | ||
|
||
|
||
if __name__ == "__main__": | ||
terms = [unknown(), known(), unsatisfactory_unknown(), learned()] | ||
|
||
fuzzy_variable = FuzzyVariable(fuzzy_sets=terms, name="Student Knowledge") | ||
fig, _ = fuzzy_variable.plot(samples=150) | ||
fig.show() | ||
|
||
# --- DEMO --- Classify 'element' | ||
|
||
example_element_membership = fuzzy_variable.degree(element=73) | ||
|
||
alpha_cut = AlphaCut(known(), 0.6, "AlphaCut") | ||
fig, _ = alpha_cut.plot(samples=250) | ||
fig.show() | ||
special_fuzzy_set = SpecialFuzzySet(known(), 0.5, "Special") | ||
fig, _ = special_fuzzy_set.plot() | ||
fig.show() | ||
|
||
alpha_cuts = [] | ||
idx, MAX_HEIGHT, IDX_OF_MAX = 0, 0, 0 | ||
for idx, membership_to_fuzzy_term in enumerate(example_element_membership): | ||
if example_element_membership[idx] > 0: | ||
special_fuzzy_set = SpecialFuzzySet( | ||
terms[idx], membership_to_fuzzy_term, terms[idx].name | ||
) | ||
alpha_cuts.append( | ||
StandardIntersection( | ||
[special_fuzzy_set, terms[idx]], name=f"A{idx + 1}" | ||
) | ||
) | ||
|
||
# maximum membership principle | ||
if membership_to_fuzzy_term > MAX_HEIGHT: | ||
MAX_HEIGHT, IDX_OF_MAX = membership_to_fuzzy_term, idx | ||
|
||
confluence: FuzzyVariable = FuzzyVariable(fuzzy_sets=alpha_cuts, name="Confluence") | ||
fig, _ = confluence.plot() | ||
fig.show() | ||
|
||
# maximum membership principle | ||
print(f"Maximum Membership Principle: {terms[idx].name}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
[build-system] | ||
requires = ["hatchling"] | ||
build-backend = "hatchling.build" | ||
|
||
[project] | ||
name = "fuzzy-theory" | ||
version = "0.0.1" | ||
authors = [ | ||
{ name="John Wesley Hostetter", email="[email protected]" }, | ||
] | ||
description = "The fuzzy-theory library provides a PyTorch interface to fuzzy set theory and fuzzy logic operations." | ||
readme = "README.md" | ||
requires-python = ">=3.9" | ||
classifiers = [ | ||
"Programming Language :: Python :: 3", | ||
"License :: OSI Approved :: MIT License", | ||
"Operating System :: OS Independent", | ||
] | ||
|
||
[project.urls] | ||
Homepage = "https://github.com/johnHostetter/fuzzy-theory" | ||
Issues = "https://github.com/johnHostetter/fuzzy-theory/issues" | ||
|
||
[tool.hatch.build] | ||
include = [ | ||
"src/fuzzy/**", | ||
"README.md", | ||
"LICENSE", | ||
] | ||
exclude = [ | ||
"tests/**", | ||
"*.pyc", | ||
"*.pyo", | ||
".git/**", | ||
"build/**", | ||
"dist/**", | ||
".venv/**", | ||
] | ||
# Ignore VCS | ||
ignore = ["*.git", "*.hg", ".git/**", ".hg/**"] | ||
|
||
[tool.hatch.build.targets.wheel] | ||
packages = ["src/fuzzy"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
numpy==1.26.4 # new is 2.0.1 (update when possible - tests fail otherwise) | ||
sympy==1.13.2 # for torch | ||
torch==2.4.0 | ||
torchquad==0.4.0 | ||
matplotlib==3.9.1 # for torchquad | ||
SciencePlots==2.1.1 # for scientific publication plots | ||
natsort==8.4.0 # for natural sorting |
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
""" | ||
Implements aggregation operators in fuzzy theory. | ||
""" | ||
|
||
import torch | ||
|
||
|
||
class OrderedWeightedAveraging(torch.nn.Module): | ||
""" | ||
Yager's On Ordered Weighted Averaging Aggregation Operators in | ||
Multicriteria Decisionmaking (1988) | ||
An operator that lies between the 'anding' or the 'oring' of multiple criteria. | ||
The weight vector allows us to easily adjust the degree of 'anding' and 'oring' | ||
implicit in the aggregation. | ||
""" | ||
|
||
def __init__(self, in_features, weights): | ||
super().__init__() | ||
self.in_features = in_features | ||
if self.in_features != len(weights): | ||
raise AttributeError( | ||
"The number of input features expected in the Ordered Weighted Averaging operator " | ||
"is expected to equal the number of elements in the weight vector." | ||
) | ||
with torch.no_grad(): | ||
if weights.sum() == 1.0: | ||
self.weights = torch.nn.parameter.Parameter(torch.abs(weights)) | ||
else: | ||
raise AttributeError( | ||
"The weight vector of the Ordered Weighted Averaging operator must sum to 1.0." | ||
) | ||
|
||
def orness(self): | ||
""" | ||
A degree of 1 means the OWA operator is the 'or' operator, | ||
and this occurs when the first element of the weight vector is equal to 1 | ||
and all other elements in the weight vector are zero. | ||
Returns: | ||
The degree to which the Ordered Weighted Averaging operator is an 'or' operator. | ||
""" | ||
return (1 / (self.in_features - 1)) * torch.tensor( | ||
[ | ||
(self.in_features - i) * self.weights[i - 1] | ||
for i in range(1, self.in_features + 1) | ||
] | ||
).sum() | ||
|
||
def dispersion(self): | ||
""" | ||
The measure of dispersion; essentially, it is a measure of entropy that is related to the | ||
Shannon information concept. The more disperse the weight vector, the more information | ||
is being used in the aggregation of the aggregate value. | ||
Returns: | ||
The amount of dispersion in the weight vector. | ||
""" | ||
# there is exactly one entry where it is equal to one | ||
if len(torch.where(self.weights == 1.0)[0]) == 1: | ||
return torch.zeros(1) | ||
return -1 * (self.weights * torch.log(self.weights)).sum() | ||
|
||
def forward(self, input_observation): | ||
""" | ||
Applies the Ordered Weighted Averaging operator. First, it will sort the argument | ||
in descending order, then multiply by the weight vector, and finally sum over the entries. | ||
Args: | ||
input_observation: Argument vector, unordered. | ||
Returns: | ||
The aggregation of the ordered argument vector with the weight vector. | ||
""" | ||
# namedtuple with 'values' and 'indices' properties | ||
ordered_argument_vector = torch.sort(input_observation, descending=True) | ||
return (self.weights * ordered_argument_vector.values).sum() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
""" | ||
Implements the t-norm fuzzy relations. | ||
""" | ||
|
||
from enum import Enum | ||
|
||
import torch | ||
|
||
|
||
class TNorm(Enum): | ||
""" | ||
Enumerates the types of t-norms. | ||
""" | ||
|
||
PRODUCT = "product" # i.e., algebraic product | ||
MINIMUM = "minimum" | ||
ACZEL_ALSINA = "aczel_alsina" # not yet implemented | ||
SOFTMAX_SUM = "softmax_sum" | ||
SOFTMAX_MEAN = "softmax_mean" | ||
LUKASIEWICZ = "generalized_lukasiewicz" | ||
# the following are to be implemented | ||
DRASTIC = "drastic" | ||
NILPOTENT = "nilpotent" | ||
HAMACHER = "hamacher" | ||
EINSTEIN = "einstein" | ||
YAGER = "yager" | ||
DUBOIS = "dubois" | ||
DIF = "dif" | ||
|
||
|
||
class AlgebraicProduct(torch.nn.Module): # TODO: remove this class | ||
""" | ||
Implementation of the Algebraic Product t-norm (Fuzzy AND). | ||
""" | ||
|
||
def __init__(self, in_features=None, importance=None): | ||
""" | ||
Initialization. | ||
INPUT: | ||
- in_features: shape of the input | ||
- centers: trainable parameter | ||
- sigmas: trainable parameter | ||
importance is initialized to a one vector by default | ||
""" | ||
super().__init__() | ||
self.in_features = in_features | ||
|
||
# initialize antecedent importance | ||
if importance is None: | ||
self.importance = torch.nn.parameter.Parameter(torch.tensor(1.0)) | ||
self.importance.requires_grad = False | ||
else: | ||
if not isinstance(importance, torch.Tensor): | ||
importance = torch.Tensor(importance) | ||
self.importance = torch.nn.parameter.Parameter( | ||
torch.abs(importance) | ||
) # importance can only be [0, 1] | ||
self.importance.requires_grad = True | ||
|
||
def forward(self, elements): | ||
""" | ||
Forward pass of the function. | ||
Applies the function to the input elementwise. | ||
""" | ||
self.importance = torch.nn.parameter.Parameter( | ||
torch.abs(self.importance) | ||
) # importance can only be [0, 1] | ||
return torch.prod(torch.mul(elements, self.importance)) |
Empty file.
Oops, something went wrong.